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
中,添加 Tinkpaymentmethodtoken
应用作为依赖项:<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 签名密钥并将其缓存在内存中
- 签名验证
- 解密