java - 如何使用旧的订单 ID 格式在本地验证 Google 收据验证?

标签 java android google-play in-app-billing android-pay

我想验证 Google 收据验证,但由于我没有客户端 key ,我无法使用 Google API:https://developers.google.com/android-publisher/archive/v1_1/inapppurchases/get

因此我使用公钥signedDatasignature 进行本地验证。

一切正常,因为我有新的 orderId,格式为:

GPA.XXXX-XXXX-XXXX-XXXXX

但是,此代码不适用于如下所示的旧模式 orderId:

4582257046313445026.7467948335710411

我得到异常:

Signature exception java.security.SignatureException: Signature length not correct: got 294 but was expecting 256

所以我成功生成了PublicKey,但是在verify上失败了:

sig.verify(Base64.decode(signature, Base64.DEFAULT) // <- java.security.SignatureException

我知道 RSA 签名应该是 256,在我的例子中我得到了 294

引用: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);
        }
}

有什么想法可以验证吗?

如果你有解决方案,不管用什么语言 Clojure, Skala, Ruby, Java .....

最佳答案

假设您正在尝试验证一些真实 收据:如果您收到SignatureException - 这意味着除了给定的签名无效。并且您的代码必须相应地处理此类签名,即

...
} catch (SignatureException e) {
    return false;
} 
...

您请求了一些与处理旧式 orderId 相关的代码:here you go .如您所见,Ruby 中的验证部分与您的验证部分非常相似。同样,您的验证代码没有问题。问题尤其在于收据签名本身。

现在,关于为什么您有无效签名的答案。您并不是唯一一个看到无效签名的人,尤其是对于像 asked here 这样的旧格式订单 ID。 (以及 thereeverywhere )在谷歌切换到新的 GPA 前缀 id 之后已经很远了。顺便说一句,您可以仔细检查您是否具有与以下相同的条件:

Searching for the orderIds in the GooglePlay console's order management tab returns no results for these orderIds.

最符合逻辑的根本原因是欺诈 Activity 。查看this answer例如,欺诈流的样子

回到您关于如何使用旧订单 ID 在本地验证收据的问题 - 以与使用新订单 ID 的收据完全相同的方式验证它(正如您已经做的那样),并将长度不正确的签名视为不正确的签名有效。

关于java - 如何使用旧的订单 ID 格式在本地验证 Google 收据验证?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45902319/

相关文章:

java - 如何在不出现 NullPointEreException 的情况下将数组的大小加倍?

java - 自定义错误请求HTTP400页面Spring MVC

android - Robotium 是否可以可靠地测试 Activity 和 fragment 的启动速度?

android - 为多个客户端发布 Android 私有(private)应用

java - 使用 JGit 获取文件、修改内容并提交

java - 在抽象方法中比instanceof/cast更好的选择?

android - "developer payload"的目的是什么?它可以用来做什么?

android - MPAndroidChart 使用 Proguard 编译错误

android - 有没有办法在 Play 商店中为每个 android 版本(不是 API 级别)设置应用程序兼容性?

android - 原生 Android JAVA 到 Flutter