web-dev-qa-db-fra.com

Android dans l'achat d'une application: échec de la vérification de la signature

J'ai essayé pendant plusieurs jours de résoudre ce problème en utilisant le code de démonstration Dungeons fourni avec le SDK. J'ai essayé de chercher une réponse sur Google mais je n'en trouve pas.

  • Dans la démo Dungeons, j'ai passé ma clé publique à partir de la console de développement.
  • Signé l'apk et téléchargé sur la console sans publier.
  • Test à la fois pour Android.test.purchased et la liste de produits créée sur la console avec publication pour abonnement (fonctionnalité principale que je souhaite pour mon application).

Mais je reçois toujours une erreur de Signature verification failed et la signature ne correspond pas aux données. Comment puis-je résoudre ça?

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

Ce problème persiste dans la version de facturation actuelle de Google. Fondamentalement, Android.test.purchased est cassé; Après avoir acheté Android.test.purchased, la fonction verifyPurchase dans Security.Java échouera toujours et QueryInventoryFinishedListener s’arrêtera à la ligne if (result.isFailure ()) ; cela est dû au fait que l'élément Android.test.purchased échoue toujours lors du contrôle TextUtils.isEmpty (signature) dans Security.Java, car il ne s'agit pas d'un élément réel et aucune signature n'est renvoyée par le serveur. 

Mon conseil (du fait de l'absence de toute autre solution) est de ne JAMAIS utiliser "Android.test.purchased". Il existe divers ajustements de code sur le net, mais aucun d’entre eux ne fonctionne à 100%.

Si vous avez utilisé Android.test.purchased, vous pouvez vous débarrasser de l'erreur comme suit: -

  1. Modifiez Security.Java et modifiez la ligne "return false" de la commande verifyPurchase en "return true" - ceci est temporaire, nous la remettrons dans une minute. 
  2. Dans votre QueryInventoryFinishedListener, après les lignes "if (result.isFailure ()) {...}", ajoutez ce qui suit pour consommer et vous débarrasser de votre élément sans fin Android.test.purchased:

    if (inventory.hasPurchase(SKU_Android_TEST_PURCHASE_GOOD)) {  
       mHelper.consumeAsync(inventory.getPurchase(SKU_Android_TEST_PURCHASE_GOOD),null);
       }
    
  3. Lancez votre application pour que consunmeAsync se produise, ceci élimine l'élément "Android.test.purchased" sur le serveur.

  4. Supprimez le code consumeAsync (ou commentez-le).
  5. De retour dans Security.Java, remplacez "retour vrai" par "retourne faux".

Votre QueryInventoryFinishedListener ne sera plus en erreur sur la vérification, tout est redevenu "normal" (si vous pouvez l'appeler ainsi). Rappelez-vous - n'utilisez plus Android.test.purchased car cela provoquera à nouveau cette erreur ... c'est fauché! Le seul moyen de tester votre achat pour télécharger un fichier APK, attendez qu'il apparaisse, puis testez-le (le même fichier APK) sur votre appareil avec la journalisation activée. 

145
Gavin Thornton

Oui, le problème persiste. Après avoir acheté Android.test.purchased, l'erreur commence à apparaître lorsque vous interrogez l'inventaire . Il est possible de réparer votre téléphone en effaçant simplement les données de l'application Google Play Store et en exécutant Google Play une fois. Lorsque vous effacez des données de Google Play, vous oubliez que vous avez acheté Android.test.purchased.

46
Robert

Veuillez vérifier que base64EncodedPublicKey et celui de la Play Developer Console sont égaux . Une fois que vous avez téléchargé à nouveau le fichier APK dans Developer Console , la clé publique peut changer, si c'est le cas, mettez à jour votre base64EncodedPublicKey.

18
user2032617

Vous pouvez ignorer le processus de vérification pour ces ID de produit "Android.test. *". Si vous utilisez l'exemple de code de l'exemple TrivialDrive, ouvrez IabHelper.Java, recherchez le code de ligne suivant, remplacez-le.

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

dans

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

C'est inoffensif, même si vous avez oublié d'annuler le code. Vous pouvez donc continuer à tester l'étape suivante du flux de travail.

9
douyw

L'erreur est due à une clé de licence incorrecte. Peut-être que la clé de licence provient probablement de votre autre application.

La solution consiste à utiliser la clé de licence appropriée fournie par:

Console de jeu> Application> Outils de développement> Licence et facturation intégrée

3
Nabin Bhandari

This Solution a fonctionné pour moi. J'ai changé la nouvelle méthode verifyPurchase en classe d'achat avec l'ancienne.

2
Khayam Gondal

Ce qui a fonctionné pour moi, lors de l'utilisation de In-App Billing v3 et des classes utilitaires incluses, a été de consommer l'achat test dans l'appel onActivityResult renvoyé. 

Aucun changement à IabHelper, à la sécurité ni à aucune des classes utilitaires de facturation intégrée n'est nécessaire pour éviter cela lors de futurs achats avec test. 

Si vous avez déjà essayé d'acheter le produit à tester et êtes maintenant bloqué dans l'erreur d'échec de la vérification de la signature d'achat, ce que vous êtes probablement depuis que vous recherchez des réponses à cette erreur, vous devez alors: 

  1. apporter les modifications recommandées par GMTDev
  2. lancez l'application pour vous assurer qu'elle consomme le produit
  3. supprimer/annuler les modifications de GMTDev 
  4. implémentez le code ci-dessous dans onActivityResult.

Non seulement cela permet au processus de test d’achat d’être fluide, mais cela devrait également éviter tout problème conflictuel avec le renvoi par iab de l’erreur "Article déjà possédé" lorsqu’on tente de racheter le produit à tester.

Si ceci est appelé depuis un fragment et si onActivityResult de votre fragment n'est pas appelé, assurez-vous d'appeler YourFragmentName.onActivityResult (requestCode, resultCode, data) depuis votre ActivityFragment parent si nécessaire. Appel de startIntentSenderForResult depuis 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();
            }
        }
    }
}

Il ne supprime l'achat que si son sku est "Android.test.purchased"; son utilisation devrait donc être sûre. 

2
lodlock

Couru dans la même affaire (vérification de la signature, et se débarrasser de l'achat du test) aujourd'hui (30 oct. 2018).

Le problème de la signature est probablement dû au fait que ces sku de test ne font pas vraiment partie de votre application et ne portent donc pas sa signature. J'ai ouvert un ticket avec Google, mais je ne suis pas sûr qu'ils puissent résoudre ce problème. La solution de contournement, comme d'autres l'ont fait remarquer, consiste à remplacer le code

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

avec 

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

En ce qui concerne "comment se débarrasser de l'achat d'Android.test.purchased SKU", j'ai constaté qu'un simple redémarrage de l'appareil, suivi d'une attente d'environ une minute et/ou d'un redémarrage de votre application plusieurs fois, l'a corrigé pour moi (c’est-à-dire que je n’avais pas à «consommer» l’achat par code). Je suppose que l'attente est nécessaire pour que le Play Store complète la synchronisation avec les serveurs de Google. (Vous ne savez pas si cela continuera à fonctionner ainsi à l'avenir, mais si cela fonctionne pour vous maintenant, cela pourrait vous aider à aller de l'avant.)

1
Venu G.

La vérification de la signature échoue uniquement pour le produit test par défaut . Une solution rapide:

  • Allez à la classe IabHelper.
  • Inverser les conditions if de Security.verifyPurchase

C'est tout!

N'oubliez pas de revenir sur les modifications lorsque le produit testé est remplacé par le produit réel.

1
Shree Harsha S

Cochez cette réponse :

Le compte principal de votre appareil de test est-il identique à votre Google Jouer au compte développeur?

Sinon, vous ne recevrez pas de signatures sur les réponses statiques Android.test. * sauf si l'application a déjà été publiée sur Play.

Voir le tableau à http://developer.Android.com/guide/market/billing/billing_testing.html#static-responses-table pour l'ensemble des conditions.

Et c'est un commentaire:

Je ne pense pas que les identifiants statiques renvoient plus la signature. Voir https://groups.google.com/d/topic/Android-developers/PCbCJdOl480/discussion

De même, précédemment, l’exemple de code (utilisé par de nombreuses applications volumineuses) de la bibliothèque de facturation de Google Play permettait une signature vide. C'est pourquoi les achats statiques ont fonctionné là-bas.
Mais c’était une faille de sécurité, donc lorsqu’il a été publié , Google a soumis une update .

0
Luten

J'ai le même problème et suis @Deadolus dit basé sur https://www.gaffga.de/implementing-in-app-billing-for-Android/

Le point clé est que nous devons faire en sorte que le SKU soit consommable, même si le résultat de la requête d'inventaire a échoué. Voici l'exemple de comment j'ai fait ça.

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

Remplacez PACKAGE_NAME dans le code ci-dessus par le nom du package de votre application.

0
Plugie

C'est ce qui a fonctionné pour moi: 

  1. Appelez BillingClient.querySkuDetailsAsync pour demander si l'élément est disponible
  2. Attendez SkuDetailsResponseListener.onSkuDetailsResponse
  3. Attendez encore 500ms
  4. Commencez votre achat en utilisant BillingClient.launchBillingFlow ... 

L'étape 3 ne devrait pas être nécessaire car lorsque j'ai reçu onSkuDetailsResponse, tout devrait bien se passer, mais ce n'est pas le cas, j'ai dû attendre un peu. Une fois que cet achat a fonctionné, il ne reste plus "d'erreur d'article non disponible". Voici comment je l'ai testé:

  1. effacer mes données d'application
  2. effacer les données Google Play
  3. lancer l'application
  4. acheter Android.test.purchased
  5. essayez d'acheter mes articles (ça échoue avec l'article non disponible)
  6. utiliser ma solution ci-dessus, cela fonctionne
0
ghostbanana34