Verifying signature on android in-app purchase message in Python on Google App Engine
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. In particular in GAE?
The following are the relevant excerpts from the android in-app billing example program. This is what would need to be converted to python using PyCrypto which was re-written to be completely python by Google and is the only Security lib available on app engine. Hopefully Google is cool with me using the excerpts below.
private static final String KEY_FACTORY_ALGORITHM = "RSA";
private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
String base64EncodedPubl开发者_高级运维icKey = "your public key here";
PublicKey key = Security.generatePublicKey(base64EncodedPublicKey);
verified = Security.verify(key, signedData, signature);
public static PublicKey generatePublicKey(String encodedPublicKey) {
try {
byte[] decodedKey = Base64.decode(encodedPublicKey);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
} catch ...
}
}
public static boolean verify(PublicKey publicKey, String signedData, String signature) {
if (Consts.DEBUG) {
Log.i(TAG, "signature: " + signature);
}
Signature sig;
try {
sig = Signature.getInstance(SIGNATURE_ALGORITHM);
sig.initVerify(publicKey);
sig.update(signedData.getBytes());
if (!sig.verify(Base64.decode(signature))) {
Log.e(TAG, "Signature verification failed.");
return false;
}
return true;
} catch ...
}
return false;
}
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
精彩评论