The sample application on the android developers site validates the purchase json using java code. Has anybody had any luck working out how to validate the purchase in python.
Here's how i did it:
from Crypto.Hash import SHA
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from base64 import b64decode
def chunks(s, n):
for start in range(0, len(s), n):
yield s[start:start+n]
def pem_format(key):
return '\n'.join([
'-----BEGIN PUBLIC KEY-----',
'\n'.join(chunks(key, 64)),
'-----END PUBLIC KEY-----'
])
def validate_purchase(publicKey, signedData, signature):
key = RSA.importKey(pem_format(publicKey))
verifier = PKCS1_v1_5.new(key)
data = SHA.new(signedData)
sig = b64decode(signature)
return verifier.verify(data, sig)
This assumes that publicKey
is your base64 encoded Google Play Store key on one line as you get it from the Developer Console.
For people who rather use m2crypto, validate_purchase()
would change to:
from M2Crypto import RSA, BIO, EVP
from base64 import b64decode
# pem_format() as above
def validate_purchase(publicKey, signedData, signature):
bio = BIO.MemoryBuffer(pem_format(publicKey))
rsa = RSA.load_pub_key_bio(bio)
key = EVP.PKey()
key.assign_rsa(rsa)
key.verify_init()
key.verify_update(signedData)
return key.verify_final(b64decode(signature)) == 1
I finally figured out that your base64 encoded public key from Google Play is an X.509 subjectPublicKeyInfo DER SEQUENCE, and that the signature scheme is RSASSA-PKCS1-v1_5 and not RSASSA-PSS. If you have PyCrypto installed, it's actually quite easy:
import base64
from Crypto.Hash import SHA
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
# Your base64 encoded public key from Google Play.
_PUBLIC_KEY_BASE64 = "YOUR_BASE64_PUBLIC_KEY_HERE"
# Key from Google Play is a X.509 subjectPublicKeyInfo DER SEQUENCE.
_PUBLIC_KEY = RSA.importKey(base64.standard_b64decode(_PUBLIC_KEY_BASE64))
def verify(signed_data, signature_base64):
"""Returns whether the given data was signed with the private key."""
h = SHA.new()
h.update(signed_data)
# Scheme is RSASSA-PKCS1-v1_5.
verifier = PKCS1_v1_5.new(_PUBLIC_KEY)
# The signature is base64 encoded.
signature = base64.standard_b64decode(signature_base64)
return verifier.verify(h, signature)
Now that we're in 2016, here's how to do it with cryptography
:
import base64
import binascii
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
class RSAwithSHA1:
def __init__(self, public_key):
# the public key google gives you is in DER encoding
# let cryptography handle it for you
self.public_key = serialization.load_der_public_key(
base64.b64decode(public_key), backend=default_backend()
)
def verify(self, data, signature):
"""
:param str data: purchase data
:param str signature: data signature
:return: True signature verification passes or False otherwise
"""
# note the signature is base64 encoded
signature = base64.b64decode(signature.encode())
# as per https://developer.android.com/google/play/billing/billing_reference.html
# the signature uses "the RSASSA-PKCS1-v1_5 scheme"
verifier = self.public_key.verifier(
signature, padding.PKCS1v15(), hashes.SHA1(),
)
verifier.update(data.encode())
try:
verifier.verify()
except InvalidSignature:
return False
else:
return True