IMPORTANT: The file library has not been through a formal code review!!!
Please peer review the code before using.
The Google Pay Decryptor library provides functions to verify Google Pay Token Signatures, decrypt Google Pay encrypted messages and format the data according to the Google Pay specifications.
Google Pay Documentation Resources
This library uses the Tink library, which is the recommended decryption library from Google.
- NOTE: The Go version of the Tink library is not full featured like the Java version
- In the Google Pay documentation, you will find functions available in Java that are NOT available in Go.
There are two main types in library:
- Token - encrypted payload from GooglePay
var input types.Token- Payment Method Token Structure
- Decrypted - decrypted payload
var output types.Decrypted- Encrypted Message Structure
- Decrypted Card Payment Method Structure
Example of a Token (encrypted payload from GooglePay):
{
"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\"}"
}
Example of a Decrypted payload:
{
"messageId": "some-message-id",
"messageExpiration": "1759309000000",
"paymentMethod": "CARD",
"paymentMethodDetails": {
"authMethod": "PAN_ONLY",
"pan": "1111222233334444",
"expirationMonth": 10,
"expirationYear": 2025
},
}
Load the following information:
- Google Root Signing Keys
- Private Key
- Recipient ID
- If processor, recipient id is the gateway id.
- If merchant, recipient id is the merchant id.
The following shows a basic example to verify and decrypt a Google Pay Token payload:
var input types.Token
var output types.Decrypted
// Read the Payload
const payloadJSON = `ADD PAYLOAD FROM GOOGLE HERE`
err := json.Unmarshal([]byte(testPayloadJSON), &input)
if err != nil {
t.Errorf("failed to unmarshal payload: %v", err)
}
// Load Private Key
privateKeyBytes, err := os.ReadFile("pk8.pem")
if err != nil {
fmt.Printf("Error reading test private key: %v\n", err)
return
}
// Create a new GooglePayDecryptor with the private key and auto-fetch latest Google Pay Root Signing Keys
decryptor, err := decrypt.NewWithRootKeysFromGoogle(decrypt.EnvironmentTest, "gateway:moov", string(privateKeyBytes))
if err != nil {
t.Errorf("failed to create decryptor: %v", err)
}
// Decrypt the test payload
output, err = decryptor.DecryptWithMerchantId(input, "googletest") // Replace googletest with the Gateway Merchant ID Assigned to the Merchant
if err != nil {
t.Errorf("failed to decrypt: %v", err)
}
// Pretty print the decrypted token
prettyOutput, err := json.MarshalIndent(output, "", " ")
if err != nil {
t.Fatalf("error formatting output: %v", err)
}
fmt.Printf("Decrypted Token:\n%s\n", string(prettyOutput))
If the payload is invalid, the key is bad or the message is expired then decryption will fail.
Google requires Processors to support Annual Key Rotation.
- This repo allows multiple keys to be added to the repo.
The ./key_generation/generate_keys.sh script will generate keys in the format that Google requires for key registration
- Once the new public key is generated, it needs to be sent to Google to be registered
Google will provide you with payloads, both good and bad.
- Use the tests in
./registrationto test the payloads - Make sure you are using the new keys you generated
- Also, you will need to set the
TEST_NEW_KEYS_AND_PAYLOADS_FROM_GOOGLEenvironment variable totruewhen attempting to run the test- Example:
TEST_NEW_KEYS_AND_PAYLOADS_FROM_GOOGLE=true go test -v ./registration
- Example:
During Key Rotation, the Processor must support both the old key and new key in order to prevent decryption issues
- Both private keys should be used to try and decrypt the payload until it's confirmed that the new public key has been loaded
- Once Google has confirmed that the new public key has been loaded, the old private key can be removed
After creating a new GooglePayDecryptor add the second key:
...
// Create a new GooglePayDecryptor with the private key
decryptor, err := decrypt.NewWithRootKeysFromGoogle(decrypt.EnvironmentTest, "gateway:moov", string(privateKeyBytes))
if err != nil {
t.Errorf("failed to create decryptor: %v", err)
}
// Load a 2nd Private Key for Key Rotation
decryptor.AddPrivateKey(string(privateKeyBytesBad), "old_key")
...
NOTE: Decryption will attempted with the first key loaded
- The 2nd key will only be used to attempt decryption if the first key failed to decrypt the payload
- Zhuman Rakhat - Initial work - Google Pay Decryptor
- Naidenko Dmytro - Forked work - Google Pay Decryptor
This project is licensed under the MIT License - see the LICENSE file for details.
- All copyright notices have been persisted in observance with the MIT license requirements