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 を含む signedKey と、signatures が含まれます。中間署名鍵の署名検証プロセスが簡単になるようにシリアル化されています。 |
signedMessage |
文字列 | encryptedMessage 、ephemeralPublicKey 、tag を含む HTML セーフ文字列としてシリアル化された JSON オブジェクト。署名検証プロセスが簡単になるようにシリアル化されています。 |
例
支払い方法トークンの 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 |
文字列 | 中間鍵の有効期限が切れる日時(エポックからの UTC ミリ秒)。 インテグレータは期限切れの鍵を拒否します。 |
署名付きメッセージ
signedMessage
は UTF-8 エンコード形式のシリアル化された JSON オブジェクトで、次の値が含まれます。
名前 | 型 | 説明 |
---|---|---|
encryptedMessage |
文字列 | 支払い情報と追加のセキュリティ フィールドを含む Base64 でエンコードされた暗号化メッセージ。 |
ephemeralPublicKey |
文字列 | 非圧縮ポイント形式でメッセージを暗号化するために秘密鍵に関連付けられた、Base64 でエンコードされた一時的な公開鍵。詳しくは、暗号化公開鍵の形式をご覧ください。 |
tag |
文字列 | encryptedMessage の Base64 でエンコードされた MAC。 |
暗号化メッセージ
復号した encryptedMessage
は、UTF-8 でエンコードしてシリアル化された JSON オブジェクトです。この JSON には 2 つのレベルがあります。外側のレベルにはセキュリティのためのメタデータとフィールドが含まれ、内側のレベルは実際の支払い認証情報を表すもう 1 つの JSON オブジェクトです。
encryptedMessage
について詳しくは、次の表と JSON オブジェクトの例をご覧ください。
名前 | 型 | 説明 |
---|---|---|
gatewayMerchantId |
文字列 | 一意の販売者 ID。決済代行業者はこの ID を使用して、これがリクエストを発行した販売者宛てのメッセージであることを確認します。決済代行業者によって作成され、Android またはウェブで |
messageExpiration |
文字列 | メッセージの有効期限が切れる日時(エポックからの UTC ミリ秒)。 インテグレータは期限切れのメッセージを拒否する必要があります。 |
messageId |
文字列 | 後でメッセージの取り消しや検索を行う場合に使用する、メッセージを識別するための一意の ID。 |
paymentMethod |
文字列 | 支払い認証情報のタイプ。現時点でサポートされているのは CARD のみです。
|
paymentMethodDetails |
オブジェクト | 支払い認証情報。このオブジェクトの形式は paymentMethod によって決まります。これについては、次の表で説明します。 |
カード
CARD
支払い方法の支払い認証情報は、次のプロパティから構成されます。
名前 | 型 | 説明 |
---|---|---|
pan |
文字列 | 請求先のカード会員番号。この文字列には数字だけが含まれます。 |
expirationMonth |
数字 | カードの有効期限の月(1 = 1 月、2 = 2 月など)。 |
expirationYear |
数字 | カードの有効期限の 4 桁の年(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
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)を利用してメッセージに署名します。使用するパラメータは、ECDSA の NIST P-256 とハッシュ関数の SHA-256(FIPS 186-4 で定義)です。
署名
署名はメッセージの最も外側のレベルに含まれ、ASN.1 バイト形式を使用して Base64 でエンコードされています。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 署名鍵を使用して各署名を検証する必要があります。少なくとも 1 つの署名の検証が成功した場合は、検証全体が完了したとみなします。後で intermediateSigningKey.signedKey.keyValue
を使用して signedStringForMessageSignature
を検証します。署名の検証には、独自のコードを実装するよりも既存の暗号ライブラリを使用することを強くおすすめします。
メッセージの署名用のバイト文字列を構築する方法
サンプル支払い方法トークン内の署名を検証するには、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 and Wallet Console に表示される値と一致します。
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 では、楕円曲線統合暗号化スキーム(ECIES)を使用して、Google Pay API レスポンスで返される支払い方法トークンを保護します。この暗号化スキームでは、次のパラメータを使用します。
パラメータ | 定義 |
---|---|
鍵カプセル化方式 | ISO 18033-2 で定義されている ECIES-KEM。
|
鍵導出関数 | HMAC ベースの SHA-256(
|
対称暗号化アルゴリズム |
ISO 18033-2 で定義されている DEM2 暗号化アルゴリズム: AES-256-CTR、ゼロ IV、パディングなし。 |
MAC アルゴリズム | 256 ビットの鍵を使用して鍵導出関数から導出された HMAC_SHA256 。 |
暗号化公開鍵の形式
Google のペイロードで返される暗号化公開鍵と ephemeralPublicKey
は、非圧縮ポイント形式の鍵を Base64 で表したものです。これは次の 2 つの要素で構成されています。
- 形式(0x04)を指定する 1 つのマジック ナンバー。
- 楕円曲線の X 座標と Y 座標を表す 2 つの大きな 32 バイト整数。
この形式について詳しくは、「Public Key Cryptography For The Financial Services Industry: The Elliptic Curve Digital Signature Algorithm (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 でエンコードされた公開鍵を生成する
上記の省略可能なステップの例で生成された秘密鍵と公開鍵は 16 進数でエンコードされています。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: Base64 でエンコードされた PKCS #8 形式の秘密鍵を生成する
Tink ライブラリでは、秘密鍵は Base64 でエンコードされた PKCS #8 形式であるとみなされます。最初のステップで生成した秘密鍵からこの形式の秘密鍵を生成するには、次のコマンドを使用します。
openssl pkcs8 -topk8 -inform PEM -outform DER -in key.pem -nocrypt | base64 | paste -sd "\0" -
このコマンドを実行すると、次のような出力が生成されます。
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgWV4oK8c/MZkCLk4qSCNjW0Zm6H0CBCtSYxkXkC9FBHehRANCAAQPldOnhO2/oXjdJD1dwlFPiNs6fcdoRgFu3/Z0iKj24SjTGyLRGAtYWLGXBZcDdPj3T2bJRHRVhE8Bc2AjkT7n
支払い方法トークンを復号する方法
トークンを復号する手順は次のとおりです。
- 秘密鍵と提供された
ephemeralPublicKey
を使用して、ECIES-KEM を使用する 512 ビット長の共有鍵を導出します。次のパラメータを使用します。 - 楕円曲線: NIST P-256(OpenSSL では prime256v1 とも呼ばれます)。
CheckMode
、OldCofactorMode
、SingleHashMode
、CofactorMode
は0
です。- エンコード関数: 非圧縮ポイント形式。
- 鍵導出関数: RFC 5869 で規定された HKDFwithSHA256。次のパラメータを使用します。
- ソルトは指定しないでください。RFC により、これは 32 個のゼロ化されたバイトからなるソルトと同等でなければなりません。
- 生成された鍵を 2 つの 256 ビット長の鍵(
symmetricEncryptionKey
とmacKey
)に分割します。 tag
フィールドがencryptedMessage
の有効な MAC であることを確認します。適切な MAC を生成するには、HMAC(RFC 5869)とハッシュ関数 SHA256、およびステップ 2 で取得した
macKey
を使用します。AES-256-CTR モードを使用して次のとおりに
encryptedMessage
を復号します。- ゼロ IV。
- パディングなし。
- ステップ 2 で導出した
symmetricEncryptionKey
。
鍵の管理
販売者の暗号鍵
販売者は、暗号化スキームの仕様に示す仕様に従って公開鍵を生成します。
Google ルート署名鍵
Google の現在有効なルート署名用公開鍵のセットは公開 URL からフェッチできます。これらの鍵は、その URL から返される HTTP キャッシュ ヘッダーに示されている限り有効で、有効期限(keyExpiration フィールドによって決定されます)が切れるまでキャッシュされます。有効期限が切れたときは、公開 URL から有効な鍵の最新リストを再度フェッチすることをおすすめします。
ECv2 プロトコルの例外: 実行時に Google から鍵をフェッチできない場合は、Google の本番環境 URL から keys.json
をフェッチしてシステムに保存し、それを定期的に手動で更新してください。通常の状況下では、ECv2 の新しいルート署名鍵は、有効期限が最も長い鍵が期限切れになる 5 年前に発行されます。鍵が危険にさらされた場合は、keys.json
を速やかに再読み込みしていただくため、セルフサービス ポータルで指定された連絡先情報を使って Google からすべての販売者にその旨が通知されます。通常のローテーションに確実に追随するため、keys.json
内の Google の鍵を保存することを選んだ販売者は、年 1 回義務付けられている鍵のローテーションの一環として毎年更新することをおすすめします。
公開 URL から提供される鍵は、次の形式でマップされます。
{ "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()
を使用して取得できます。
テスト環境と本番環境の URL が次のリンクによって提供されています。
- テスト環境:
https://payments.developers.google.com/paymentmethodtoken/test/keys.json
- 本番環境:
https://payments.developers.google.com/paymentmethodtoken/keys.json
鍵のローテーション
直接統合を使用してサーバー上で直接支払い方法トークンを復号する場合は、毎年鍵をローテーションする必要があります。
暗号鍵をローテーションするには、次の手順を行います。
- OpenSSL を使用して、新しい鍵ペアを生成します。
- Google アカウントでログインして Google Pay and Wallet Console を開きます。ログインには、以前にデベロッパーとして Google Pay に登録したときに使用したアカウントを使用します。
- [Google Pay API] タブの [Direct integration] パネルで、既存の公開鍵の横にある [管理] をクリックします。[Add another key] をクリックします。
- [Public encryption key] テキスト入力フィールドを選択し、新しく生成した非圧縮ポイント形式の公開鍵を表す Base64 文字列を追加します。
- [Save encryption keys] をクリックします。
鍵のローテーションをシームレスに行えるように、鍵の移行中は新旧どちらの秘密鍵でも復号できるようにします。
Tink ライブラリを使用してトークンを復号する場合は、次の Java コードを使用して複数の秘密鍵をサポートします。
String decryptedMessage = new PaymentMethodTokenRecipient.Builder() .addRecipientPrivateKey(newPrivateKey) .addRecipientPrivateKey(oldPrivateKey);
必ずこの復号コードを本番環境に導入し、復号が正常に行われるかどうかを確認してください。
コードで使用される公開鍵を変更します。
PaymentMethodTokenizationSpecification
parameters
プロパティのpublicKey
属性の値を置き換えます。const tokenizationSpecification = { "type": "DIRECT", "parameters": { "protocolVersion": "ECv2", "publicKey": "BOdoXP1aiNp.....kh3JUhiSZKHYF2Y=" } }
- ステップ 4 のコードを本番環境に導入します。コードの導入後は、暗号化および復号トランザクションで新しい鍵ペアが使用されます。
古い公開鍵がトランザクションの暗号化に使用されていないことを確認します。
- 古い秘密鍵を削除します。
- 以前に Google Pay でデベロッパーとして登録したときに使用した Google アカウントでログインして、Google Pay and Wallet Console を開きます。
- [Google Pay API] タブの [Direct integration] パネルで、既存の公開鍵の横にある [管理] をクリックします。古い公開鍵の横にある [Delete] をクリックし、[Save encryption keys] をクリックします。
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] を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);
PrivateKey1
を適切な秘密鍵の値に置き換えます。この値は、Google に登録済みの公開鍵の値と関連付けられた値で、鍵を準備して Google に登録するで得られるものです。Google に登録する鍵のローテーションが必要な場合は、後で他の複数の秘密鍵の値を追加できます。これらの変数には、Base64 でエンコードされた PKCS8 文字列とECPrivateKey
オブジェクトのどちらかを指定できます。Base64 でエンコードされた PKCS8 秘密鍵を生成する方法について詳しくは、鍵を準備して Google に登録するをご覧ください。鍵を復号するたびに 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 日まで有効です。鍵が危険にさらされた場合は、
keys.json
を速やかに再読み込みしていただくため、セルフサービス ポータルで指定された連絡先情報を使って Google からすべての販売者にその旨が通知されます。次のセキュリティに関する部分は上記のコード スニペットによって処理されるため、デベロッパーはペイロードをどのように消費するかに専念できます。
- Google 署名鍵のフェッチとメモリへのキャッシュ
- 署名の検証
- 復号