Google Pay API 会以签名的加密 PaymentMethodToken 载荷返回付款方式。返回的付款方式是由 PAN 组成的卡,或由设备 PAN 和密码组成的令牌化卡。
负载中含有一个名为 protocolVersion 的字段,该字段会告诉负载接收者正在使用哪些加密基元以及预期的格式。
本指南介绍了如何生成公钥以请求由 Google 签名和加密的付款方式令牌,并详述了验证和解密令牌的步骤。
本指南仅适用于 protocolVersion = ECv2。
由于您直接接收支付卡信息,因此请确保您的应用符合 PCI DSS,并确保您的服务器具有所需的基础架构,可在您继续操作之前安全地处理用户的付款凭据。
以下步骤概述了集成商在使用 Google Pay API ECv2 PaymentMethodToken 载荷时必须执行的操作:
- 获取 Google 根签名密钥。
- 通过任何未到期的根签名密钥,验证中间签名密钥的签名是否有效。
- 验证负载的中间签名密钥是否到期。
- 通过中间签名密钥,验证载荷的签名是否有效。
- 在验证签名后解密载荷的内容。
- 验证消息是否到期。您需要检查当前时间是否早于解密内容中的 messageExpiration字段。
- 使用解密内容中的付款方式并扣取费用。
Tink 库中的示例代码可执行第 1-6 步。
付款方式令牌结构
Google 在 PaymentData 响应中返回的消息是 UTF-8 编码的序列化 JSON 对象,具有下表中指定的键:
| 名称 | 类型 | 说明 | 
|---|---|---|
| protocolVersion | 字符串 | 标识创建消息时所使用的加密或签名架构。如果需要,您可以通过此参数让协议随时间演进。 | 
| signature | 字符串 | 验证消息是否来自 Google。此参数采用 Base64 编码,并通过中间签名密钥使用 ECDSA 创建而成。 | 
| intermediateSigningKey | 对象 | 含有 Google 所提供的中间签名密钥的 JSON 对象。它包含带 keyValue、keyExpiration和signatures的signedKey。已经过序列化处理,可简化中间签名密钥的签名验证过程。 | 
| signedMessage | 字符串 | 已序列化为 HTML 安全字符串的 JSON 对象,其中包含 encryptedMessage、ephemeralPublicKey和tag。它已经过序列化处理,可简化签名验证过程。 | 
示例
以下是采用 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 | 字符串 | encryptedMessage的 MAC,采用 Base64 编码。 | 
加密的消息
解密后的 encryptedMessage 是一个采用 UTF-8 编码的序列化 JSON 对象。该 JSON 共有两层。外层包含元数据和出于安全目的而加入的字段,而内层则是另一个代表实际付款凭据的 JSON 对象。
如需详细了解 encryptedMessage,请参阅以下表格和 JSON 对象示例:
| 名称 | 类型 | 说明 | 
|---|---|---|
| gatewayMerchantId | 字符串 | 商家的唯一标识符,处理方将解读并使用该标识符来验证消息是否针对提出请求的商家。该标识符由处理方创建,并由商家在 Android 或 Web 上利用  | 
| messageExpiration | 字符串 | 消息到期的日期和时间(采用世界协调时间,以毫秒为单位,从 Epoch 起算)。集成商应拒绝所有过期的消息。 | 
| messageId | 字符串 | 用于标识消息的唯一 ID,可在日后需要撤消或定位消息时使用。 | 
| paymentMethod | 字符串 | 付款凭据的类型。目前只支持 CARD。 | 
| paymentMethodDetails | 对象 | 付款凭据本身。此对象的格式由 paymentMethod决定,详情请见下表。 | 
卡
以下属性构成了 CARD 付款方式的付款凭据:
| 名称 | 类型 | 说明 | 
|---|---|---|
| pan | 字符串 | 扣费的个人帐号。该字符串仅包含数字。 | 
| expirationMonth | 数字 | 卡的到期月份,其中 1 代表 1 月,2 代表 2 月,依此类推。 | 
| expirationYear | 数字 | 卡的四位数到期年份,例如 2020。 | 
| authMethod | 字符串 | 卡交易的身份验证方法。 | 
| assuranceDetails | AssuranceDetailsSpecifications | 此对象可提供对返回的付款凭据执行验证的相关信息。 | 
PAN_ONLY
以下 JSON 代码段示例展示了针对包含 PAN_ONLY authMethod 的 CARD paymentMethod 的完整 encryptedMessage。
"paymentMethod": "CARD", "paymentMethodDetails": { "authMethod": "PAN_ONLY", "pan": "1111222233334444", "expirationMonth": 10, "expirationYear": 2020 }, "gatewayMerchantId": "some-merchant-id", "messageId": "some-message-id", "messageExpiration": "1577862000000" }
CRYPTOGRAM_3DS
采用 3D 安全密文 (CRYPTOGRAM_3DS authMethod) 进行身份验证的 CARD。它包含以下额外字段:
| 名称 | 类型 | 说明 | 
|---|---|---|
| cryptogram | 字符串 | 3D 安全密文。 | 
| eciIndicator | 字符串 | 不一定提供。仅会针对 Visa 卡网络上的令牌返回该字符串。此值通过付款授权请求传递。 | 
以下 JSON 代码段示例展示了针对包含 CRYPTOGRAM_3DS authMethod 的 CARD 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_id、protocolVersion、signedKey)都必须采用 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_id 为 Google,则 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_id、recipient_id、protocolVersion、signedMessage)都必须采用 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:merchantId。merchantId 与具有生产环境访问权限的商家的 Google Pay 业务控制台中的值一致。
如果 sender_id 为 Google,且 recipient_id 为 merchant: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 中所定义)。 
 | 
| 密钥推导函数 | 基于 HMAC 并采用 SHA-256 ( 
 | 
| 对称加密算法 | DEM2(如 ISO 18033-2 中所定义) 加密算法:IV 为零且未填充的 AES-256-CTR。 | 
| MAC 算法 | HMAC_SHA256,采用从密钥推导函数推导出的 256 位密钥。 | 
加密公钥格式
加密公钥和 Google 载荷中返回的 ephemeralPublicKey 都是采用未压缩点格式的 Base64 版密钥。它包含以下两个元素:
- 用于指定格式的幻数 (0x04)。
- 两个 32 字节的大整数,表示椭圆曲线中的 X 和 Y 坐标。
如需详细了解这种格式,请参阅“金融服务行业的公钥密码学:椭圆曲线数字签名算法(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 库期望您的私钥以 PKCS #8 格式进行 Base64 编码。要根据第一步中生成的私钥生成此格式的私钥,请使用以下命令:
openssl pkcs8 -topk8 -inform PEM -outform DER -in key.pem -nocrypt | base64 | paste -sd "\0" -
该命令会产生类似于以下内容的输出:
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgWV4oK8c/MZkCLk4qSCNjW0Zm6H0CBCtSYxkXkC9FBHehRANCAAQPldOnhO2/oXjdJD1dwlFPiNs6fcdoRgFu3/Z0iKj24SjTGyLRGAtYWLGXBZcDdPj3T2bJRHRVhE8Bc2AjkT7n
如何解密付款方式令牌
要解密令牌,请按以下步骤操作:
- 使用您的私钥和所提供的 ephemeralPublicKey推导出长度为 512 位且采用 ECIES-KEM 的共享密钥。请使用以下参数:
- 椭圆曲线:NIST P-256(在 OpenSSL 中也称为 prime256v1)。
- CheckMode、- OldCofactorMode、- SingleHashMode和- CofactorMode为- 0。
- 编码函数:未压缩的点格式。
- 密钥推导函数:HKDFwithSHA256(如 RFC 5869 中所述),它具有以下参数:
- 请勿提供盐。根据 RFC,这必须等同于 32 个零字节的盐。
- 将生成的密钥拆分为两个长度为 256 位的密钥:symmetricEncryptionKey和macKey。
- 验证 - tag字段是否为- encryptedMessage的有效 MAC。- 要生成预期的 MAC,请使用采用散列函数 SHA256 的 HMAC (RFC 5869) 以及在第 2 步中取得的 - macKey。
- 使用符合以下条件的 AES-256-CTR 模式解密 - encryptedMessage:- 零 IV。
- 未填充。
- 第 2 步中推导出的 symmetricEncryptionKey。
 
密钥管理
商家加密密钥
商家应根据加密架构规范所述的规格生成公钥。
Google 根签名密钥
Google 发布了一组当前有效的根签名公钥,这些公钥可通过一个公共网址获取。该网址返回的 HTTP 缓存标头会指示密钥的有效期限。这些密钥会被缓存起来,直到到期,而到期日由 keyExpiration 字段决定。如果获取的密钥到期,我们建议再次从该公共网址获取密钥以接收当前有效的密钥列表。
ECv2 协议的例外情况:如果您无法在运行时从 Google 获取密钥,请通过我们的生产环境网址获取 keys.json 并将其保存到您的系统中,然后定期手动刷新。正常情况下,Google 会在有效期最长的密钥到期的五年前,为 ECv2 发布新的根签名密钥。如果密钥被破解,Google 会通过自助门户网站中提供的联系信息通知所有商家,以便商家更快请求重新加载 keys.json。为确保您不会错过定期轮替,我们建议选择在 keys.json 内容中保存 Google 密钥的商家,在其每年的密钥轮替工作中,每年刷新一次该文件。
通过公开网址提供的密钥采用以下格式进行映射:
{ "keys": [ { "keyValue": "encoded public key", "protocolVersion": "ECv2" "keyExpiration":"2000000000000" }, { "keyValue": "encoded public key", "protocolVersion": "ECv2" "keyExpiration":"3000000000000" } ] }
keyValue 是采用 X.509 标准中定义的 ASN.1 类型 SubjectPublicKeyInfo 编码的 Base64 版密钥(未换行或填充)。在 Java 中,上述 ASN.1 编码将由 X509EncodedKeySpec 类表示。您可以通过 ECPublicKey.getEncoded() 获取该编码。
测试环境和生产环境的网址如下:
- 测试环境网址:https://payments.developers.google.com/paymentmethodtoken/test/keys.json
- 正式版:https://payments.developers.google.com/paymentmethodtoken/keys.json
密钥轮替
对于直接集成,如果您在服务器上直接解密付款方式令牌,则必须每年轮替一次密钥。
要轮替加密密钥,请完成以下步骤:
- 使用 OpenSSL 生成新密钥对。
- 登录您之前用来在 Google Pay 中注册为开发者的 Google 帐号 之前用于通过 Google Play 管理您的应用。
- 在 Google Pay API 标签页中的直接集成窗格下,点击现有公钥旁边的管理。点击添加其他密钥。
- 选择公开加密密钥文本输入字段,然后添加新生成的采用未压缩点格式及 Base64 编码的公钥。
- 点击保存加密密钥。
- 为确保无缝轮替密钥,请在转换密钥时,同时支持新旧私钥的解密。 - 如果使用 Tink 库来解密令牌,请使用以下 Java 代码来支持多个私钥: - String decryptedMessage = new PaymentMethodTokenRecipient.Builder() .addRecipientPrivateKey(newPrivateKey) .addRecipientPrivateKey(oldPrivateKey); - 请确保将解密代码部署至生产环境,且解密取得成功。 
- 更改代码中使用的公钥。 - 替换 - PaymentMethodTokenizationSpecification- parameters属性中的- publicKey特性的值:- /** * @param publicKey public key retrieved from your server */ private static JSONObject getTokenizationSpecification(String publicKey) { JSONObject tokenizationSpecification = new JSONObject(); tokenizationSpecification.put("type", "DIRECT"); tokenizationSpecification.put( "parameters", new JSONObject() .put("protocolVersion", "ECv2") .put("publicKey", publicKey)); return tokenizationSpecification; } 
- 将第 4 步中的代码部署到生产环境。部署代码后,加密和解密交易将使用新的密钥对。
- 确认旧公钥不再用于加密任何交易。 
- 移除旧的私钥。
- 登录您之前用来在 Google Pay 中注册为开发者的 Google 帐号,然后打开 Google Pay 商家控制台。
- 在 Google Pay API 标签页中的直接集成窗格下,点击现有公钥旁边的管理。点击旧公钥旁边的删除,然后点击保存加密密钥。
Google 将使用 PaymentMethodTokenizationSpecification parameters 对象的 publicKey 属性所指定的密钥,如下例所示:
{
  "protocolVersion": "ECv2",
  "publicKey": "BOdoXP+9Aq473SnGwg3JU1..."
}使用 Tink 库管理加密响应
如要验证签名和解密消息,请使用 Tink 加密库。要与 Tink 集成并执行验证和解密,请完成以下步骤:
- 在您的 - 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>
- 在服务器启动时,预先获取 Google 签名密钥,让密钥保存在内存中。此操作可防止用户在解密过程中因获取密钥而经历网络延迟。 - GooglePaymentsPublicKeysManager.INSTANCE_PRODUCTION.refreshInBackground(); 
- 使用以下代码解密密钥(该代码假定 - paymentMethodToken存储在- encryptedMessage变量中),并根据您的情况替换粗体部分。- 对于环境测试,请将 - INSTANCE_PRODUCTION替换为- INSTANCE_TEST,将 [YOUR MERCHANT ID] 替换为- 。- 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); 
- 将 PrivateKey1 和 PrivateKey2 替换为您自己的密钥。变量可以是采用 Base64 编码的 PKCS8 字符串或者 - ECPrivateKey对象。如需详细了解如何生成采用 Base64 编码的 PKCS8 私钥,请参阅使用 OpenSSL 生成密钥对。
- 如果无法每次解密密钥时都调用 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 签名密钥并将其缓存在内存中
- 签名验证
- 解密