Skip to content

Instantly share code, notes, and snippets.

@pc-coholic
Created March 18, 2019 21:32
Show Gist options
  • Save pc-coholic/125340e91b237d4eed9e94f50915048c to your computer and use it in GitHub Desktop.
Save pc-coholic/125340e91b237d4eed9e94f50915048c to your computer and use it in GitHub Desktop.
Google Pay // Google Pay Passes Server Callbacks // Payment data cryptography for merchants
{
"keys":[
{
"keyValue":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEs/J7ghdwu4/b6NvqzQebS+Z80FEP8ZlGdAdWDUjfPeBKaV+YOWW0l6U/4IGXnfY9NrsND1fwxAqRq0unR/LCIg==",
"protocolVersion":"ECv2SigningOnly",
"keyExpiration":"1553109880808"
}
]
}
import requests
import json
import base64
import struct
from datetime import datetime
from Crypto.PublicKey import ECC
from Crypto.Signature import DSS
from Crypto.Hash import SHA256
# Reference:
# https://developers.google.com/pay/api/web/guides/resources/payment-data-cryptography
# https://developers.google.com/pay/api/android/guides/resources/payment-data-cryptography
# Notes:
# - The sender_Id is depends on your environment:
# * Tokenization as described in the two links above use "Google"
# * Google Pay Passes Server Callbacks use "GooglePayPasses"
# - The recipient_id also depends on your environment:
# * Tokenization as described in the two links above use "merchant:<MerchantId>" (without <>)
# * Google Pay Passes Server Callbacks uses the issuerID of the class/object
# - This sample loads the public keys from a local file in order to maintain functionality with the sample
# * You can change this by un-commenting lines 27/28 and commenting lines 29/30.
# - You could probably also just use Google's Tink-library to do all of this.
# - This sample does not validate the key expiration time.
# - The intermediateSigningKey has one or more signatures. According to Google's documentation, verification is deemed successful, as soon as one of those signatures are validated.
def pack(string):
return struct.pack('<Q', len(string))[:4] + bytes(string, 'utf-8')
#r = requests.get("https://pay.google.com/gp/m/issuer/keys")
#pkjson = json.loads(r.content)
with open('pk.json') as infile:
pkjson = json.load(infile)
for key in pkjson['keys']:
pk = ECC.import_key(base64.b64decode(key['keyValue']))
print("Public Key / keyValue:", pk)
print("Public Key / protocolVersion:", key['protocolVersion'])
print("Public Key / keyExpiration:", datetime.fromtimestamp(int(key['keyExpiration'])/1000).isoformat())
print("---------------------------------------------------------------------------------------------------------")
with open('webhook.json') as infile:
webhookjson = json.load(infile)
print("signature:", webhookjson['signature'])
intermediateSigningKey = json.loads(webhookjson['intermediateSigningKey']['signedKey'])
sk = ECC.import_key(base64.b64decode(intermediateSigningKey['keyValue']))
print("intermediateSigningKey / signedKey:", webhookjson['intermediateSigningKey']['signedKey'])
print("intermediateSigningKey / signedKey / keyValue:", sk)
print("intermediateSigningKey / signedKey / keyExpiration:", datetime.fromtimestamp(int(intermediateSigningKey['keyExpiration'])/1000).isoformat())
for signature in webhookjson['intermediateSigningKey']['signatures']:
isksig = signature
print("intermediateSigningKey / signatures:", signature)
print("protocolVersion:", webhookjson['protocolVersion'])
print("signedMessage", webhookjson['signedMessage'])
print("---------------------------------------------------------------------------------------------------------")
# || == concat
# signedStringForIntermediateSigningKeySignature =
# length_of_sender_id || sender_id || length_of_protocol_version || protocol_version || length_of_signed_key || signed_key
# 15 --> \x0F\x00\x00\x00 || GooglePayPasses || 15 --> \x0F\x00\x00\x00 || ECv2SigningOnly || 181 --> \xb5\x00\x00\x00 || {"keyValue":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEU2bSn9wnfmImXa/0cljpmltRnaITkqIRCPW7eJmRdgmolpQzG5vWT2DTSylp70eoI+t+qCNTc9ZF61pO0sYwiA\u003d\u003d","keyExpiration":"1553622047830"}
# signedStringForMessageSignature =
# length_of_sender_id || sender_id || length_of_recipient_id || recipient_id || length_of_protocolVersion || protocolVersion || length_of_signedMessage || signedMessage
# 15 --> \x0F\x00\x00\x00 || GooglePayPasses || \x13\x00\x00\x00 || 3294101106405327289 || 15 --> \x0F\x00\x00\x00 || ECv2SigningOnly || 345 --> \xFF\x90\x00\x00 || {"classId":"3294101106405327289.pretix-b1b784d828bf41f2804396f58d87937f-bigevents-lowercase2020-2","expTimeMillis":1552938749769,"count":1,"eventType":"save","nonce":"8050b06d-2255-400c-81fd-92b06429dd0f","objectId":"3294101106405327289.pretix-b1b784d828bf41f2804396f58d87937f-bigevents-lowercase2020-2-M3NMT-3-e86a9276cb8e467fabc57436eda6a41e"}
# Verify Intermediate Signed Key with Public Key
verifier = DSS.new(pk, 'fips-186-3', 'der')
try:
verifier.verify(
SHA256.new(
pack('GooglePayPasses') + pack(key['protocolVersion']) + pack(webhookjson['intermediateSigningKey']['signedKey'])
),
base64.b64decode(isksig)
)
print("Valid Intermediate Signed Key")
except ValueError:
print("Invalid Intermediate Signed Key")
# Verify signedMessage with intermediateSigningKey / signedKey
verifier = DSS.new(sk, 'fips-186-3', 'der')
try:
verifier.verify(
SHA256.new(
pack('GooglePayPasses') + pack('3294101106405327289') + pack(webhookjson['protocolVersion']) + pack(webhookjson['signedMessage'])
),
base64.b64decode(webhookjson['signature'])
)
print("Valid signedMessage")
except ValueError:
print("Invalid signedMessage")
{
"signature":"MEQCIGFbsdqpsWisGoEDYqk6qZPUod07YvGjMLjG0mJ1oRMLAiA1Pusf8FSpmU+l0rX6PGUcHpT3i+7DKlhL4pXjtEJm2g==",
"intermediateSigningKey":{
"signedKey":"{\"keyValue\":\"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEU2bSn9wnfmImXa/0cljpmltRnaITkqIRCPW7eJmRdgmolpQzG5vWT2DTSylp70eoI+t+qCNTc9ZF61pO0sYwiA\\u003d\\u003d\",\"keyExpiration\":\"1553622047830\"}",
"signatures":[
"MEUCIQD7bsQ87xxWSzpq5k0F/aHcP5pXYdCsfIL1TJNz8nSPNAIgT/SjZA7CAuyI6B2mB0ViwHB+1XjYiu4h1Ujpfi9/7WU="
]
},
"protocolVersion":"ECv2SigningOnly",
"signedMessage":"{\"classId\":\"3294101106405327289.pretix-b1b784d828bf41f2804396f58d87937f-bigevents-lowercase2020-2\",\"expTimeMillis\":1552938749769,\"count\":1,\"eventType\":\"save\",\"nonce\":\"8050b06d-2255-400c-81fd-92b06429dd0f\",\"objectId\":\"3294101106405327289.pretix-b1b784d828bf41f2804396f58d87937f-bigevents-lowercase2020-2-M3NMT-3-e86a9276cb8e467fabc57436eda6a41e\"}"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment