71

SDK に付属の Dungeons デモ コードを使用して、この問題を解決するために数日間試みました。Google で回答を探しましたが、見つかりません。

  • 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;
}
4

14 に答える 14

150

この問題は、現在の 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 を使用したことがある場合、エラーを取り除く 1 つの方法は次のとおりです。

  1. Security.java を編集し、verifyPurchase の「return false」行を「return true」に変更します。これは一時的なもので、すぐに元に戻します。
  2. 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);
       }
    
  3. アプリを実行して consunmeAsync が発生するようにします。これにより、サーバー上の「android.test.purchased」アイテムが削除されます。

  4. consumerAsync コードを削除 (またはコメントアウト) します。
  5. Security.java に戻り、"return true" を "return false" に戻します。

あなたの QueryInventoryFinishedListener は、検証時にエラーが発生しなくなり、すべてが「通常」に戻ります (それを呼び出すことができる場合)。覚えておいてください - もう一度 android.test.purchased を使用しないでください。このエラーが再び発生するだけです...壊れています! 購入して APK をアップロードし、表示されるのを待ってから、ロギングを有効にしたデバイスで (同じ APK) をテストする唯一の実際の方法です。

于 2014-02-28T07:18:57.593 に答える
23

PlayDeveloperConsolebase64EncodedPublicKeyのものと同じであることを確認してください。Developer ConsoleでAPKを再アップロードすると、公開鍵が変更される可能性があります。変更された場合は、を更新してください。base64EncodedPublicKey

于 2013-02-01T13:11:44.403 に答える
9

これらの「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)) { ... }

コードをロールバックするのを忘れたとしても、害はありません。したがって、さらにワークフローのステップをテストし続けることができます。

于 2016-06-01T12:35:10.573 に答える
8

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);
}

私は 1 行だけを変更しました (コメントを参照)。このようにして、デバッグ用にコードを保持し、リリース バージョンを安全に公開することができます。

于 2014-10-07T15:05:41.670 に答える
3

In-app Billing v3 と含まれているユーティリティ クラスを使用しているときに、返された onActivityResult 呼び出し内でテスト購入を消費していました。

今後のテスト購入でこれを回避するために、IabHelper、セキュリティ、またはアプリ内課金ユーティリティ クラスを変更する必要はありません。

すでにテスト製品を購入しようとしていて、購入署名の検証に失敗したというエラーが表示される場合は、このエラーの回答を探しているため、次のことを行う必要があります。

  1. GMTDev が推奨する変更を行う
  2. アプリを実行して、製品が確実に消費されるようにする
  3. GMTDev の変更を削除/元に戻す
  4. onActivityResult 内に以下のコードを実装します。

これにより、購入テスト プロセスがスムーズになるだけでなく、テスト製品を再購入しようとしたときにiab がItem Already Owned」エラーを返すという競合する問題も回避できます。

これがフラグメント内から呼び出され、フラグメントの onActivityResult が呼び出されていない場合は、必要に応じて親 ActivityFragment から YourFragmentName.onActivityResult(requestCode, resultCode, data) を呼び出してください。これについては、フラグメントからの startIntentSenderForResult の呼び出し (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」の場合にのみ購入が削除されるため、安全に使用できます。

于 2015-03-30T14:04:56.273 に答える
2

このソリューションは私にとってはうまくいきました。購入クラスの新しい verifyPurchase メソッドを古いものに変更しました。

于 2014-04-11T08:57:40.157 に答える
1

署名の検証は、デフォルトのテスト製品でのみ失敗します。簡単な修正:

  • IabHelper クラスに移動します。
  • の if 条件を反転しSecurity.verifyPurchaseます。

それでおしまい!

テスト製品が実際の製品に置き換えられた場合は、変更を元に戻すことを忘れないでください

于 2016-01-06T17:54:39.910 に答える
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 をアプリのパッケージ名に置き換えます。

于 2015-05-10T09:54:18.350 に答える
0

この回答を確認してください:

テスト デバイスのプライマリ アカウントは、Google Play デベロッパー アカウントと同じですか?

そうしないと、アプリが以前に Play で公開されていない限り、android.test.* 静的応答で署名を取得できません。

条件の完全なセットについては、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 Billing Library のサンプル コード (多くの大きなアプリで使用されている) では、空の署名が許可されていました。そのため、静的購入がそこで機能しました。
しかし、これはセキュリティ ホールであったため、公開時に Google が更新を送信しました。

于 2013-12-06T11:00:10.087 に答える