Created
March 18, 2019 21:32
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"keys":[ | |
{ | |
"keyValue":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEs/J7ghdwu4/b6NvqzQebS+Z80FEP8ZlGdAdWDUjfPeBKaV+YOWW0l6U/4IGXnfY9NrsND1fwxAqRq0unR/LCIg==", | |
"protocolVersion":"ECv2SigningOnly", | |
"keyExpiration":"1553109880808" | |
} | |
] | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"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