Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to validate Google receipt validation locally with old Order ID format?

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

Code example

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 .....

like image 573
snaggs Avatar asked Aug 27 '17 06:08

snaggs


1 Answers

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.

like image 75
Kostiantyn Avatar answered Oct 05 '22 06:10

Kostiantyn