I want to validate Google receipt validation but since I don't have client key, I cannot use Google API: https://developers.google.com/android-publisher/archive/v1_1/inapppurchases/get
So I do local validation by using public key
, signedData
and signature
.
Everything works fine since I have new orderId
with format:
GPA.XXXX-XXXX-XXXX-XXXXX
However this code doesn't work for old pattern orderId
that looks like:
4582257046313445026.7467948335710411
I get Exception:
Signature exception java.security.SignatureException: Signature length not correct: got 294 but was expecting 256
So I succeeded to generate PublicKey
bu fails on verify
:
sig.verify(Base64.decode(signature, Base64.DEFAULT) // <- java.security.SignatureException
I know that RSA
signature should be 256, in my case I got 294
Ref: Google Play Order ID updated to new format
String base64PublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3dkBTr2pD2YSqSK2ewlEWwH9Llu0iA4PkwgVOyNRxOsHfrOlqi0Cm51qdNS0aqh/SMZkuQTAroqH3pAr9gVFOiejKRw+ymTaL5wB9+5n1mAbdeO2tv2FDsbawDvp7u6fIBejYt7Dtmih+kcu707fEO58HZWqgzx9qSHmrkMZr6yvHCtAhdBLwSBBjyhPHy7RAwKA+PE+HYVV2UNb5urqIZ9eI1dAv3RHX/xxHVHRJcjnTyMAqBmfFM+o31tp8/1CxGIazVN6HpVk8Qi2uqSS5HdKUu6VnIK8VuAHQbXQn4bG6GXx5Tp0SX1fKrejo7hupNUCgOlqsYHFYxsRkEOi0QIDAQAB";
String signedData = "{\"orderId\":\"GPA.3353-8027-5082-45637\",\"packageName\":\"com.mycompany.testapp\",\"productId\":\"weekly\",\"purchaseTime\":1503578932746,\"purchaseState\":0,\"developerPayload\":\"1502364785372-5918650324956818356\",\"purchaseToken\":\"bfljoelddlibhbibhnbnflej.AO-J1Oz8pvdqCmzz04OBmegRVKEG1stj4su5HH4uc-gzsz_vlhcz7iB_NUZVBNXp3RlTGyIGnsIgOe6bqvqfUIbPC9_CrCngL0EkZp-SBwaRzfn-EgJ32yQ\",\"autoRenewing\":true}";
String signature = "TyVJfHg8OAoW7W4wuJtS4dM//zmyECiNzWa8wuVrXyDOCPirHqxjpNthq23lmAZlxbTXyMNwedMQPr9R8NJtp3VTzGuNlLYBSOERVehmgstXiiwWDBvTNzgWbwimZmFaIiCExMQiPvbXHoWQh2rClFeAd4FfdC15pNf3NqfOGhUAEmieeb572umOo4YoF0l0421pY/JWYXa+2dtO6pcnSHF6gidRDXR66s/enRZUvkB4x9CEHdA862LDKbwOG4Aihh03IRLjD+m/5WNW+w05Q8bNNA6sCzFGVD+qa3IDiSqkiISCpd3UnufePxf3+O2doWjg2mXC5agEDMnNXvhfrw==";
boolean result = DefaultSignatureValidator.validate(base64PublicKey, signedData, signature);
DefaultSignatureValidator.class:
public class DefaultSignatureValidator {
protected static final String KEY_FACTORY_ALGORITHM = "RSA";
protected static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
/**
* Generates a PublicKey instance from a string containing the
* Base64-encoded public key.
*
* @param encodedPublicKey
* Base64-encoded public key
* @throws IllegalArgumentException
* if encodedPublicKey is invalid
*/
protected static PublicKey generatePublicKey(String encodedPublicKey) {
try {
byte[] decodedKey = Base64.decode(encodedPublicKey, Base64.DEFAULT);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch (InvalidKeySpecException e) {
System.out.println("Invalid key specification.");
throw new IllegalArgumentException(e);
} catch (Exception e) {
System.out.println("Base64 decoding failed.");
throw new IllegalArgumentException(e);
}
}
protected static boolean validate(PublicKey publicKey, String signedData, String signature) {
Signature sig;
try {
sig = Signature.getInstance(SIGNATURE_ALGORITHM);
sig.initVerify(publicKey);
sig.update(signedData.getBytes());
if (!sig.verify(Base64.decode(signature, Base64.DEFAULT))) {
System.out.println("Signature verification failed.");
return false;
}
return true;
} catch (NoSuchAlgorithmException e) {
System.out.println("NoSuchAlgorithmException" + e);
} catch (InvalidKeyException e) {
System.out.println("Invalid key specification" + e);
} catch (SignatureException e) {
System.out.println("Signature exception" + e);
} catch (Exception e) {
System.out.println("Base64 decoding failed" + e);
}
return false;
}
public static boolean validate(String base64PublicKey, String signedData, String signature) {
PublicKey key = DefaultSignatureValidator.generatePublicKey(base64PublicKey);
return DefaultSignatureValidator.validate(key, signedData, signature);
}
}
Any ideas how to validate it?
If you have solution, no matter in what language Clojure, Skala, Ruby, Java .....
In assumption you're trying to validate some real receipt: if you're getting SignatureException
- that means nothing else but the given signature is invalid. And your code must handle such signatures accordingly, i.e.
...
} catch (SignatureException e) {
return false;
}
...
You've requested some code related to processing for old-styled orderId: here you go. As you can see, the validation part in Ruby is much the same to yours one. Again, there is no issue with your validation piece of code. The problem consists in particular receipt signatures themselves.
And now, the answer on why do you have invalid signatures. You are not alone in seeing invalid signatures coming in, especially for old-formatted order ids like asked here (and there and everywhere) which are coming far after Google has switched to the new GPA-prefixed ids. By the way, you can double-check you have the same conditions as below:
Searching for the orderIds in the GooglePlay console's order management tab returns no results for these orderIds.
The root cause most logically coming to mind is fraudulent activity. Check out this answer for example of how fraudulent flow can look like
Returning to your question on how to locally validate receipt with old order id - validate it exactly in the same way as one with new order id (as you already do), and consider signature with incorrect length as one which is just not valid.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With