適用於商家的付款資料密碼編譯機制

Google Pay API 會利用已簽署的加密 PaymentMethodToken 酬載傳回付款方式。傳回的付款方式是由 PAN 組成的卡片,或是由裝置 PAN 和密文組成的權杖化卡片。

酬載包含 protocolVersion 欄位,可讓酬載的接收者瞭解使用中的密碼編譯基元是哪些,以及這些基元的預期格式。

本指南將說明如何產生公開金鑰,藉此要求 Google 簽署及加密的付款方式權杖;另也提供驗證及解密該權杖的詳細步驟。

本指南僅適用於 protocolVersion = ECv2

由於您會直接收到付款卡資訊,因此在繼續操作之前,請先確認您的應用程式符合 PCI DSS 規範,伺服器也具備所需的基礎架構,可以安全地處理使用者的付款憑證。

以下簡單說明整合商使用 Google Pay API ECv2 PaymentMethodToken 酬載時必須採取的步驟:

  1. 擷取 Google 根簽署金鑰
  2. 透過任何未過期的根簽署金鑰,驗證中繼簽署金鑰的簽章是否有效。
  3. 驗證酬載的中繼簽署金鑰尚未過期。
  4. 透過中繼簽署金鑰,驗證酬載的簽章是否有效。
  5. 先驗證簽章,再將酬載的內容解密。
  6. 驗證訊息是否尚未過期。這個步驟需要您查看目前的時間是否早於解密內容中的 messageExpiration 欄位。
  7. 使用解密內容中的付款方式收費。

Tink 程式庫中的程式碼範例可用來執行步驟 1 到 6。

付款方式權杖的結構

Google 在 PaymentData 回應中傳回的訊息是採用 UTF-8 編碼的序列化 JSON 物件,其中包含下表中指定的金鑰:

名稱 類型 說明
protocolVersion 字串 指出建立訊息時使用的加密或簽署配置。如有需要,此項目能讓通訊協定隨時間更新。
signature 字串 驗證訊息確實來自 Google。這個字串採用 Base64 編碼,而且是透過中繼簽署金鑰以 ECDSA 建立而成。
intermediateSigningKey 物件 內含 Google 中繼簽署金鑰的 JSON 物件,其中包含擁有 keyValuekeyExpirationsignaturessignedKey。為簡化中繼簽署金鑰的簽章驗證程序,這個物件已經過序列化處理。
signedMessage 字串 已序列化為 HTML 安全字串的 JSON 物件,當中包含 encryptedMessageephemeralPublicKeytag。為簡化簽章的驗證程序,這個物件已經過序列化處理。

示例

以下是採 JSON 格式的付款方式權杖回應:

{
  "protocolVersion":"ECv2",
  "signature":"MEQCIH6Q4OwQ0jAceFEkGF0JID6sJNXxOEi4r+mA7biRxqBQAiAondqoUpU/bdsrAOpZIsrHQS9nwiiNwOrr24RyPeHA0Q\u003d\u003d",
  "intermediateSigningKey":{
    "signedKey": "{\"keyExpiration\":\"1542323393147\",\"keyValue\":\"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/1+3HBVSbdv+j7NaArdgMyoSAM43yRydzqdg1TxodSzA96Dj4Mc1EiKroxxunavVIvdxGnJeFViTzFvzFRxyCw\\u003d\\u003d\"}",
    "signatures": ["MEYCIQCO2EIi48s8VTH+ilMEpoXLFfkxAwHjfPSCVED/QDSHmQIhALLJmrUlNAY8hDQRV/y1iKZGsWpeNmIP+z+tCQHQxP0v"]
  },
  "signedMessage":"{\"tag\":\"jpGz1F1Bcoi/fCNxI9n7Qrsw7i7KHrGtTf3NrRclt+U\\u003d\",\"ephemeralPublicKey\":\"BJatyFvFPPD21l8/uLP46Ta1hsKHndf8Z+tAgk+DEPQgYTkhHy19cF3h/bXs0tWTmZtnNm+vlVrKbRU9K8+7cZs\\u003d\",\"encryptedMessage\":\"mKOoXwi8OavZ\"}"
}

中繼簽署金鑰

intermediateSigningKey 是採用 UTF-8 編碼的序列化 JSON 物件,其包含下列值:

名稱 類型 說明
signedKey 字串 採用 Base64 編碼的訊息,當中包含金鑰的付款說明。
signatures 字串 驗證中繼簽署金鑰確實來自 Google。這個字串採用 Base64 編碼,而且是以 ECDSA 建立而成。

已簽署的金鑰

signedKey 是採用 UTF-8 編碼的序列化 JSON 物件,其包含下列值:

名稱 類型 說明
keyValue 字串 採用 ASN.1 類型編碼的 Base64 版本金鑰,SubjectPublicKeyInfo 如 X.509 標準所定義。
keyExpiration 字串 中繼金鑰的效期截止日期與時間,以毫秒為單位自 Epoch 紀元時間起算 (採用世界標準時間)。整合商會拒絕所有已過期的金鑰。

已簽署的訊息

signedMessage 是採用 UTF-8 編碼的序列化 JSON 物件,其包含下列值:

名稱 類型 說明
encryptedMessage 字串 採用 Base64 編碼的加密訊息,當中包含付款資訊和一些額外的安全性欄位
ephemeralPublicKey 字串 與私密金鑰相關聯並採用 Base64 編碼的暫時公開金鑰,可用來加密未壓縮點格式的訊息。詳情請參閱加密公開金鑰格式一節。
tag 字串 採用 Base64 編碼的 encryptedMessage MAC。

已加密的訊息

解密後的 encryptedMessage 是採 UTF-8 編碼的序列化 JSON 物件。JSON 共有兩層,外層包含基於安全考量而納入的中繼資料和欄位,內層則是另一個代表實際付款憑證的 JSON 物件。

如要進一步瞭解 encryptedMessage,請參閱下方表格和 JSON 物件示例:

名稱 類型 說明
gatewayMerchantId 字串

處理方可解讀的不重複商家 ID,用於驗證訊息是否屬於提出要求的商家。由處理方建立,商家會透過 Android網頁版PaymentMethodTokenizationSpecification,將 ID 傳送給 Google。

messageExpiration 字串 訊息的效期截止日期與時間,以毫秒為單位自 Epoch 紀元時間起算 (採用世界標準時間)。整合商應拒絕任何已過期的訊息。
messageId 字串 可用於識別訊息的唯一識別碼,日後需要撤銷或找出訊息時即可使用。
paymentMethod 字串 付款憑證的類型,目前僅支援 CARD
paymentMethodDetails 物件 付款憑證本身。這個物件的格式會由 paymentMethod 決定,詳細說明請見下表。

卡片

CARD 付款方式的付款憑證是由下列屬性組成:

名稱 類型 說明
pan 字串 可供扣款的個人帳號,只能由數字組成。
expirationMonth 數字 卡片的到期月分,其中 1 代表 1 月,2 代表 2 月,依此類推。
expirationYear 數字 卡片的到期年分,共四位數,例如 2020。
authMethod 字串 卡片交易的驗證方法。
assuranceDetails AssuranceDetailsSpecifications 這個物件會提供相關資訊,說明系統對傳回的付款憑證執行的驗證作業。

PAN_ONLY

下列 JSON 程式碼片段為 CARD paymentMethod 適用的完整 encryptedMessage 示例,而且這個付款方式具備 PAN_ONLY 這種 authMethod

  "paymentMethod": "CARD",
  "paymentMethodDetails": {
    "authMethod": "PAN_ONLY",
    "pan": "1111222233334444",
    "expirationMonth": 10,
    "expirationYear": 2020
  },  "gatewayMerchantId": "some-merchant-id",  "messageId": "some-message-id",
  "messageExpiration": "1577862000000"
}

CRYPTOGRAM_3DS

已利用 3-D Secure 密文「CRYPTOGRAM_3DS authMethod」驗證的 CARD,當中包含下列額外欄位:

名稱 類型 說明
cryptogram 字串 3-D Secure 密文。
eciIndicator 字串 不一定存在。系統只會針對 Visa 發卡機構的權杖傳回這個字串。這個值會透過付款授權要求傳送。

以下 JSON 程式碼片段是具有 CRYPTOGRAM_3DS authMethodCARD paymentMethod 所適用的完整 encryptedMessage 示例:

{
  "paymentMethod": "CARD",
  "paymentMethodDetails": {
    "authMethod": "CRYPTOGRAM_3DS",
    "pan": "1111222233334444",
    "expirationMonth": 10,
    "expirationYear": 2020,
    "cryptogram": "AAAAAA...",
    "eciIndicator": "eci indicator"
    
  },
  
  "messageId": "some-message-id",
  "messageExpiration": "1577862000000"
}

驗證簽章

驗證簽章 (包含中繼金鑰和訊息簽章) 所需的項目如下:

  • 用來建立簽章的演算法
  • 用來建立簽章的位元組字串
  • 與用來建立簽章的私密金鑰相對應的公開金鑰
  • 簽章本身

簽章演算法

Google 是採用橢圓曲線數位簽章演算法 (ECDSA) 來簽署訊息,所用的參數如下:透過 NIST P-256 執行 ECDSA,以 SHA-256 做為雜湊函式 (如 FIPS 186-4 所定義)。

簽章

簽章位於訊息的最外層,並採用 Base64 編碼和 ASN.1 位元組格式。如要進一步瞭解 ASN.1,請參閱 IETF 工具附錄 A 。簽章由 ECDSA 整數 r 和 s 組成,詳情請參閱簽章產生演算法說明。

以下是前述 ASN.1 位元組格式的示例 (此為透過實作 Java Cryptography Extension (JCE) ECDSA 所產生的標準格式)。

ECDSA-Sig-Value :: = SEQUENCE {
 r INTEGER,
 s INTEGER
}

如何建立中繼簽署金鑰簽章的位元組字串

如要驗證付款方式權杖範例中的中繼簽署金鑰簽章,請使用下列公式建立 signedStringForIntermediateSigningKeySignature

signedStringForIntermediateSigningKeySignature =
length_of_sender_id || sender_id || length_of_protocol_version || protocol_version || length_of_signed_key || signed_key

「||」符號代表串連。每個元件 (sender_idprotocolVersionsignedKey) 都必須採用 UTF-8 編碼。signedKey 必須是 intermediateSigningKey.signedKey 的字串,且位元組長度均為 4 個位元組 (採用位元組由小到大格式)。

示例

本示例使用的付款方式權杖範例如下:

{
  "protocolVersion":"ECv2",
  "signature":"MEQCIH6Q4OwQ0jAceFEkGF0JID6sJNXxOEi4r+mA7biRxqBQAiAondqoUpU/bdsrAOpZIsrHQS9nwiiNwOrr24RyPeHA0Q\u003d\u003d",
  "intermediateSigningKey":{
    "signedKey": "{\"keyExpiration\":\"1542323393147\",\"keyValue\":\"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/1+3HBVSbdv+j7NaArdgMyoSAM43yRydzqdg1TxodSzA96Dj4Mc1EiKroxxunavVIvdxGnJeFViTzFvzFRxyCw\\u003d\\u003d\"}",
    "signatures": ["MEYCIQCO2EIi48s8VTH+ilMEpoXLFfkxAwHjfPSCVED/QDSHmQIhALLJmrUlNAY8hDQRV/y1iKZGsWpeNmIP+z+tCQHQxP0v"]
  },
  "signedMessage":"{\"tag\":\"jpGz1F1Bcoi/fCNxI9n7Qrsw7i7KHrGtTf3NrRclt+U\\u003d\",\"ephemeralPublicKey\":\"BJatyFvFPPD21l8/uLP46Ta1hsKHndf8Z+tAgk+DEPQgYTkhHy19cF3h/bXs0tWTmZtnNm+vlVrKbRU9K8+7cZs\\u003d\",\"encryptedMessage\":\"mKOoXwi8OavZ\"}"
}

sender_id 一律為 Google,而 protocol_version 則為 ECv2

如果 sender_idGoogle,則 signedString 會如下例所示:

signedStringForIntermediateSigningKeySignature =
\x06\x00\x00\x00 || Google || | \x04\x00\x00\x00 || ECv2 || \xb5\x00\x00\x00 || {"keyExpiration":"1542323393147","keyValue":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/1+3HBVSbdv+j7NaArdgMyoSAM43yRydzqdg1TxodSzA96Dj4Mc1EiKroxxunavVIvdxGnJeFViTzFvzFRxyCw\u003d\u003d"}

如何根據 signedStringForIntermediateSigningKeySignature 驗證簽章

系統會使用標準的 ECDSA 驗證演算法,組合中繼簽署金鑰簽章的已簽署字串。針對 ECv2 通訊協定,您必須個別處理 intermediateSigningKey.signatures 中的所有簽章,並嘗試利用 keys.json 中未到期的 Google 簽署金鑰驗證每個簽章。如果至少有一個簽章可通過驗證,您就能認定驗證作業已順利完成。請稍後使用 intermediateSigningKey.signedKey.keyValue 驗證 signedStringForMessageSignature。Google 強烈建議您使用既有的密碼編譯程式庫,而非自己的驗證作業程式碼。

如何建立訊息簽章的位元組字串

如要驗證範例付款方式權杖中的簽章,請使用下列公式建構 signedStringForMessageSignature

signedStringForMessageSignature =
length_of_sender_id || sender_id || length_of_recipient_id || recipient_id || length_of_protocolVersion || protocolVersion || length_of_signedMessage || signedMessage

「||」符號代表串連。每個元件 (sender_idrecipient_idprotocolVersionsignedMessage) 都必須採用 UTF-8 編碼,且位元組長度均為 4 個位元組 (採用位元組由小到大格式)。建構位元組字串時,請勿剖析或修改 signedMessage。例如,請勿將 \u003d 替換成 = 字元。

示例

以下是付款方式權杖舉例:

{
  "protocolVersion":"ECv2",
  "signature":"MEQCIH6Q4OwQ0jAceFEkGF0JID6sJNXxOEi4r+mA7biRxqBQAiAondqoUpU/bdsrAOpZIsrHQS9nwiiNwOrr24RyPeHA0Q\u003d\u003d",
  "intermediateSigningKey":{
    "signedKey": "{\"keyExpiration\":\"1542323393147\",\"keyValue\":\"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/1+3HBVSbdv+j7NaArdgMyoSAM43yRydzqdg1TxodSzA96Dj4Mc1EiKroxxunavVIvdxGnJeFViTzFvzFRxyCw\\u003d\\u003d\"}",
    "signatures": ["MEYCIQCO2EIi48s8VTH+ilMEpoXLFfkxAwHjfPSCVED/QDSHmQIhALLJmrUlNAY8hDQRV/y1iKZGsWpeNmIP+z+tCQHQxP0v"]
  },
  "signedMessage":"{\"tag\":\"jpGz1F1Bcoi/fCNxI9n7Qrsw7i7KHrGtTf3NrRclt+U\\u003d\",\"ephemeralPublicKey\":\"BJatyFvFPPD21l8/uLP46Ta1hsKHndf8Z+tAgk+DEPQgYTkhHy19cF3h/bXs0tWTmZtnNm+vlVrKbRU9K8+7cZs\\u003d\",\"encryptedMessage\":\"mKOoXwi8OavZ\"}"
}

sender_id 一律為 Google,而 recipient_id 則為 merchant:merchantIdmerchantIdGoogle Pay 商家主控台中針對具備實際工作環境存取權商家所找到的值相符。

如果 sender_idGooglerecipient_idmerchant:12345,則 signedString 會以下例中的方式顯示:

signedStringForMessageSignature =
\x06\x00\x00\x00 || Google || \x0e\x00\x00\x00 || merchant:12345 || | \x04\x00\x00\x00 || ECv2 || \xd2\x00\x00\x00 || {"tag":"jpGz1F1Bcoi/fCNxI9n7Qrsw7i7KHrGtTf3NrRclt+U\u003d","ephemeralPublicKey":"BJatyFvFPPD21l8/uLP46Ta1hsKHndf8Z+tAgk+DEPQgYTkhHy19cF3h/bXs0tWTmZtnNm+vlVrKbRU9K8+7cZs\u003d","encryptedMessage":"mKOoXwi8OavZ"}

如何根據 signedStringForMessageSignature 驗證簽章

系統會使用標準的 ECDSA 驗證演算法,組合已簽署字串。在上一個步驟中驗證的 intermediateSigningKey.signedKey.keyValue 會用來驗證 signedMessage。Google 強烈建議您使用既有的密碼編譯程式庫,而非自己的驗證作業程式碼。

加密配置規格

Google 使用橢圓曲線整合式加密配置 (ECIES) 來確保 Google Pay API 回應中傳回的付款方式權杖安全無虞。這種加密配置所使用的參數如下:

參數 定義
金鑰封裝方法

ECIES-KEM (如 ISO 18033-2 所定義)。

  • 橢圓曲線:NIST P-256 (在 OpenSSL 中也稱為 prime256v1)。
  • CheckModeOldCofactorModeSingleHashModeCofactorMode為 0。
  • 採未壓縮點格式。
金鑰衍生函式

採 HMAC 和 SHA-256 (HKDFwithSHA256)。

  • 請勿提供鹽。
  • 資訊必須是 Google 採用 ASCII 編碼而成,且適用於通訊協定版本 ECv2
  • 必須要有 256 位元是針對 AES256 金鑰衍生,另外 256 位元則必須針對 HMAC_SHA256 金鑰衍生。
對稱加密演算法

DEM2 (如 ISO 18033-2 所定義)

加密演算法:初始向量為零且未填充的 AES-256-CTR。

MAC 演算法 HMAC_SHA256 (具備金鑰衍生函式產生的 256 位元金鑰)。

加密公開金鑰格式

加密公開金鑰和 Google 酬載傳回的 ephemeralPublicKey 都是採用未壓縮點格式的 Base64 編碼金鑰,當中包含下列兩項元素:

  • 一個用來指定格式的魔術數字 (0x04)。
  • 兩個較大的 32 位元組整數,代表橢圓曲線中的 X 和 Y 座標。

如要進一步瞭解這種格式,請參閱《Public Key Cryptography For The Financial Services Industry: The Elliptic Curve Digital Signature Algorithm (ECDSA)》(適用於金融服務產業的公開金鑰密碼編譯:橢圓曲線數位簽章演算法 (ECDSA)),ANSI X9.62,1998。

使用 OpenSSL 產生公開金鑰

步驟 1:產生私密金鑰

下列示例會產生適合與 NIST P-256 搭配使用的橢圓曲線私密金鑰,並將金鑰寫入 key.pem

openssl ecparam -name prime256v1 -genkey -noout -out key.pem

選擇性操作:查看私密與公開金鑰

您可以使用下列指令來查看私密與公開金鑰:

openssl ec -in key.pem -pubout -text -noout

這個指令會輸出與以下類似的內容:

read EC key
Private-Key: (256 bit)
priv:
    08:f4:ae:16:be:22:48:86:90:a6:b8:e3:72:11:cf:
    c8:3b:b6:35:71:5e:d2:f0:c1:a1:3a:4f:91:86:8a:
    f5:d7
pub:
    04:e7:68:5c:ff:bd:02:ae:3b:dd:29:c6:c2:0d:c9:
    53:56:a2:36:9b:1d:f6:f1:f6:a2:09:ea:e0:fb:43:
    b6:52:c6:6b:72:a3:f1:33:df:fa:36:90:34:fc:83:
    4a:48:77:25:48:62:4b:42:b2:ae:b9:56:84:08:0d:
    64:a1:d8:17:66
ASN1 OID: prime256v1

步驟 2:產生採用 Base64 編碼的公開金鑰

上述選擇性步驟示例中產生的私密與公開金鑰會採用十六進位編碼。如要取得採 Base64 編碼和未壓縮點格式的公開金鑰,請使用下列指令:

openssl ec -in key.pem -pubout -text -noout 2> /dev/null | grep "pub:" -A5 | sed 1d | xxd -r -p | base64 | paste -sd "\0" - | tr -d '\n\r ' > publicKey.txt

這個指令會輸出內容如下的 publicKey.txt 檔案,即採用未壓縮點格式的 Base64 版本金鑰:

BOdoXP+9Aq473SnGwg3JU1aiNpsd9vH2ognq4PtDtlLGa3Kj8TPf+jaQNPyDSkh3JUhiS0KyrrlWhAgNZKHYF2Y=

檔案內容不得含有多餘的空格或回車字元。如要確認內容未違反這項規定,請在 Linux 或 MacOS 中執行下列指令:

od -bc publicKey.txt

步驟 3:產生採用 PKCS #8 格式的 Base64 編碼私密金鑰

Tink 程式庫會預期您的私密金鑰是採用 Base64 編碼與 PKCS #8 格式。請使用下列指令,以您在第一個步驟中產生的私密金鑰產生這種格式的私密金鑰:

openssl pkcs8 -topk8 -inform PEM -outform DER -in key.pem -nocrypt | base64 | paste -sd "\0" -

這個指令會輸出與以下類似的內容:

MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgWV4oK8c/MZkCLk4qSCNjW0Zm6H0CBCtSYxkXkC9FBHehRANCAAQPldOnhO2/oXjdJD1dwlFPiNs6fcdoRgFu3/Z0iKj24SjTGyLRGAtYWLGXBZcDdPj3T2bJRHRVhE8Bc2AjkT7n

如何解密付款方式權杖

請按照下列步驟解密權杖:

  1. 使用您的私密金鑰和取得的 ephemeralPublicKey,衍生出採用 ECIES-KEM 的 512 位元共用金鑰。請使用以下參數:
    • 橢圓曲線:NIST P-256 (在 OpenSSL 中又稱為「prime256v1」)。
    • CheckModeOldCofactorModeSingleHashModeCofactorMode0
    • 編碼函式:未壓縮點格式。
    • 金鑰衍生函式:HKDFwithSHA256 (如 RFC 5869 中所述),其中包含下列參數:
      • 請勿提供鹽。根據 RFC,這必須等同於 32 個零位元組的鹽。
  2. 將產生的金鑰分成兩組長度為 256 位元的金鑰:symmetricEncryptionKeymacKey
  3. 驗證 tag 欄位是 encryptedMessage 的有效 MAC。

    如要產生預期的 MAC,請使用具備雜湊函式 SHA256 的 HMAC (RFC 5869) 和步驟 2 中取得的 macKey

  4. 使用 AES-256-CTR 模式並按照以下條件將 encryptedMessage 解密:

    • 初始向量為零。
    • 未填補。
    • 步驟 2 中產生的 symmetricEncryptionKey

管理金鑰

商家加密金鑰

商家會根據加密配置規格一節中所述的規格產生公開金鑰。

Google 根簽署金鑰

Google 會發布目前有效的根簽署公開金鑰組,您可以透過公開網址擷取這組金鑰。網址傳回的 HTTP 快取標頭會註明金鑰的有效期限。系統會將這些金鑰加入快取,直到金鑰到期為止,而到期日是由「keyExpiration」欄位決定。如果擷取作業已過期,建議您重新透過公開網址擷取金鑰,以便取得最新的有效金鑰清單。

ECv2 通訊協定的例外狀況:如果您無法在執行階段中透過 Google 擷取金鑰,請改用我們的實際工作環境網址來擷取 keys.json,將其儲存至您的系統,然後定期手動重新整理。在正常情況下,Google 會在有效期限最長的金鑰到期五年前,發出新的 ECv2 根簽署金鑰。如果金鑰遭到盜用,Google 會透過自助入口網站中的聯絡資訊通知所有商家,以便盡早要求重新載入 keys.json。如果貴商家選擇將 Google 金鑰儲存在 keys.json 內容中,為確保您記得定期輪替金鑰,我們會建議您每年重新整理一次,藉此完成每年的金鑰輪替作業。

您透過公開網址取得的金鑰會按照以下格式顯示:

{
  "keys": [
    {
      "keyValue": "encoded public key",
      "protocolVersion": "ECv2"
      "keyExpiration":"2000000000000"
    },
    {
      "keyValue": "encoded public key",
      "protocolVersion": "ECv2"
      "keyExpiration":"3000000000000"
    }
  ]
}

keyValue 是不換行或填充的 Base64 版本金鑰,並採用 ASN.1 類型 SubjectPublicKeyInfo 編碼 (如 X.509 標準所定義)。在 Java 中,前述的 ASN.1 編碼是以 X509EncodedKeySpec 類別表示,您可以使用 ECPublicKey.getEncoded() 取得。

測試版和正式版環境的網址如下:

金鑰輪替

如果您在具備直接整合功能的伺服器上,直接將付款方式權杖解密,則必須每年輪替一次金鑰。

如要輪替加密金鑰,請完成下列步驟:

  1. 透過 OpenSSL 產生新的金鑰組
  2. 在登入 Google 帳戶的狀態下開啟 Google Pay 商家主控台。使用的帳戶是您先前用來申請成為 Google Pay 開發人員的 Google 帳戶。
  3. 在「Google Pay API」分頁的「直接整合」窗格下方,按一下現有公開金鑰旁的「管理」。按一下「新增其他金鑰」
  4. 選取「公開加密金鑰」文字輸入欄位,然後新增您剛剛產生的未壓縮點格式 Base64 編碼公開金鑰。
  5. 按一下「儲存加密金鑰」
  6. 為順利執行金鑰的輪替作業,請在替換金鑰時確保新舊私密金鑰均適用於解密作業。

    如果您使用 Tink 程式庫解密權杖,請使用下列 Java 程式碼來支援使用多個私密金鑰:

    String decryptedMessage =
        new PaymentMethodTokenRecipient.Builder()
            .addRecipientPrivateKey(newPrivateKey)
            .addRecipientPrivateKey(oldPrivateKey);

    請確認解密作業的程式碼已部署至實際工作環境,解密作業也能順利完成。

  7. 變更程式碼中使用的公開金鑰。

    替換 PaymentMethodTokenizationSpecification parameters 屬性中的 publicKey 屬性值:

    const tokenizationSpecification = {
      "type": "DIRECT",
      "parameters": {
        "protocolVersion": "ECv2",
        "publicKey": "BOdoXP1aiNp.....kh3JUhiSZKHYF2Y="
      }
    }
  8. 將步驟 4 中的程式碼部署至實際工作環境。程式碼部署完畢之後,加密和解密交易就會使用新的金鑰組。
  9. 確認系統已不再使用舊的公開金鑰來加密任何交易。

  10. 移除舊版私密金鑰。
  11. 登入您先前用來申請成為 Google Pay 開發人員的 Google 帳戶,然後開啟 Google Pay 商家主控台
  12. 在「Google Pay API」分頁的「直接整合」窗格下,按一下現有公開金鑰旁的「管理」。按一下舊公開金鑰旁的「刪除」,然後按一下「儲存加密金鑰」

Google 會使用 PaymentMethodTokenizationSpecification parameters 物件中 publicKey 屬性指定的金鑰,如以下範例所示:

{
  "protocolVersion": "ECv2",
  "publicKey": "BOdoXP+9Aq473SnGwg3JU1..."
}

使用 Tink 程式庫管理加密回應

如要驗證簽名及解密訊息,請使用 Tink 加密編譯程式庫。如要與 Tink 整合並執行驗證和解密作業,請完成下列步驟:

  1. 在您的 pom.xml 中,新增 Tink paymentmethodtoken 應用程式做為依附元件:

    <dependencies>
      <!-- other dependencies ... -->
      <dependency>
        <groupId>com.google.crypto.tink</groupId>
        <artifactId>apps-paymentmethodtoken</artifactId>
        <version>1.2.0</version>  <!-- or latest version -->
      </dependency>
    </dependencies>
    
  2. 在伺服器啟動時,預先擷取 Google 簽署金鑰,讓該金鑰保存在記憶體中, 以防解密程序中的金鑰擷取作業產生網路延遲情況,對使用者造成不便。

    GooglePaymentsPublicKeysManager.INSTANCE_PRODUCTION.refreshInBackground();
  3. 使用下列程式碼來解密金鑰 (這個程式碼假設 paymentMethodToken 儲存於 encryptedMessage 變數中),並視情況替換粗體的部分。

    如要測試環境,請將 INSTANCE_PRODUCTION 替換為 INSTANCE_TEST,並將 [YOUR MERCHANT ID] 替換為 12345678901234567890

    String decryptedMessage =
        new PaymentMethodTokenRecipient.Builder()
        .fetchSenderVerifyingKeysWith(
            GooglePaymentsPublicKeysManager.INSTANCE_PRODUCTION)
        .recipientId("merchant:YOUR_MERCHANT_ID")
        // This guide applies only to protocolVersion = ECv2
        .protocolVersion("ECv2")
        // Multiple private keys can be added to support graceful
        // key rotations.
        .addRecipientPrivateKey(PrivateKey1)
        .addRecipientPrivateKey(PrivateKey2)
        .build()
        .unseal(encryptedMessage);
    
  4. 以您自己的金鑰取代 PrivateKey1PrivateKey2。變數可以是採用 Base64 編碼的 PKCS8 字串或 ECPrivateKey 物件。如要進一步瞭解如何產生採用 Base64 編碼的 PKCS8 私密金鑰,請參閱使用 OpenSSL 產生金鑰組的說明。

  5. 如果您無法在每次解密金鑰時都呼叫 Google 伺服器,請使用下列程式碼進行解密,並視情況替換粗體的部分。

    String decryptedMessage =
        new PaymentMethodTokenRecipient.Builder()
        .addSenderVerifyingKey("ECv2 key fetched from test or production url")
        .recipientId("merchant:YOUR_MERCHANT_ID")
        // This guide applies only to protocolVersion = ECv2
        .protocolVersion("ECv2")
        // Multiple private keys can be added to support graceful
        // key rotations.
        .addRecipientPrivateKey(PrivateKey1)
        .addRecipientPrivateKey(PrivateKey2)
        .build()
        .unseal(encryptedMessage);
    

    在正常情況下,實際工作環境中的現有金鑰會在 2038 年 4 月 14 日到期,除非金鑰遭到盜用。如果金鑰遭到盜用,Google 會透過自助入口網站中的聯絡資訊通知所有商家,以便盡早要求重新載入 keys.json

    程式碼片段會執行下列安全性細部作業,讓您專心處理與使用酬載相關的工作:

    • 擷取 Google 簽署金鑰,並儲存至快取記憶體
    • 驗證簽章
    • 解密