Last active
January 29, 2023 09:51
-
-
Save mkorszunsands/3cbc0016ac7d0bca318228f96ffd0ab3 to your computer and use it in GitHub Desktop.
Verify Google Play in-app purchase signatures
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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