Are you interested in attending a Google Pay launchathon in your city? Let us know!

Payment data cryptography for merchants

The Google Pay API will return payment methods (cards consisting of Primary Account Number (PAN) or tokenized cards consisting of device PAN and cryptograms) in a signed and encrypted PaymentMethodToken payload.

Because you will be receiving payment card information directly, make sure your app is PCI DSS compliant and your servers have the required infrastructure for securely handling the user's payment credentials before you proceed.

The PaymentMethodToken payload will contain a field called protocolVersion to allow receivers of a payload to understand what crypto primitives are being used as well as what is the expected format.

You must do the following in order to consume the Google Pay API's PaymentMethodToken payload:

  1. Fetch the Google signing keys
  2. Verify the signature of the payload is valid
  3. Decrypt the contents of the payload
  4. Verify the message is not expired by checking that the current time is less than the messageExpiration field in the decrypted contents
  5. Use the payment method in the decrypted contents and charge it

Payment token structure

The message returned by Google in the PaymentData response is a UTF-8 encoded serialized JSON dictionary.

This guide focuses on payloads with protocolVersion = ECv1.

Parameter Type Description
protocolVersion string Identifies which encryption/signing scheme this message has been created. In this way, the protocol can evolve over time if needed. If it is not set, assume ECv0.
signature string (base64) Verifies the message came from Google. The signature is created using ECDSA.
signedMessage string A serialized JSON string containing the encryptedMessage, ephemeralPublicKey and tag. To simplify the signature verification process, this value is serialized.

Example

The following is an example payment method token response:

{
  "protocolVersion": "ECv1",
  "signature": "TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ",
  "signedMessage": "{\"encryptedMessage\":
                     \"ZW5jcnlwdGVkTWVzc2FnZQ==\",
                     \"ephemeralPublicKey\": \"ZXBoZW1lcmFsUHVibGljS2V5\",
                     \"tag\": \"c2lnbmF0dXJl\"}"
}

Signed message

The signedMessage is a UTF-8 encoded, serialized JSON dictionary with values from the following table.

Parameter Type Description
encryptedMessage string (base64) The encrypted message containing the actual payment information as well as some additional security fields. For more information, see Encrypted message.
ephemeralPublicKey string (base64) The ephemeral public key associated with the private key to encrypt the message in uncompressed point format. For more information, see key format.
tag string (base64) The Message Authentication Code (MAC) of encryptedMessage.

Encrypted message

The decrypted encryptedMessage is a UTF-8 encoded, serialized JSON dictionary. The JSON contains two levels:

  • The outer level contains metadata and fields included for security
  • The inner level is another JSON dictionary representing the actual payment credential

The innermost JSON can be one of two formats:

  • TOKENIZED_CARD
  • CARD

The following table details the parameters:

Parameter Type Description
messageExpiration string (UTC milliseconds since epoch) Length of time the message is valid. Processors should reject any message that has expired.
messageId string A unique ID to identify the message in case it needs to be revoked or located at some later point.
paymentMethod string The type of the payment credential; either TOKENIZED_CARD or CARD.
paymentMethodDetails TOKENIZED_CARD or CARD The payment credential. The format of this JSON is determined by the paymentMethod and is described in the tables below.

If paymentMethodDetails is a TOKENIZED_CARD, then use TOKENIZED_CARD. If paymentMethodDetails is a CARD, then use CARD.

Tokenized card

The table below shows the TOKENIZED_CARD attributes:

Parameter Type Description
dpan string (digits only) The device-specific personal account number.
expirationMonth number The expiration month of the DPAN (1 = January, 2 = February, etc.).
expirationYear number The four-digit expiration year of the DPAN (e.g., 2020).
authMethod string

The constant "3DS".

3dsCryptogram string A 3DSecure cryptogram.
3dsEciIndicator string (optional) An ECI indicator per 3DSecure specification.

Example

The following example shows a fully-encrypted encryptedMessage:

{
  "paymentMethod": "TOKENIZED_CARD",
  "paymentMethodDetails": {
      "dpan": "4444444444444444",
      "expirationMonth": 10,
      "expirationYear": 2020
      "authMethod": "3DS",
      "3dsCryptogram": "AAAAAA...",
      "3dsEciIndicator": "eci indicator"
  },
  
  "messageId": "some-message-id",
  "messageExpiration": "1520836260646"
}

Card

The table below shows the CARD attributes:

Parameter Type Description
pan string The card number to be charged.
expirationMonth number The expiration month of the card (1 = January, 2 = February, etc.).
expirationYear number The four-digit expiration year of the card (e.g., 2020).

Example

The following example is a fully encryptedMessage:

{
  "paymentMethod": "CARD",
  "paymentMethodDetails": {
      "pan": "1111222233334444",
      "expirationMonth": 10,
      "expirationYear": 2020
  },
  
  "messageId": "some-message-id",
  "messageExpiration": "1520836260646"
}

Signature verification

To verify the signature, the following are required:

  1. The algorithm used to create the signature
  2. The byte-string that was used to create the signature
  3. The public key corresponding to the private key used to create the signature
  4. The signature itself

The signature algorithm

Google uses the Elliptic Curve Digital Signature Algorithm (ECDSA) to sign the messages with the following parameters: ECDSA over NIST P-256 with SHA-256 as the hash function, as defined in FIPS 186-4.

The signature

The signature is included in the outermost level of the message. The signature is Base-64 encoded, and it consists of the ECDSA integers r and s in ASN.1 byte format:

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

This is the standard format produced by JCE (Java Cryptography Extension) ECDSA implementations.

Constructing the byte-string

Given a signed JSON message like:

{
  "protocolVersion": "ECv1",
  "signature": "TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ",
  "signedMessage": "{\"encryptedMessage\": \"ZW5jcnlwdGVkTWVzc2FnZQ==\",
  \"ephemeralPublicKey\": \"ZXBoZW1lcmFsUHVibGljS2V5\",
  \"tag\": \"c2lnbmF0dXJl\"}"
}

Construct the signedString with the following formula:

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

The || notation means concatenate. Each component (sender_id, recipient_id, protocolVersion, signedMessage) should be UTF-8 encoded and the byte length of each component is 4 bytes in little endian format.

The sender_id will always be Google and the recipient_id will be of the form: merchant:merchantId (for example, merchant:12345), where merchantId can be found on the developer portal.

Example

For example, assuming that the sender_id is Google and the recipient_id is merchant:12345, the signedString would be:

signedString =
\x06\x00\x00\x00 ||
Google ||
\x0e\x00\x00\x00 ||
merchant:12345 ||
\x04\x00\x00\x00 ||
ECv1 ||
\x77\x00\x00\x00 ||
{"encryptedMessage": "ZW5jcnlwdGVkTWVzc2FnZQ==",
 "ephemeralPublicKey": "ZXBoZW1lcmFsUHVibGljS2V5",
 "tag": "c2lnbmF0dXJl"}

Verifying the signature

With the four pieces of data as input, the standard ECDSA verification algorithm can be used. Google strongly recommends using an existing cryptographic library rather than implementing your own verification code.

Encryption scheme specification

Google uses the Elliptic Curve Integrated Encryption Scheme (ECIES) to secure the payment method token when it is returned in the loadPaymentData response. The encryption scheme uses the following parameters:

  1. Key encapsulation: ECIES-KEM, as defined in ISO 18033-2
    • Elliptic curve: NIST P-256 (also known in openssl as prime256v1)
    • CheckMode, OldCofactorMode, SingleHashMode and CofactorMode are 0
    • Point format is uncompressed
  2. Key derivation function
    • HKDFwithSHA256, as described in RFC 5869, using the following parameters:
      • Salt should not be provided
      • Info should either be Android or Google encoded in ASCII for protocol versions ECv0 and ECv1 respectively
    • 128 bits should be derived for the AES128 key and another 128 bits should be derived for the HMAC_SHA256 key
  3. For the symmetric encryption algorithm, use DEM2 from ISO 18033-2 with the following parameters:
    • Encryption algorithm: AES128 CTR with zero IV and no padding
    • Message Authentication Code (MAC) algorithm: HMAC_SHA256 using a key of 128bits as derived in step 2

Encryption public key format

The encryption public key as well as the ephemeralPublicKey returned in Google payloads will be formatted using the base64 representation of the key in uncompressed point format. It consists of one magic number which specifies the format (0x04) + two 32 byte big integers representing the X and Y coordinates in the Elliptic Curve. This format is described in more detail in "Public Key Cryptography For The Financial Services Industry: The Elliptic Curve Digital Signature Algorithm (ECDSA)", ANSI X9.62, 1998.

Using OpenSSL to generate a private and public key pair

The openssl CLI is the easiest way to generate Elliptic Curve Cryptography (ECC) keys and convert them to the correct format expected by the Google Pay API and the Tink library.

Step 1: Generate a private key

The following example generates an Elliptic Curve private key using the NIST P-256 curve and writes it to key.pem. This type of private key is suitable for use with the Google Pay API.

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

Optional: View the private and public keys

The following example shows how to view the private and public key generated in the previous step:

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

This produces output similar to the following:

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

Step 2: Generate a Base64-encoded public key

The Google Pay API requires the public key to be Base64-encoded in uncompressed point format. To generate the public key in this format from the private key generated in the previous step, use the following command:

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

This produces output similar to the following:

BOdoXP+9Aq473SnGwg3JU1aiNpsd9vH2ognq4PtDtlLGa3Kj8TPf+jaQNPyDSkh3JUhiS0KyrrlWhAgNZKHYF2Y=

Step 3: Generate a Base64-encoded private key in PKCS8 format

To perform decryption, you need to pass your private key to the Tink library. The Tink library accepts either a ECPrivateKey object or a String containing your private key encoded in Base64-encoded PKCS8 format. To convert the private key generated in step one to the Base64-encoded PKCS8 format, use the following command:

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

This produces output similar to the following:

MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgWV4oK8c/MZkCLk4qSCNjW0Zm6H0CBCtSYxkXkC9FBHehRANCAAQPldOnhO2/oXjdJD1dwlFPiNs6fcdoRgFu3/Z0iKj24SjTGyLRGAtYWLGXBZcDdPj3T2bJRHRVhE8Bc2AjkT7n

Decrypting the payment token

To decrypt the token:

  1. Using your private key and the given ephemeralPublicKey, derive a 256 bit long shared key using ECIES-KEM. Use the following parameters as defined in ISO 18033-2:
    • Elliptic curve: NIST P-256 (also known in OpenSSL as prime256v1)
    • CheckMode, OldCofactorMode, SingleHashMode and CofactorMode are 0
    • Encoding function: Uncompressed Point format
    • Key derivation function: HKDFwithSHA256, as described in RFC 5869, using the following parameters:
      • Salt should not be provided (per the RFC, this should be equivalent to a salt of 32 zeroed bytes)
      • Info should be Android or Google encoded in ASCII for protocol versions ECv0 and Ecv1 respectively
  2. Split the generated key into two 128-bit-long keys: symmetricEncryptionKey and macKey.
  3. Verify that the tag field is a valid MAC for encryptedMessage:
    • For generating the expected MAC, use HMAC (RFC 5869) with hash function SHA256 and the macKey obtained above
    • Use a constant time array comparison to avoid timing attacks
  4. Decrypt encryptedMessage using AES128 CTR mode with a zero IV, no padding, and the symmetricEncryptionKey derived above.

Key management

Google signing keys

Google will publish the set of currently valid signing public keys on a public URL. The keys are valid for as long as the cache headers returned by the URL indicate. You should fetch the URL and cache in-memory respecting the cache headers returned.

The format of the keys provided via the static HTTPS URL will be a map of the following format:

{
  "keys": [
    {
      "keyValue": "encoded public key",
      "protocolVersion": "ECv1"
    },
    {
      "keyValue": "encoded public key",
      "protocolVersion": "ECv1"
    }
  ]
}

The keyValue will be a base64 (no wrapping, padded) version of the key encoded in ASN.1 type SubjectPublicKeyInfo defined in the X.509 standard. In Java, the referred ASN.1 encoding is represented by the X509EncodedKeySpec class , and can be obtained with ECPublicKey.getEncoded().

Google keeps its published keys in the following URLs:

Merchant encryption keys

Merchants should generate a public key as per the specifications outlined in Encryption scheme specification.

Merchants are expected to rotate encryption keys once a year and register those keys in our self service portal. Google will allow a 3 month grace period for rotation to happen. If the merchant does not rotate their keys, Google may stop fulfilling requests made to the Google Pay API.

How Google determines which key to use for encryption

The following example passes the base64 string in the PaymentMethodTokenizationParameters as the publicKey parameter in the PaymentDataRequest object:

{
  "publicKey": "BOdoXP+9Aq473SnGwg3JU1..."
}

Using the Tink library to manage the encrypted response

Step 1

In your pom.xml add the Tink library's paymentmethodtoken utility as a dependency:

  <dependencies>
    <!-- other dependencies ... -->
    <dependency>
      <groupId>com.google.crypto.tink</groupId>
      <artifactId>apps-paymentmethodtoken</artifactId>
      <version>1.0.0</version>
    </dependency>
  </dependencies>

Step 2

At server startup, prefetch the Google signing keys. This way the key will be available in memory and your users will not see any network latency of fetching the keys during decryption.

The following is an example of prefetching the Google signing keys:

GooglePaymentsPublicKeysManager.INSTANCE_PRODUCTION
    .refreshInBackground();

Step 3

Decrypt using the following code (assuming paymentMethodToken is stored in the encryptedMessage variable) to handle the following security details:

  • Fetching Google signing keys and caching them in memory
  • Signature verification
  • Decryption
  • Checking the payload is not expired

The following code example handles the security details:

String decryptedMessage =
    new PaymentMethodTokenRecipient.Builder()
        .fetchSenderVerifyingKeysWith(
            GooglePaymentsPublicKeysManager.INSTANCE_PRODUCTION)
        .recipientId("merchant:YourMerchantID")
        // Multiple private keys can be added to support graceful
        // key rotations.
        .addRecipientPrivateKey(
            PrivateKey1)
        .addRecipientPrivateKey(
            PrivateKey2)
        .build()
        .unseal(encryptedMessage);

Replace the PrivateKey1 and PrivateKey2 with your own key(s). The variables can be either a Base64-encoded PKCS8 String or an ECPrivateKey object. For more information on how to produce a Base64-encoded PKCS8 private key, see Using OpenSSL to generate a key pair.

When you are doing environment testing, replace INSTANCE_PRODUCTION with INSTANCE_TEST and YourMerchantID with 12345678901234567890.

Send feedback about...

Google Pay API