在Google App Engine上用Python验证Android应用内购买消息的签名
安卓开发者网站上的示例应用程序使用Java代码来验证购买的json数据。有没有人成功地用Python来验证购买,特别是在GAE(谷歌应用引擎)上?
以下是安卓应用内购买的相关摘录,来自示例程序。这些内容需要转换成Python代码,使用PyCrypto库,这个库是谷歌完全用Python重写的,也是应用引擎上唯一可用的安全库。希望谷歌对我使用下面的摘录没有问题。
private static final String KEY_FACTORY_ALGORITHM = "RSA";
private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
String base64EncodedPublicKey = "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;
}
3 个回答
2
现在已经是2016年了,下面是如何使用 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
7
我终于搞明白了,从Google Play获取的那个用base64编码的公钥其实是一个X.509格式的subjectPublicKeyInfo DER序列。而且,它使用的签名方案是RSASSA-PKCS1-v1_5,而不是RSASSA-PSS。如果你安装了PyCrypto,其实操作起来非常简单:
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)
15
这是我怎么做的:
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)
这里假设 publicKey
是你从开发者控制台获取的,已经用 base64 编码的 Google Play 商店密钥,且它是一行的格式。
如果你更喜欢使用 m2crypto,那么 validate_purchase()
的写法会变成:
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