Android beim App-Kauf: Signaturüberprüfung fehlgeschlagen


71

Ich habe mehrere Tage lang versucht, dieses Problem mithilfe des mit dem SDK gelieferten Dungeons-Demo-Codes zu lösen. Ich habe versucht, eine Antwort bei Google zu finden, kann aber keine finden.

  • In der Dungeons-Demo habe ich meinen öffentlichen Schlüssel von der Entwicklerkonsole übergeben.
  • Signierte die apk und lud sie ohne Veröffentlichung auf die Konsole hoch.
  • Testen auf beide android.test.purchasedund auf der Konsole erstellte Produktliste mit Abonnement veröffentlicht (die Hauptfunktion, die ich für meine App möchte).

Trotzdem bekomme ich einen Fehler von Signature verification failedund dann stimmt die Signatur nicht mit den Daten überein. Wie kann ich das lösen?

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

Antworten:


150

Dieses Problem tritt in der aktuellen Google-Abrechnungsversion weiterhin auf. Grundsätzlich ist der android.test.purchased kaputt; Nach dem Kauf von android.test.purchased schlägt die Funktion verifyPurchase in Security.java immer fehl und der QueryInventoryFinishedListener stoppt an der Zeile if (result.isFailure ()) . Dies liegt daran, dass das gekaufte android.test.purchased- Element die Prüfung TextUtils.isEmpty (Signatur) in Security.java immer nicht besteht, da es sich nicht um ein echtes Element handelt und keine vom Server zurückgegebene Signatur vorliegt .

Mein Rat (aus Mangel an einer anderen Lösung) ist, NIEMALS "android.test.purchased" zu verwenden. Es gibt verschiedene Code-Optimierungen im Netz, aber keine davon funktioniert zu 100%.

Wenn Sie android.test.purchased verwendet haben, können Sie den Fehler wie folgt beheben: -

  1. Bearbeiten Sie Security.java und ändern Sie die Zeile "return false" in verifyPurchase in "return true". Dies ist nur vorübergehend. Wir werden sie in einer Minute zurücksetzen.
  2. Fügen Sie in Ihrem QueryInventoryFinishedListener nach den Zeilen "if (result.isFailure ()) {...}" Folgendes hinzu, um Ihr nie endendes android.test.purchased-Element zu konsumieren und zu entfernen:

    if (inventory.hasPurchase(SKU_ANDROID_TEST_PURCHASE_GOOD)) {  
       mHelper.consumeAsync(inventory.getPurchase(SKU_ANDROID_TEST_PURCHASE_GOOD),null);
       }
    
  3. Führen Sie Ihre App so aus, dass consunmeAsync ausgeführt wird. Dadurch wird das Element "android.test.purchased" auf dem Server entfernt.

  4. Entfernen Sie den ConsumAsync-Code (oder kommentieren Sie ihn aus).
  5. Ändern Sie in der Datei Security.java "return true" wieder in "return false".

Ihr QueryInventoryFinishedListener wird bei der Überprüfung keinen Fehler mehr machen, alles ist wieder "normal" (wenn Sie es so nennen können). Denken Sie daran - verwenden Sie android.test.purchased nicht erneut, da dies nur erneut zu diesem Fehler führt ... es ist kaputt! Die einzige echte Möglichkeit, Ihren Kauf zu testen, um eine APK hochzuladen, darauf zu warten, dass sie angezeigt wird, und sie dann (dieselbe APK) auf Ihrem Gerät mit aktivierter Protokollierung zu testen.


5
Ich hatte das gleiche Problem, fand aber eine einfachere Lösung, um den android.test.purchase auf dieser Site zu konsumieren. Dort erstellt er den Kauf statisch, um ihn später zu konsumieren: `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 "}", ""); `
Deadolus

54
Googles atemberaubende Inkompetenz überrascht mich immer wieder.
TimSim

6
Danke @GTMDev. Dies ist im Jahr 2015 immer noch kaputt und Ihre Antwort hat mir geholfen, wieder normal zu werden. Für zukünftige Leser sollte der Wert der obigen Konstanten SKU_ANDROID_TEST_PURCHASE_GOOD "android.test.purchased" sein.
Bäume

16
Ja, das Problem tritt weiterhin auf. Nachdem ich android.test.purchased gekauft habe, erhalte ich den Fehler beim Abfragen des Inventars. Ich möchte nur hinzufügen, dass es möglich ist, Ihr Telefon zu reparieren, indem Sie nur die Daten der Google Play Store-Anwendung löschen und einmal ausführen. Wenn Sie Daten von Google Play löschen, wird vergessen, dass Sie android.test.purchased gekauft haben.
Robert

10
Srsly, wer zum Teufel hat diese API geschrieben? Es ist schrecklich
user2161301

50

Ja, das Problem tritt weiterhin auf. Nachdem ich android.test.purchased gekauft habe, erhalte ich den Fehler beim Abfragen des Inventars. Sie können Ihr Telefon reparieren, indem Sie einfach die Daten der Google Play Store-Anwendung löschen und Google Play einmal ausführen. Wenn Sie Daten von Google Play löschen, wird vergessen, dass Sie android.test.purchased gekauft haben


1
Für mich geht das. Vielen Dank für diese sehr einfache Lösung.
Sydney

Einfachste Lösung. Das funktioniert auch bei mir! Vielen Dank! Dies sollte als Antwort akzeptiert werden!
Vaske

Dies ist eine viel einfachere Lösung und weniger fehleranfällig, da Sie nicht versehentlich vergessen können, Code zu löschen, nachdem Sie den Testkauf verbraucht haben ...
DiscDev

16.10.2015 und dies tritt immer noch auf.
Pimentoso

1
Immer noch passiert 21 / 10.15 - android.test.purchased ist der Grund für die Stunden, in denen dies geklärt wird? nicht zu glauben. Danke, dass du mich
davor bewahrt

23

Bitte überprüfen Sie, ob base64EncodedPublicKeyund die von der Play Developer Console gleich sind. Sobald Sie die APK in der Entwicklerkonsole erneut hochladen , kann sich der öffentliche Schlüssel ändern. Aktualisieren Sie in diesem Fall Ihren base64EncodedPublicKey.


Ich erhalte den gleichen Fehler und meine Schlüssel sind genau gleich. Es muss noch etwas los sein.
Alfie Hanssen

4
Ich hatte das gleiche Problem und hatte tatsächlich eine Nichtübereinstimmung mit dem öffentlichen Schlüssel. Der öffentliche Schlüssel scheint sich jedoch nicht jedes Mal zu ändern, wenn Sie eine APK erneut hochladen (Gott sei Dank!).
Jean-Philippe Pellet

@ Jean-PhilippePellet Gleich hier! Ich weiß nicht, wann der Schlüssel geändert wird.
Deqing

"Sobald Sie die APK in der Entwicklerkonsole erneut hochladen, kann sich der öffentliche Schlüssel ändern." Sagen Sie, dass ich base64EncodedPublicKey jedes Mal ändern muss, wenn ich eine neue Version in den Play Store hochlade? Das wäre wirklich lächerlich.
Marian Paździoch

Nein, wie andere in dieser Frage angegeben haben, liegt das Problem wahrscheinlich an der von android.test.purchased SKU. Keine Schlüsselinkongruenz.
IgorGanapolsky

9

Sie können den Überprüfungsprozess für diese Produkt-IDs "android.test. *" Überspringen. Wenn Sie den Beispielcode aus dem TrivialDrive-Beispiel verwenden, öffnen Sie IabHelper.java, suchen Sie den folgenden Zeilencode und ändern Sie ihn von

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

in

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

Es ist harmlos, auch wenn Sie vergessen haben, den Code zurückzusetzen. So können Sie den weiteren Workflow-Schritt weiter testen.


8

Basierend auf der Antwort von GMTDev ist dies das, was ich tue, um die Testprobleme beim Konsum von Produkten auf einfachste Weise zu beheben . Ersetzen Sie in Security.java die verifyPurchase () -Methode durch folgende:

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

Ich habe nur eine Zeile geändert (siehe Kommentar). Auf diese Weise können Sie den Code für das Debuggen beibehalten und Ihre Release-Versionen dennoch sicher veröffentlichen.


Hallo, ich habe eine Frage zu in der App-Abrechnung: stackoverflow.com/questions/36298320/…
Ruchir Baronia

5

Der Fehler wird durch den falschen Lizenzschlüssel verursacht. Möglicherweise stammt der Lizenzschlüssel von Ihrer anderen App.

Die Lösung besteht darin, den richtigen Lizenzschlüssel zu verwenden von:

Spielekonsole> App> Entwicklungstools> Lizenzierung und In-App-Abrechnung


Genau mein Problem. Es wurde beschlossen, den Lizenzcode aus meiner vorhandenen App mit meiner neuen App zu teilen, und der alte Lizenzschlüssel wurde dort belassen, sodass der Fehler bei der Überprüfung der Signatur fehlgeschlagen ist.
Doug Simonton

3

Bei der Verwendung von In-App Billing v3 und den enthaltenen Dienstprogrammklassen hat sich für mich der Testkauf innerhalb des zurückgegebenen onActivityResult-Aufrufs bewährt.

Es sind keine Änderungen an IabHelper, Security oder einer der In-App Billing Util-Klassen erforderlich, um dies für zukünftige Testkäufe zu vermeiden.

Wenn Sie bereits versucht haben, das Testprodukt zu kaufen, und nun den Fehler "Überprüfung der Kaufsignaturprüfung fehlgeschlagen" haben, den Sie wahrscheinlich haben, da Sie nach Antworten auf diesen Fehler suchen, sollten Sie:

  1. Nehmen Sie die von GMTDev empfohlenen Änderungen vor
  2. Führen Sie die App aus, um sicherzustellen, dass das Produkt verbraucht wird
  3. Entfernen / Rückgängigmachen der Änderungen von GMTDev
  4. Implementieren Sie den folgenden Code in onActivityResult.

Dies ermöglicht nicht nur einen reibungslosen Kaufprozess, sondern sollte auch Konflikte vermeiden, wenn iab beim Versuch, das Testprodukt zurückzukaufen, den Fehler " Item Already Owned " zurückgibt.

Wenn dies aus einem Fragment heraus aufgerufen wird und das onActivityResult Ihres Fragments nicht aufgerufen wird, rufen Sie bei Bedarf YourFragmentName.onActivityResult (requestCode, resultCode, data) von Ihrem übergeordneten ActivityFragment auf. Dies wird ausführlicher unter Aufrufen von startIntentSenderForResult aus Fragment (Android Billing v3) erläutert. .

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

Der Kauf wird nur entfernt, wenn die SKU "android.test.purchased" lautet, sodass die Verwendung sicher sein sollte.


Bestätigtes IAB funktioniert NICHT ordnungsgemäß, wenn Sie den onActivityResult()Rückruf weggelassen haben .
Grux

2

Diese Lösung hat bei mir funktioniert. Ich habe die neue verifyPurchase-Methode in der Kaufklasse durch die alte geändert.


1

Die Signaturüberprüfung schlägt nur für das Standardtestprodukt fehl. Eine schnelle Lösung:

  • Gehe zur IabHelper-Klasse.
  • Invertieren Sie die if-Bedingungen von Security.verifyPurchase.

Das ist es!

Denken Sie daran, die Änderungen rückgängig zu machen, wenn das Testprodukt durch das tatsächliche Produkt ersetzt wird


1

Heute (30. Oktober 2018) ist das gleiche Problem aufgetreten (Überprüfung der Signatur und Abschaffung des Testkaufs).

Das Signaturproblem wird wahrscheinlich durch die Tatsache verursacht, dass diese Test-Skus nicht wirklich Teil Ihrer App sind und daher nicht über die Signatur Ihrer App verfügen. Ich habe ein Ticket bei Google geöffnet, bin mir aber nicht sicher, ob sie das beheben können. Die Problemumgehung besteht, wie andere betonten, darin, den Code zu ersetzen

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

mit

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

In Bezug auf "Wie man den Kauf von android.test.purchased SKU loswird" stellte ich fest, dass ein einfacher Neustart des Geräts, gefolgt von etwa einer Minute Wartezeit und / oder ein paar Neustarts der App, das Problem behoben hat für mich (dh ich musste den Kauf nicht per Code "konsumieren"). Ich vermute, dass das Warten erforderlich ist, damit der Play Store die Synchronisierung mit den Servern von Google abschließt. (Ich bin mir nicht sicher, ob dies auch in Zukunft so funktionieren wird. Wenn es jetzt für Sie funktioniert, kann dies Ihnen helfen, weiterzukommen.)


0

Überprüfen Sie diese Antwort :

Entspricht das primäre Konto auf Ihrem Testgerät Ihrem Google Play-Entwicklerkonto?

Wenn nicht, erhalten Sie keine Signaturen für den android.test. * Statische Antworten, es sei denn, die App wurde zuvor auf Play veröffentlicht.

In der Tabelle unter http://developer.android.com/guide/market/billing/billing_testing.html#static-responses-table finden Sie alle Bedingungen.

Und es ist ein Kommentar:

Ich glaube nicht, dass die statischen IDs mehr Signatur zurückgeben. Siehe https://groups.google.com/d/topic/android-developers/PCbCJdOl480/discussion

Zuvor erlaubte der Beispielcode (der von vielen großen Apps verwendet wird) aus der Google Play Billing Library eine leere Signatur. Deshalb haben dort statische Einkäufe funktioniert.
Da es sich jedoch um eine Sicherheitslücke handelte , hat Google bei der Veröffentlichung ein Update eingereicht .


Ich habe eine Frage zu in der App-Abrechnung: stackoverflow.com/questions/36298320/…
Ruchir Baronia

Es tut mir leid, dass Sie hier @Luten gefragt haben, aber haben Sie Erfahrung mit dem Umgang mit Mehrwertsteuer bei In-App-Abrechnungen und in welchen Ländern wird dies automatisch von Google durchgeführt und an welche muss ich die Mehrwertsteuer manuell melden / zahlen? siehe stackoverflow.com/questions/36506835/…
Vidar Vestnes

@VidarVestnes, sorry, kann dir dabei nicht helfen.
Lauten

0

Ich habe das gleiche Problem und folge @Deadolus basierend auf https://www.gaffga.de/implementing-in-app-billing-for-android/

Der entscheidende Punkt ist, dass die SKU konsumierbar ist, auch wenn das Ergebnis der Bestandsabfrage fehlschlägt. Unten ist das Beispiel, wie ich das gemacht habe.

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().
             */                   
        }
    };

Ersetzen Sie PACKAGE_NAME im obigen Code durch den Paketnamen Ihrer App.


0

Das hat bei mir funktioniert:

  1. Rufen Sie BillingClient.querySkuDetailsAsync auf, um abzufragen, ob ein Element verfügbar ist
  2. Warten Sie auf SkuDetailsResponseListener.onSkuDetailsResponse
  3. Warten Sie weitere 500 ms
  4. Starten Sie den Kauf mit BillingClient.launchBillingFlow ...

Der Schritt 3 sollte nicht notwendig sein, denn als ich onSkuDetailsResponse erhielt, sollte es in Ordnung sein, aber es ist nicht so, ich musste ein wenig warten. Nachdem dieser Kauf funktioniert, wird kein Fehler "Artikel nicht verfügbar" angezeigt. So habe ich es getestet:

  1. lösche meine App-Daten
  2. Google Play-Daten löschen
  3. App ausführen
  4. Kauf android.test.purchased
  5. versuche meine Artikel zu kaufen (dies schlägt fehl, wenn der Artikel nicht verfügbar ist)
  6. Verwenden Sie meine Lösung oben, es funktioniert

-1

Für Cordova- und Hybrid-Apps müssen Sie die Methode this.iap.subscribe (this.productId) verwenden, um InAppPurchase zu abonnieren.

Folgendes ist der Code, der für mich gut funktioniert:

 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);
                });
        })
    }
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.