Android应用内购买:签名验证失败

时间:2013-01-30 09:36:21

标签: android in-app-billing signing

我已经尝试了几天来解决这个问题,使用SDK附带的Dungeons演示代码。我曾尝试谷歌寻求答案,但找不到答案。

  • 在Dungeons演示中,我从开发控制台传递了我的公钥。
  • 签名apk并上传到控制台而不发布。
  • 测试android.test.purchased和&在控制台上创建的产品列表已发布以供订阅(我想要的应用程序的主要功能)。

但我仍然收到Signature verification failed的错误,然后签名与数据不匹配。我该如何解决这个问题?

public static ArrayList<VerifiedPurchase> verifyPurchase(String signedData, String signature)
{
    if (signedData == null) {
        Log.e(TAG, "data is null");
        return null;
    }
    if (Consts.DEBUG) {
        Log.i(TAG, "signedData: " + signedData);
    }
    boolean verified = false;
    if (!TextUtils.isEmpty(signature)) {

        String base64EncodedPublicKey = "MIIBIjA....AQAB";
        PublicKey key = Security.generatePublicKey(base64EncodedPublicKey);
        verified = Security.verify(key, signedData, signature);
        if (!verified) {
            Log.w(TAG, "signature does not match data.");
            return null;
        }
    }
}

public static boolean verify(PublicKey publicKey, String signedData, String signature)
{
    if (Consts.DEBUG) {
        Log.i(TAG, "signature: " + signature);
    }
    Signature sig;
    try {
        sig = Signature.getInstance(SIGNATURE_ALGORITHM);
        sig.initVerify(publicKey);
        sig.update(signedData.getBytes());
        if (!sig.verify(Base64.decode(signature))) {
            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.");
    } catch (Base64DecoderException e) {
        Log.e(TAG, "Base64 decoding failed.");
    }
    return false;
}

14 个答案:

答案 0 :(得分:146)

目前的Google结算版本仍然存在此问题。基本上 android.test.purchased 被打破了;购买android.test.purchased后, Security.java 中的 verifyPurchase 功能将始终失败, QueryInventoryFinishedListener 将停在行if(result.isFailure());这是因为 android.test.purchased 项始终未通过Security.java中的 TextUtils.isEmpty(签名)检查,因为它不是真实项目且没有返回签名由服务器。

我的建议(缺乏任何其他解决方案)绝不使用&#34; android.test.purchased&#34;。网上有各种代码调整,但它们都不能100%工作。

如果您使用过android.test.purchased,那么摆脱错误的一种方法是执行以下操作: -

  1. 编辑Security.java并更改&#34;返回false&#34;验证行中的行&#34;返回true&#34; - 这是暂时的,我们会在一分钟之内把它放回去。
  2. 在QueryInventoryFinishedListener中,在&#34; if(result.isFailure()){...}&#34;之后行添加以下内容以消耗和摆脱永无止境的android.test.purchased项目:

    if (inventory.hasPurchase(SKU_ANDROID_TEST_PURCHASE_GOOD)) {  
       mHelper.consumeAsync(inventory.getPurchase(SKU_ANDROID_TEST_PURCHASE_GOOD),null);
       }
    
  3. 运行你的应用程序,以便consunmeAsync发生,这摆脱了&#34; android.test.purchased&#34;服务器上的项目。

  4. 删除consumeAsync代码(或将其注释掉)。
  5. 回到Security.java,更改&#34;返回true&#34;回到&#34;返回false&#34;。
  6. 您的QueryInventoryFinishedListener将不再在验证时出错,一切都回到&#34;正常&#34; (如果你可以称之为)。请记住 - 不要再费心使用android.test.purchased,因为它只会再次导致此错误...它已经破了!唯一真正的方法是测试您购买它以上传APK,等待它出现,然后在启用了日志记录的设备上测试它(相同的APK)。

答案 1 :(得分:46)

是的,问题仍然存在。 在我买了android.test.purchased之后,我开始在查询库存时遇到错误。 只需清除Google Play商店应用程序的数据并运行Google Play,即可修复手机。 当您清除Google Play的数据时,它会忘记您购买了android.test.purchased

答案 2 :(得分:20)

请检查base64EncodedPublicKey Play Developer Console 中的那个是否相同。 在开发者控制台中重新上传APK后,公钥可能会发生变化,如果是,请更新base64EncodedPublicKey

答案 3 :(得分:9)

您可以跳过那些&#34; android.test。*&#34;的验证过程。产品ID。如果您正在使用TrivialDrive示例中的示例代码,请打开IabHelper.java,找到以下行代码,从

更改它
   if (Security.verifyPurchase(mSignatureBase64, purchaseData, dataSignature)) { ... }

   boolean verifySignature = !sku.startsWith("android.test."); // or inplace the condition in the following line
   if (verifySignature && !Security.verifyPurchase(mSignatureBase64, purchaseData, dataSignature)) { ... }

即使您忘记回滚代码,它也是无害的。因此,您可以继续测试进一步的工作流程步骤。

答案 4 :(得分:7)

根据GMTDev的回答,这就是我在以最简单的方式消费产品时解决测试问题的方法。在Security.java中,将verifyPurchase()方法替换为:

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 BuildConfig.DEBUG; // Line modified by Cristian. Original line was: return false;
    }

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

我只修改了一行(请参阅注释),这样你就可以保存代码来进行调试,并且仍然可以安全地发布你的发行版本。

答案 5 :(得分:4)

错误是由于许可证密钥错误引起的。也许许可证密钥可能来自您的另一个应用程序。

解决方案是使用以下适当的许可证密钥:

  

播放控制台&gt;应用&gt;开发工具&gt;许可证和应用内结算

答案 6 :(得分:3)

在使用In-app Billing v3和所包含的实用程序类时,对我来说有用的是在返回的onActivityResult调用中消耗测试购买。

无需更改IabHelper,安全性或任何应用内结算工具类,以避免此类测试购买。

如果您已经尝试购买测试产品并且现在卡在购买签名验证失败错误上,您可能是因为您正在查找此错误的答案,那么您应该:

  1. 进行GMTDev推荐的更改
  2. 运行应用以确保其使用产品
  3. 删除/撤消GMTDev的更改
  4. 在onActivityResult中实现以下代码。
  5. 这不仅可以使购买测试过程流畅,而且还可以避免在尝试时iab返回项目已经拥有错误时出现的任何冲突问题重新购买测试产品。

    如果从片段内调用此片段并且未调用片段的onActivityResult,则必须确保从父ActivityFragment调用YourFragmentName.onActivityResult(requestCode,resultCode,data)。这在Calling startIntentSenderForResult from Fragment (Android Billing v3) 中有更详细的解释。

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == REQUEST_PURCHASE) {
    
            //this ensures that the mHelper.flagEndAsync() gets called 
            //prior to starting a new async request.
            mHelper.handleActivityResult(requestCode, resultCode, data);
    
            //get needed data from Intent extra to recreate product object
            int responseCode = data.getIntExtra("RESPONSE_CODE", 0);
            String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA");
            String dataSignature = data.getStringExtra("INAPP_DATA_SIGNATURE");
    
            // Strip out getActivity() if not being used within a fragment
            if (resultCode == getActivity().RESULT_OK) {
                try {
                    JSONObject jo = new JSONObject(purchaseData);
                    String sku = jo.getString("productId");
    
                    //only auto consume the android.test.purchased product
                    if (sku.equals("android.test.purchased")) {
                        //build the purchase object from the response data
                        Purchase purchase = new Purchase("inapp", purchaseData, dataSignature);
                        //consume android.test.purchased
                        mHelper.consumeAsync(purchase,null);
                    }
                } catch (JSONException je) {
                    //failed to parse the purchase data
                    je.printStackTrace();
                } catch (IllegalStateException ise) {
                    //most likely either disposed, not setup, or 
                    //another billing async process is already running
                    ise.printStackTrace();
                } catch (Exception e) {
                    //unexpected error
                    e.printStackTrace();
                }
            }
        }
    }
    

    如果sku是“android.test.purchased”,它将仅删除购买,因此它应该是安全的。

答案 7 :(得分:2)

This解决方案对我有用。我在旧购买类中更改了新的verifyPurchase方法。

答案 8 :(得分:1)

签名验证仅对默认测试产品失败。 快速修复:

  • 转到IabHelper班。
  • 反转Security.verifyPurchase的if条件。

多数民众赞成!

请记住在测试产品被实际产品替换时还原更改

答案 9 :(得分:1)

今天(2018年10月30日)遇到同样的问题(签名验证和摆脱测试购买)。

签名问题可能是由于这些测试sku并不是您应用程序的真正组成部分,因此没有您应用程序的签名。我确实在Google开了票,但不确定他们是否可以解决此问题。正如其他人指出的那样,解决方法是替换代码

if (verifyValidSignature(purchase.getOriginalJson(), purchase.getSignature())) {

if (verifyValidSignature(purchase.getOriginalJson(), purchase.getSignature()) ||
                        (purchase.getSku().startsWith("android.test.")) ) { 

关于“如何摆脱购买android.test.purchased SKU的购买”,我发现设备只需重新启动,然后等待一分钟左右和/或重新启动您的应用,次为我解决了这个问题(即我不必通过代码“消费”购买)。我猜想需要等待,以便Play商店完成与Google服务器的同步。 (不确定将来是否会继续以这种方式工作,但是如果现在可以使用,这可能会帮助您前进。)

答案 10 :(得分:0)

检查answer

  

您的测试设备上的主要帐户是否与您的Google相同   玩开发者帐户?

     

如果不是,你将无法获得android.test。*静态响应的签名   除非该应用程序已在Play上发布。

     

见表格   http://developer.android.com/guide/market/billing/billing_testing.html#static-responses-table   对于完整的条件。

这是评论:

  

我认为静态ids不再返回签名了。看到   https://groups.google.com/d/topic/android-developers/PCbCJdOl480/discussion

此外,之前Google Play结算库中的示例代码(由许多大型应用使用)允许空签名。这就是为什么静态购买在那里工作的原因 但这是一个安全漏洞,所以当它published时,Google提交了update

答案 11 :(得分:0)

我遇到同样的问题,并根据https://www.gaffga.de/implementing-in-app-billing-for-android/

关注@Deadolus

关键是我们需要使SKU成为消耗品,即使库存查询结果失败也是如此。以下是我如何做到这一点的示例。

IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
        public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
            Log.d(TAG, "Query inventory finished.");

            // Have we been disposed of in the meantime? If so, quit.
            if (mHelper == null) return;

            // Is it a failure?
            if (result.isFailure()) {
                try {
                    Purchase purchase = new Purchase("inapp", "{\"packageName\":\"PACKAGE_NAME\","+
                            "\"orderId\":\"transactionId.android.test.purchased\","+
                            "\"productId\":\"android.test.purchased\",\"developerPayload\":\"\",\"purchaseTime\":0,"+
                            "\"purchaseState\":0,\"purchaseToken\":\"inapp:PACKAGE_NAME :android.test.purchased\"}",
                            "");
                } catch (JSONException e) {
                    e.printStackTrace();
                }
                mHelper.consumeAsync(purchase, null);
                complain("Failed to query inventory: " + result);
                return;
            }

            Log.d(TAG, "Query inventory was successful.");

            /*
             * Check for items we own. Notice that for each purchase, we check
             * the developer payload to see if it's correct! See
             * verifyDeveloperPayload().
             */                   
        }
    };

将上面代码中的PACKAGE_NAME替换为您应用的包名称。

答案 12 :(得分:0)

这对我有用:

  1. 调用BillingClient.querySkuDetailsAsync查询项目是否可用
  2. 等待SkuDetailsResponseListener.onSkuDetailsResponse
  3. 再等待500毫秒
  4. 使用BillingClient.launchBillingFlow ...开始购买

步骤3没必要,因为当我收到onSkuDetailsResponse时应该可以,但不是,必须稍等片刻。购买成功后,不再显示“商品不可用错误”。这就是我测试的方式:

  1. 清除我的应用数据
  2. 清除Google Play数据
  3. 运行应用
  4. 购买android.test.purchased
  5. 尝试购买我的物品(失败,因为物品不可用)
  6. 使用上面的解决方案,它有效

答案 13 :(得分:-1)

对于Cordova和Hybrid应用,您需要使用 this.iap.subscribe(this.productId)方法订阅InAppPurchase。

以下代码对我来说很好用:

 getProdutIAP() {
        this.navCtrl.push('subscribeDialogPage');
        this.iap
            .getProducts(['productID1']).then((products: any) => {
                this.buy(products);
            })
            .catch((err) => {
                console.log(JSON.stringify(err));
                alert('Finished Purchase' + JSON.stringify(err));
                console.log(err);
            });
    }

    buy(products: any) {
        // this.getProdutIAP();
        // alert(products[0].productId);
        this.iap.subscribe(products[0].productId).then((buydata: any) => {
            alert('buy Purchase' + JSON.stringify(buydata));
            // this.sub();
        }).catch((err) => {
            // this.navCtrl.push('subscribeDialogPage');
            alert('buyError' + JSON.stringify(err));
        });
    }

    sub() {
        this.platform.ready().then(() => {
            this.iap
                .subscribe(this.productId)
                .then((data) => {
                    console.log('subscribe Purchase' + JSON.stringify(data));
                    alert('subscribe Purchase' + JSON.stringify(data));
                    this.getReceipt();
                }).catch((err) => {
                    this.getReceipt();
                    alert('subscribeError' + JSON.stringify(err));
                    console.log(err);
                });
        })
    }