Skip to content

moovfinancial/google-pay-decryptor

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

33 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

IMPORTANT: The file library has not been through a formal code review!!!

Please peer review the code before using.

Google Pay Decryptor

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

Getting Started

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.

Types

There are two main types in library:

  1. Token - encrypted payload from GooglePay
  2. Decrypted - decrypted payload

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
  },
}

Setup

Load the following information:

  1. Google Root Signing Keys
  2. Private Key
  3. Recipient ID
    • If processor, recipient id is the gateway id.
    • If merchant, recipient id is the merchant id.

Usage

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.

Key Rotation

Google requires Processors to support Annual Key Rotation.

  • This repo allows multiple keys to be added to the repo.

Key Generation

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

Testing Key Rotation with Google

Google will provide you with payloads, both good and bad.

  • Use the tests in ./registration to 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_GOOGLE environment variable to true when attempting to run the test
    • Example: TEST_NEW_KEYS_AND_PAYLOADS_FROM_GOOGLE=true go test -v ./registration

Supporting more than one key at runtime

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

Usage

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

Original Authors

License

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

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Go 98.0%
  • Shell 1.3%
  • Makefile 0.7%