【问题标题】:in-app billing: Which public and private keys are used for the in-app billing verification?应用内计费:应用内计费验证使用哪些公钥和私钥?
【发布时间】:2018-07-28 23:25:08
【问题描述】:

使用应用内结算时,您应该验证购买数据,方法是检查 INAPP_PURCHASE_DATA 是否使用来自 Google Play 商店的 base64 编码公钥与 INAPP_DATA_SIGNATURE 签署。
请参阅 INAPP_PURCHASE_DATA 和 INAPP_DATA_SIGNATURE here 的说明。 有一个Security class 可以用来验证购买:

public class Security {
    private static final String TAG = "IABUtil/Security";

    private static final String KEY_FACTORY_ALGORITHM = "RSA";
    private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";

    /**
     * Verifies that the data was signed with the given signature, and returns
     * the verified purchase. The data is in JSON format and signed
     * with a private key. The data also contains the {@link PurchaseState}
     * and product ID of the purchase.
     * @param base64PublicKey the base64-encoded public key to use for verifying.
     * @param signedData the signed JSON string (signed, not encrypted)
     * @param signature the signature for the data, signed with the private key
     */
    public static boolean verifyPurchase(String base64PublicKey, String signedData, String signature) {
        if (TextUtils.isEmpty(signedData) || TextUtils.isEmpty(base64PublicKey) ||
                TextUtils.isEmpty(signature)) {
            Log.e(TAG, "Purchase verification failed: missing data.");
            return false;
        }

        PublicKey key = Security.generatePublicKey(base64PublicKey);
        return Security.verify(key, signedData, signature);
    }

    /**
     * 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
     */
    public 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) {
            Log.e(TAG, "Invalid key specification.");
            throw new IllegalArgumentException(e);
        }
    }

    /**
     * Verifies that the signature from the server matches the computed
     * signature on the data.  Returns true if the data is correctly signed.
     *
     * @param publicKey public key associated with the developer account
     * @param signedData signed data from server
     * @param signature server signature
     * @return true if the data and signature match
     */
    public static boolean verify(PublicKey publicKey, String signedData, String signature) {
        byte[] signatureBytes;
        try {
            signatureBytes = Base64.decode(signature, Base64.DEFAULT);
        } catch (IllegalArgumentException e) {
            Log.e(TAG, "Base64 decoding failed.");
            return false;
        }
        try {
            Signature sig = Signature.getInstance(SIGNATURE_ALGORITHM);
            sig.initVerify(publicKey);
            sig.update(signedData.getBytes());
            if (!sig.verify(signatureBytes)) {
                Log.e(TAG, "Signature verification failed.");
                return false;
            }
            return true;
        } catch (NoSuchAlgorithmException e) {
            Log.e(TAG, "NoSuchAlgorithmException.");
        } catch (InvalidKeyException e) {
            Log.e(TAG, "Invalid key specification.");
        } catch (SignatureException e) {
            Log.e(TAG, "Signature exception.");
        }
        return false;
    }
}

您必须调用 verifyPurchase 并从 Google Play 传递购买数据、给定签名和 base64PublicKey 公钥。有一个 wrapper implementation 可用于我的应用程序中的购买流程。
如果您查看IabHelper 实现,它们会传递公钥以在构造函数中进行验证。 ctor 的文档说:

 * @param base64PublicKey Your application's public key, encoded in base64.
 *     This is used for verification of purchase signatures. You can find your app's base64-encoded
 *     public key in your application's page on Google Play Developer Console. Note that this
 *     is NOT your "developer public key".
 */

我猜它们是指 Google Play 中许可和应用内计费部分中的 Base64 编码的 RSA 公钥:

也许我对密码学知之甚少,但我怎么可能使用 Google Play 的公钥来检查据说是用我的“开发人员私钥”进行的加密(参见第一个链接中的解释)。他们是指我的“我用来签署应用程序的私钥”吗?我不这么认为,因为他们不知道我的(本地)私钥(我用来签署我的应用程序)以及它与 Google Play 的这个公钥有什么关系,那么他们对“开发者的私钥”的含义是什么"。
所以我的问题是:

  • 我是否理解正确,公钥是来自 许可和应用内结算?

  • 我是否还需要add licensing to my app 才能得到这个 验证工作还是应该“开箱即用”,我也可以 省略这一步?

  • Google 使用什么“开发者的私钥”来签署 购买数据,我在哪里可以看到? (我需要运行一些单元测试 在我的服务器上检查我的实现,我想加密 INAPP_PURCHASE_DATA 也获得 INAPP_DATA_SIGNATURE 如果我用给定的验证它能够获得有效的安全检查 公钥。

[更新]。显然,私钥是隐藏的:

Google Play 管理中心会向任何登录到 Play 管理中心的开发者公开用于许可的公钥,但它会将私钥隐藏在安全位置对所有用户隐藏。

见:https://developer.android.com/google/play/licensing/adding-licensing.html

【问题讨论】:

    标签: android google-play in-app-purchase in-app-billing android-billing


    【解决方案1】:

    签名有不对称和对称两种:

    Asymm 使用一对密钥,私钥和公钥,密钥之间有数学关系,用私钥签名的一块数据可以用公钥验证。私钥从不公开,但公开。

    然后,Google 为您的应用内结算创建了一对密钥……但您只需要知道公众即可进行验证。 没有私钥,任何主体都不会生成有效的签名。

    相反,Symm 在双方都使用相同的密钥,这带来了共享密钥的问题,并有被嗅探的风险,但它的优势是比 asymm 更快。

    更新

    我是否还需要向我的应用添加许可才能获得此验证 工作还是应该“开箱即用”,所以我可以省略这一步吗?

    取决于,如果您想知道该应用是否已从官方 Google Play 商店安装,那么您需要验证许可,如果您的应用是付费应用,则更适用,而不是如果您的应用是免费的,但它具有 -应用产品重要的是要知道他们是否合法购买了该项目。

    对我来说,在外部服务器中验证购买更为重要,你有一个很好的例子https://stackoverflow.com/a/48645216/7690376

    【讨论】:

    • 谢谢,但我基本上知道非对称加密是如何工作的。这就是为什么我对公钥感到困惑,因为我不知道私钥在哪里。这个问题我自己已经回答过了。现在我需要回答我的问题,如果我需要许可才能让所有这些工作。
    • 抱歉,如果您对公钥和私钥感到困惑,那么您不知道什么是非对称签名......我更新了答案
    • 好吧,如果我没看错的话,购买验证基本上不需要使用授权。根据此信息,我将暂时投票并在我完成实施时接受。如果不需要,那么我不在乎,因为人们无法伪造我的购买,因为一切都在我的服务器上处理,并且我的服务器执行 Google Developer API 请求以检查状态。顺便说一句,我认为链接中描述的您的验证是不安全的。是什么阻止我作为黑客只是模拟这个请求并总是返回“true”?
    • 感谢您在问题中发布该安全类。通过一些自定义,我能够在我的应用程序中为 InApp Billing 进行设置以进行验证。再次感谢。 @Bevor
    • @i_o 很高兴能为您提供帮助。 (顺便说一句:这也不是我的最终版本,尽管我只需要进行很少的更改。我不得不使用Base64.decode(...) 而不是Base64.decodeBase64(...))。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-14
    • 2011-08-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多