Skip to content

Instantly share code, notes, and snippets.

@sevaa
Last active November 7, 2022 19:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sevaa/73422926e0c9aa0fe905af39c55376bf to your computer and use it in GitHub Desktop.
Save sevaa/73422926e0c9aa0fe905af39c55376bf to your computer and use it in GitHub Desktop.
Hand-checking the digital signature of Android in-app purchase against the Google public key
//The modulus of the IAP public key, as Base64.
//The public exponent is 65537, we'll hard-code it.
private static final String GMOD = "APXj+9V6Mrp7DwDVLP2yIDhuMiB30R+NQ9JO14jg42S3TcJFhURQZ2RD21GIbp5S7RLy7YDcxOjH765HM7FWUJgJegvL01lYtzFkXv0XRcnL05m5sgTp58i9fYOJt1QKar2k4FI/a6iv7sjT4qGLOcX3drjDx6WKwZdnu6q5rA94rycHoe+BdELsy1eKBp/iI4KIe/Y3WePYfVgynL4mrJOHutf1tvy6WL04zG61yl3PBlwh6uy1K+RBqEXeiznS0ee4Xq3fe3puq6HgEZKw8PQIihxk8odbg1lneqAk51JZ8vuQi9WEZMdvqWK+p4jT+q7mTYQO18NH1MP5y2/fj8k=";
//d is the value of the IAP result intent's string extra "INAPP_PURCHASE_DATA"
//s is the value of the IAP result intent's string extra "INAPP_DATA_SIGNATURE"
private static boolean PowModThenCheck(String d, String c)
{
try
{
byte []h = MessageDigest.getInstance("SHA1").digest(d.getBytes());
byte [] p = new java.math.BigInteger(1, Util.FromBase64(c)) //Substitute your own FromBase64 function
.modPow(
java.math.BigInteger.valueOf(65537),
new java.math.BigInteger(Util.FromBase64(GMOD))
).toByteArray();
if(p.length != 255 || p[0] != 1)
return false;
int i;
for(i=1;i<219;i++)
if(p[i] != -1)
return false;
final byte []TheOID = new byte[]{0, 0x30, 0x21, 0x30, 9, 6, 5, 0x2B, 0xE, 3, 0x02, 0x1A, 5, 0, 4, 0x14};
for(i=219;i<235;i++)
if(p[i] != TheOID[i-219])
return false;
for(i=235;i<255;i++)
if(p[i] != h[i-235])
return false;
return true;
}
catch(Exception exc)
{
return false;
}
}
@sevaa
Copy link
Author

sevaa commented Nov 7, 2022

Companion gist to this blog post. Implements a hand-coded digital signature check on a Google Play in-app purchase. First, the signature is RSA-decoded with a public key (the key that Google publishes), then the result is matched against a well known structure of a SHA1/RSA digital signature, which includes the SHA1 hash of the plaintext.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment