针对商家的付款数据加密

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 字符串 encryptedMessage 的 MAC,采用 Base64 编码。

加密的消息

解密后的 encryptedMessage 是一个采用 UTF-8 编码的序列化 JSON 对象。该 JSON 共有两层。外层包含元数据和出于安全目的而加入的字段,而内层则是另一个代表实际付款凭据的 JSON 对象。

如需详细了解 encryptedMessage,请参阅以下表格和 JSON 对象示例:

名称 类型 说明
gatewayMerchantId 字符串

商家的唯一标识符,处理方将解读并使用该标识符来验证消息是否针对提出请求的商家。该标识符由处理方创建,并由商家在 AndroidWeb 上利用 PaymentMethodTokenizationSpecification 传递给 Google。

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 authMethodCARD 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 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_versionECv2

如果 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_idmerchant:merchantIdmerchantId 与具有正式版访问权限的商家的 Google Pay 和钱包控制台中的值一致。

如果 sender_idGoogle,且 recipient_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)。

  • 请勿提供盐 (Salt)。
  • 对于协议版本 ECv2,信息必须由 Google 采用 ASCII 码进行编码。
  • 必须为 AES256 密钥推导出 256 位并为 HMAC_SHA256 密钥推导出另外 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

如何解密付款方式令牌

要解密令牌,请按以下步骤操作:

  1. 使用您的私钥和所提供的 ephemeralPublicKey 推导出长度为 512 位且采用 ECIES-KEM 的共享密钥。请使用以下参数:
    • 椭圆曲线: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

    • 零 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() 获取该编码。

测试环境和生产环境的网址如下:

密钥轮替

对于直接集成,如果您在服务器上直接解密付款方式令牌,则必须每年轮替一次密钥。

要轮替加密密钥,请完成以下步骤:

  1. 使用 OpenSSL 生成新密钥对
  2. 登录您之前用来在 Google Pay 中注册为开发者的 Google 帐号,然后打开 Google Pay 和钱包控制台
  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("gateway:processorname")
        // 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. 请将 PrivateKey1 替换为与您在准备密钥并向 Google 注册步骤中向 Google 注册的公钥值相关联的相应私钥值。稍后,当您需要通过 Google 轮替密钥时,您可以添加多个其他私钥值。变量可以是采用 base64 编码的 PKCS8 字符串或者 ECPrivateKey 对象。如需详细了解如何生成采用 base64 编码的 PKCS8 私钥,请参阅准备密钥并向 Google 注册

  5. 如果无法在每次解密密钥时都调用 Google 服务器,请使用以下代码进行解密,并根据您的情况替换粗体部分。

    String decryptedMessage =
        new PaymentMethodTokenRecipient.Builder()
        .addSenderVerifyingKey("ECv2 key fetched from test or production url")
        .recipientId("gateway:processorname")
        // 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 签名密钥并将其缓存在内存中
    • 签名验证
    • 解密