Skip to content

Instantly share code, notes, and snippets.

@mkorszunsands
Last active January 29, 2023 09:51
Show Gist options
  • Save mkorszunsands/3cbc0016ac7d0bca318228f96ffd0ab3 to your computer and use it in GitHub Desktop.
Save mkorszunsands/3cbc0016ac7d0bca318228f96ffd0ab3 to your computer and use it in GitHub Desktop.
Verify Google Play in-app purchase signatures
import sun.misc.BASE64Decoder;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
/**
* Signature verifier
*/
public class SignatureVerifier {
/**
* @param key Base64 encoded public key
* @param signature Base64 encoded signature
* @param data Signed data
*/
public boolean verify(String key, String signature, String data) throws Exception {
return verify("SHA1withRSA", key, signature, data);
}
/**
* @param algorithm Signature hashing algorithm
* @param key Base64 encoded public key
* @param signature Base64 encoded signature
* @param data Signed data
*/
public boolean verify(String algorithm, String key, String signature, String data) throws Exception {
Signature sig = Signature.getInstance(algorithm);
sig.initVerify(getKey(key));
sig.update(data.getBytes());
BASE64Decoder decoder = new BASE64Decoder();
byte[] sigBytes = decoder.decodeBuffer(signature);
return sig.verify(sigBytes);
}
private static PublicKey getKey(String key) throws NoSuchAlgorithmException, InvalidKeySpecException, IOException {
BASE64Decoder decoder = new BASE64Decoder();
X509EncodedKeySpec X509publicKey = new X509EncodedKeySpec(decoder.decodeBuffer(key));
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePublic(X509publicKey);
}
// Example - verify Google Play in-app purchase signature
public static void main(String... args) throws Exception {
String key = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgVv152mMs3kn+uvEfU1gbhf0/CcAMhInr3wvS+E4SEaDS/SLtvzeC/XQdoHRLUnOW5Q40074eL7sL0WvKG4fEpnTD4aP85HwkoYNPzW7o2h7TwpxH46pmsCZ4p3rPRNBaNObe/6aKZ9IxrTsy/pUhN5X3sD3wAbXLsCYqbNrJY1VHaoqCWFucvn7VW6GXLLojWFZnprMlTtsdHMwGeHrkJfUOOE1ArQJaVbSCzDjpnuOGh3kZGRCklLXtKk3CTzzrNFT9ONR0BftrB3VviId28HcakZ7bv4DkiRGSTz6+SmYS9UMA6xUoBW3jj7+fNWUMeZ3ds1bFiMtApr7ykw73wIDAQAB";
String signature = "DefUPOQ2/c3LwySfk+fdczZefijWQ+eZKzOOM3bIH2+Dz+XgNUtoUe4A4logwZKKkduIJthAxuKbf5JeCspTQI8yLCBYRU0LBv4vjINNRpjY/vCeXUaFeQd5Sd1iw186pw7vvsUoSdrIdVlf2BaARJ5M2hO8SmeZRBFxaZOlN5Ud8rRNxFQOkMXxDtwY+6ihYViLDKjY4Ej1wi7pFTPPRz9R7I9APGT9UJQ/M47DWqd3bZMlZ84TPSntRXb/Qf0QUswS9fV36pQCFKwFfIXEmnF1hQfIxMTRyyMKOw7SqPT8xazexDGy9mcxaOzskeC0OZL2E4jfTnQSoMY/woCIKg==";
String data = "{\"packageName\":\"com.topdox.android.trivialdrivesample2\",\"productId\":\"topdox_android_monthly_subscription\",\"purchaseTime\":1456139019030,\"purchaseState\":0,\"purchaseToken\":\"edgcacfhmkpekcilnihgdjkb.AO-J1OxnZr_-c4xGioV-wbb9YI4w7gtRzY87CRLsa6CrHuP_nF97WNzHaBjbqCyZeYYf_sZByLD1DKxkMOFlpIsiOJnSeHxu5XIwa303DbJwFQ7Lo-sM6dgY4-4DCEqk61C9qgUx0GsLaOMZJF0zMC0mRS9K8Z2P3-uSDQpUv0qorTGt7xQC42s\",\"autoRenewing\":true}";
System.out.println("Verified: " + new SignatureVerifier().verify(key, signature, data));
}
}
@skjalgsm
Copy link

skjalgsm commented Oct 11, 2017

var google ='{"Store":"GooglePlay","Payload":{"json":{"packageName":"com.topdox.android.trivialdrivesample2","productId":"topdox_android_monthly_subscription","purchaseTime":1456139019030,"purchaseState":0,"purchaseToken":"edgcacfhmkpekcilnihgdjkb.AO-J1OxnZr_-c4xGioV-wbb9YI4w7gtRzY87CRLsa6CrHuP_nF97WNzHaBjbqCyZeYYf_sZByLD1DKxkMOFlpIsiOJnSeHxu5XIwa303DbJwFQ7Lo-sM6dgY4-4DCEqk61C9qgUx0GsLaOMZJF0zMC0mRS9K8Z2P3-uSDQpUv0qorTGt7xQC42s","autoRenewing":true},"signature":"DefUPOQ2/c3LwySfk+fdczZefijWQ+eZKzOOM3bIH2+Dz+XgNUtoUe4A4logwZKKkduIJthAxuKbf5JeCspTQI8yLCBYRU0LBv4vjINNRpjY/vCeXUaFeQd5Sd1iw186pw7vvsUoSdrIdVlf2BaARJ5M2hO8SmeZRBFxaZOlN5Ud8rRNxFQOkMXxDtwY+6ihYViLDKjY4Ej1wi7pFTPPRz9R7I9APGT9UJQ/M47DWqd3bZMlZ84TPSntRXb/Qf0QUswS9fV36pQCFKwFfIXEmnF1hQfIxMTRyyMKOw7SqPT8xazexDGy9mcxaOzskeC0OZL2E4jfTnQSoMY/woCIKg=="}}'

iap.validateOnce(params, google, onValidate);

where params is an object containing clientId, clientSecret and refreshToken

The log I get is
[1507714148685][VERBOSE] Use dynamically fed secret:
[1507714148686][VERBOSE] Validate: https://appstore-sdk.amazon.com/version/1.0/verifyReceiptId/developer/{"Store":"GooglePlay","Payload":{"json":{"packageName":"com.topdox.android.trivialdrivesample2",
[1507714149333][VERBOSE] Validation failed: { status: 404, message: 'Unknown operation exception.' }

if I instead call iap.validateOnce(iap.UNITY, params, google, onValidate); then it works

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