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

Posted

技术标签:

【中文标题】Android 应用内购买:签名验证失败【英文标题】:Android in app purchase: Signature verification failed 【发布时间】:2013-01-14 01:43:14 【问题描述】:

我已经尝试了几天来解决这个问题,使用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;

【问题讨论】:

【参考方案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);
                );
        )
    

【讨论】:

【参考方案2】:

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

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

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

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

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

【讨论】:

【参考方案3】:

这对我有用:

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

步骤 3 应该不是必需的,因为当我收到 onSkuDetailsResponse 时,它​​应该没问题,但不是,必须稍等片刻。购买成功后,不再出现“项目不可用错误”。这就是我测试它的方式:

    清除我的应用数据 清除 Google Play 数据 运行应用程序 购买android.test.purchased 尝试购买我的商品(由于商品不可用而失败) 使用我上面的解决方案,它可以工作

【讨论】:

【参考方案4】:

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

解决方案是使用正确的许可证密钥:

Play 控制台 > 应用 > 开发工具 > 许可和应用内计费

【讨论】:

正是我的问题。决定与我的新应用程序共享我现有应用程序的许可证代码并将旧许可证密钥留在其中,给我签名验证失败错误。【参考方案5】:

您可以跳过那些“android.test.*”产品 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))  ... 

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

【讨论】:

【参考方案6】:

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

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

就是这样!

当测试产品替换为实际产品时,请记住还原更改

【讨论】:

【参考方案7】:

请检查base64EncodedPublicKeyPlay 开发者控制台 中的是否相等。 在开发者控制台中重新上传 APK 后,公钥可能会发生变化,如果发生这种情况,请更新您的 base64EncodedPublicKey

【讨论】:

我遇到了同样的错误,我的钥匙完全一样。肯定有其他事情发生。 我遇到了同样的问题,并且确实与公钥不匹配。但是,每次您重新上传 APK 时,公钥不会发生变化(谢天谢地!)。 @Jean-PhilippePellet 这里也一样!不知道什么时候换钥匙。 “一旦你在开发者控制台重新上传APK,公钥可能会改变”你是说每次我上传新版本到游戏商店我都必须改变base64EncodedPublicKey?这真的很荒谬。 不,正如其他人在这个问题中所说,问题可能是由于 android.test.purchased SKU。不是键不匹配。【参考方案8】:

我也有同样的问题,按照@Deadolus 说的基于https://www.gaffga.de/implementing-in-app-billing-for-android/

关键是我们需要让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 替换为您应用的包名称。

【讨论】:

【参考方案9】:

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

无需对 IabHelper、Security 或任何应用内结算实用程序类进行任何更改,以避免在未来的测试购买中出现这种情况。

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

    进行 GMTDev 建议的更改 运行应用程序以确保它使用产品 删除/撤消 GMTDev 的更改 在 onActivityResult 中实现以下代码。

这不仅允许购买测试过程流畅,而且还应该避免与 iab 在尝试时返回 " Item Already Owned " 错误的任何冲突问题重新购买测试产品。

如果这是从片段中调用的,并且您的片段的 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”时它才会删除购买,所以它应该可以安全使用。

【讨论】:

确认如果您省略了onActivityResult() 回调,IAB 将无法正常工作。【参考方案10】:

这个问题在当前的 Google 结算版本中仍然存在。基本上 android.test.purchased 坏了;购买 android.test.purchased 后,Security.java 中的 verifyPurchase 函数将始终失败,QueryInventoryFinishedListener 将停止在 行if (result.isFailure());这是因为 android.test.purchased 项目总是无法通过 Security.java 中的 TextUtils.isEmpty(signature) 检查,因为它不是真正的项目并且没有返回签名由服务器。

我的建议(由于缺乏任何其他解决方案)是永远不要使用“android.test.purchased”。网上有各种代码调整,但没有一个能 100% 工作。

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

    编辑 Security.java 并将 verifyPurchase 中的“return false”行更改为“return true” - 这是暂时的,我们将在一分钟内将其放回原处。

    在您的 QueryInventoryFinishedListener 中,在“if (result.isFailure()) ...”行之后添加以下内容以消耗并摆脱您永无止境的 android.test.purchased 项目:

    if (inventory.hasPurchase(SKU_ANDROID_TEST_PURCHASE_GOOD))   
       mHelper.consumeAsync(inventory.getPurchase(SKU_ANDROID_TEST_PURCHASE_GOOD),null);
       
    

    运行您的应用程序,以便 consunmeAsync 发生,这将摆脱服务器上的“android.test.purchased”项。

    删除 consumeAsync 代码(或将其注释掉)。 返回Security.java,将“return true”改回“return false”。

您的 QueryInventoryFinishedListener 在验证时将不再出错,一切都恢复“正常”(如果您可以这样称呼它)。记住 - 不要再使用 android.test.purchased 了,因为它只会再次导致这个错误......它坏了!测试您购买它的唯一真正方法是上传 APK,等待它出现,然后在启用日志记录的设备上测试它(相同的 APK)。

【讨论】:

我遇到了同样的问题,但在this site 找到了一个更简单的使用android.test.purchase 的解决方案,他在那里静态创建购买以供以后使用:` pp = 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\"", "");` Google 惊人的无能总是让我感到惊讶。 感谢@GTMDev。这在 2015 年仍然存在问题,您的回答帮助我恢复正常。此外,对于未来的读者,上面 SKU_ANDROID_TEST_PURCHASE_GOOD 常量的值应该是“android.test.purchased”。 是的,问题仍然存在。在我购买了 android.test.purchased 之后,我开始在查询库存时遇到错误。我只想补充一点,只需清除 Google Play Store 应用程序的数据并运行一次即可修复您的手机。当您清除 Google Play 的数据时,它会忘记您购买了 android.test.purchased。 Srsly 这个 API 到底是谁写的?太可怕了【参考方案11】:

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

【讨论】:

它对我有用。感谢这个非常简单的解决方案。 最简单的解决方案。它也适用于我!谢谢!这应该被接受! 这是一个更简单的解决方案,而且不太容易出错,因为您不会在使用测试购买后不小心忘记删除代码... 2015 年 10 月 16 日,这种情况仍然存在。 21/10.15 仍在发生 - android.test.purchased 是时间整理出来的原因吗?逆天。感谢您让我免于浪费一天多的时间,干杯【参考方案12】:

根据 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);

我只修改了一行(见评论),这样您就可以保留这样的代码进行调试,并且仍然可以安全地发布您的发布版本。

【讨论】:

您好,我有一个关于应用内结算的问题:***.com/questions/36298320/…【参考方案13】:

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

【讨论】:

【参考方案14】:

查看answer:

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

否则,您将不会在 android.test.* 静态响应上获得签名 除非该应用之前已在 Play 上发布过。

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

这是评论:

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

此外,以前来自 Google Play 结算库的示例代码(许多大型应用程序使用)允许使用空签名。这就是静态购买在那里奏效的原因。 但这是一个安全漏洞,所以当它是published时,谷歌提交了一个update。

【讨论】:

我有一个关于应用内结算的问题:***.com/questions/36298320/… 很抱歉在这里问@Luten,但是你有没有关于如何通过应用内结算处理增值税的经验,以及它是由谷歌自动完成的国家,以及我必须手动报告的国家/缴纳增值税?见***.com/questions/36506835/… @VidarVestnes,抱歉,无法帮助您。

以上是关于Android 应用内购买:签名验证失败的主要内容,如果未能解决你的问题,请参考以下文章

Google Play 购买中签名验证失败

Android应用内购买服务器签名验证使用php OpenSSL

Android LVL 签名验证失败

JWT/OAuth 令牌签名验证失败

IDX10501:签名验证失败。无法匹配键

IOS应用内购买验证或数据检索