Android in app purchase: Signature verification failed
Asked Answered
K

14

71

I have tried for several days to solve this problem, using the Dungeons demo code that comes with the SDK. I've tried to Google for an answer but can't find one.

  • In the Dungeons demo, I passed my public key from the dev console.
  • Signed the apk and uploaded to console without publish.
  • Testing for both android.test.purchased & product list created on console with published for subscription (The main feature I want for my app).

But still I get an error of Signature verification failed and then the signature does not match data. How can I solve this?

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;
}
Kaliski answered 30/1, 2013 at 9:36 Comment(0)
G
150

This problem is still going on in the current Google billing version. Basically the android.test.purchased is broken; After you buy android.test.purchased the verifyPurchase function in Security.java will always fail and the QueryInventoryFinishedListener will stop at the line if (result.isFailure()); this is because the android.test.purchased item always fails the TextUtils.isEmpty(signature) check in Security.java as it is not a real item and has no signature returned by the server.

My advice (from lack of any other solution) is to NEVER use "android.test.purchased". There are various code tweaks on the net but none of them work 100%.

If you have used the android.test.purchased then one way to get rid of the error is to do the following:-

  1. Edit Security.java and change the "return false" line in the verifyPurchase to "return true" - this is temporary, we'll be putting it back in a minute.
  2. In your QueryInventoryFinishedListener, after the "if (result.isFailure()) {...}" lines add the following to consume and get rid of your never ending android.test.purchased item:

    if (inventory.hasPurchase(SKU_ANDROID_TEST_PURCHASE_GOOD)) {  
       mHelper.consumeAsync(inventory.getPurchase(SKU_ANDROID_TEST_PURCHASE_GOOD),null);
       }
    
  3. Run your app so the consunmeAsync happens, this gets rid of the "android.test.purchased" item on the server.

  4. Remove the consumeAsync code (or comment it out).
  5. Back in the Security.java, change the "return true" back to "return false".

Your QueryInventoryFinishedListener will no longer error on the verify, everything is back to "normal" (if you can call it that). Remember - don't bother using android.test.purchased again as it will just cause this error again... it's broke! The only real way to test your purchasing it to upload an APK, wait for it to appear, and then test it (the same APK) on your device with logging enabled.

Gunk answered 28/2, 2014 at 7:18 Comment(11)
I had the same problem, but found an easier solution to consume the android.test.purchase at this site, he there creates the purchase statically to consume it later: ` 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\"}", "");`Anus
Google's breathtaking incompetence never ceases to surprise me.Overcurious
Google should definitely put some hint this into the docs indicating that the static responses don't fully work!Memorialist
Thanks @GTMDev. This is still broken in 2015 and your answer helped me get back to normal. Also, for future readers, the value of the SKU_ANDROID_TEST_PURCHASE_GOOD constants above should be "android.test.purchased".Elation
Yes, the problem still occurs. After I bought android.test.purchased I start getting the error on quering the inventory. I just want to add that it is possible to fix your phone by just clearing data of Google Play Store application and running it one time. When you clear data of Google Play it forgets that you bought android.test.purchased.Wellesley
@Wellesley You should add that as an answer. It's the simplest solution.Danedanegeld
This problem is still there in 2016! It's amazing how developing for Android is a total PITA compared to iOS.Stibnite
Wait, in 2016 this still seems to be an issue. How do people test In-App Purchases then??Abaddon
If we can't test the signature verification part using the verifyPurchase method because we get an empty INAPP_DATA_SIGNATURE back while using android.test.purchased, how are we supposed to test the signature verification code then (without having to upload an app to production and perform a real purchase)?Compassionate
This happens, if you use Trivial Drive's code, and this answer helps to solve it tooNuts
Its 2019 and this issue is still not resolved - unbelievableFreitas
W
50

Yes, the problem still occurs. After I bought android.test.purchased I start getting the error on quering the inventory. It is possible to fix your phone by just clearing data of Google Play Store application and running Google Play one time. When you clear data of Google Play it forgets that you bought android.test.purchased

Wellesley answered 14/2, 2015 at 12:0 Comment(7)
It works for me. Thanks for that very simple solution.Quicksilver
This is a much simpler solution and less prone to errors since you can't accidentally forget to delete code after you consume the test purchase...Congruity
10/16/2015 and this still occurs.Np
Still happening 21/10.15 - android.test.purchased is the reason for the hours sorting this out? unbelievable. Thanks for saving me from wasting more than a day, cheersNeb
This works well. Also I notice that if the sales window is cancelled before the buy button is pressed there is no need to clear data in Google Play app each time, ie if you just want to test that store is connectedNeb
This is definitely the best and most simple answer.Kaylenekayley
Unfortunately this still happens in 2016. Is there any other fix for this from Google?Abaddon
F
23

Please check that base64EncodedPublicKey and the one from the Play Developer Console are equal. Once you re-upload the APK in the Developer Console, the public key may change, if so update your base64EncodedPublicKey.

Flowing answered 1/2, 2013 at 13:11 Comment(6)
I'm getting the same error and my keys are exactly the same. There has to be something else going on.Osmic
I had the same issue and indeed had a mismatch for the public key. However, the public key does not seem to change each time you reupload an APK (thankfully!).Updraft
@Jean-PhilippePellet Same here! I don't know when the key will be changed.Vacuole
"Once you re-upload the APK in the Developer Console, the public key may change" do you say that every time I upload new version to play store I have to change base64EncodedPublicKey? This would be really ridiculous.Vociferant
No, as others have stated in this question, the issue is likely due to android.test.purchased SKU. Not a key mismatch.Abaddon
Well, it's true. The key has changed for me too, I had a working public key and after some releases IAP stopped working, the key was different from the one I had in the app. Somehow it changed.Darwindarwinian
N
9

You can skip the verifying process for those "android.test.*" product ids. If you are using the sample code from the TrivialDrive example, open IabHelper.java, find the following line code, change it from

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

into

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

It's harmless, even if you forgot to rollback the code. So, you can continue to test the further workflow step.

Nesline answered 1/6, 2016 at 12:35 Comment(0)
B
8

Based on GMTDev's answer, this is what I do in order to fix the testing issues when consuming products in the simplest possible way. In Security.java, replace the verifyPurchase() method with this:

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

I only modified one line (see comment), and this way you can keep the code like that for debugging and still publish your release versions safely.

Bonneau answered 7/10, 2014 at 15:5 Comment(1)
Hi, I have a question about in app billing: #36298820Bot
N
5

The error is caused because of the wrong license key. Maybe the license key is probably from your another app.

The solution is to use the proper license key from :

Play console > App > Development Tools > Licensing & in-app billing

Nowak answered 10/10, 2017 at 7:18 Comment(1)
Exactly my problem. Decided to share the license code from my existing app with my new app and left the old license key in there, giving me the signature verification failed error.Camillecamilo
S
3

What worked for me, while using In-app Billing v3 and the included utility classes, was consuming the test purchase within the returned onActivityResult call.

No changes to IabHelper, Security, or any of the In-app Billing util classes are needed to avoid this for future test purchases.

If you have already tried purchasing the test product and are now stuck on the purchase signature verification failed error, which you likely are since you are looking up answers for this error, then you should:

  1. make the changes that GMTDev recommended
  2. run the app to ensure that it consumes the product
  3. remove/undo GMTDev's changes
  4. implement the code below within onActivityResult.

Not only does this allow for the purchase testing process to be fluid but this should also avoid any conflicting issues with iab returning the " Item Already Owned " error when attempting to repurchase the test product.

If this is being called from within a fragment and your fragment's onActivityResult isn't being called then be sure to call YourFragmentName.onActivityResult(requestCode, resultCode, data) from your parent ActivityFragment if necessary. This is explained in more detail in 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();
            }
        }
    }
}

It will only remove the purchase if it's sku is "android.test.purchased" so it should be safe to use.

Scorekeeper answered 30/3, 2015 at 14:4 Comment(1)
Confirmed IAB will NOT work properly if you have omitted the onActivityResult() callback.Exhortative
H
2

This Solution worked for me. I changed the new verifyPurchase method in purchase class with old one.

Highgrade answered 11/4, 2014 at 8:57 Comment(0)
Q
1

Signature verification fails only for the default test product. A quick fix :

  • Goto IabHelper class.
  • Invert the if conditions of Security.verifyPurchase.

Thats it!

Remember to revert the changes when test product is replaced by actual product

Quita answered 6/1, 2016 at 17:54 Comment(0)
N
1

Ran into the same issue (signature verification, and getting rid of the test purchase) today (Oct 30, 2018).

The signature issue is probably being caused by the fact that these test sku's are not really part of your app, and are thus do not have your app's signature. I did open a ticket with Google, but not sure if they can fix this. The workaround, as others pointed out, is to replace the code

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

with

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

Regarding "how to get rid of the purchase of android.test.purchased SKU", I found that a simple reboot of the device, followed by waiting for a minute or so and/or re-starting your app a couple of times fixed it for me (i.e. I didn't have to 'consume' the purchase by code). I am guessing that the wait is needed so that the Play store completes syncing with Google's servers. (Not sure if this will continue to work this way in the future, but if it works for you now, this might help you move forward.)

Nilsanilsen answered 30/10, 2018 at 21:58 Comment(0)
E
0

Check this answer:

Is the primary account on your test device the same as your Google Play developer account?

If not you won't get signatures on the android.test.* static responses unless the app has been published on Play before.

See the table at http://developer.android.com/guide/market/billing/billing_testing.html#static-responses-table for the full set of conditions.

And it's comment:

I don't think the static ids return signature anymore. See https://groups.google.com/d/topic/android-developers/PCbCJdOl480/discussion

Also, previously the sample code (used by many big apps) from Google Play Billing Library allowed an empty signature. That's why it's static purchases worked there.
But it was a security hole, so when it was published, Google submitted an update.

Ebullition answered 6/12, 2013 at 11:0 Comment(3)
I have a question on in app billing:#36298820Bot
Sorry for asking here @Luten, but do you have any experience on how to handle VAT tax with In-app billing, and which countries it is done automatically by google, and which I have to manually report/pay VAT tax to? see #36507335Innumerable
@VidarVestnes, sorry, can't help you with this.Ebullition
S
0

I have the same problem and follow @Deadolus said based on https://www.gaffga.de/implementing-in-app-billing-for-android/

The key point is we need to make the SKU is consumable even the inventory query result is failed. Below is the sample how i did that.

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

Replace PACKAGE_NAME in the code above with the package name of your app.

Saltish answered 10/5, 2015 at 9:54 Comment(0)
L
0

This is what worked for me:

  1. Call BillingClient.querySkuDetailsAsync to query if item if available
  2. Wait for SkuDetailsResponseListener.onSkuDetailsResponse
  3. Wait another 500ms
  4. Start purchase using BillingClient.launchBillingFlow...

The step 3 shouldn't be necessary because when I received onSkuDetailsResponse it should be OK but it isn't, had to wait a little bit. After that purchase works, no more "Item not available error". This is how I tested it:

  1. clear my app data
  2. clear Google Play data
  3. run app
  4. purchase android.test.purchased
  5. try to purchase my items (it fails with item not available)
  6. use my solution above, it works
Lichen answered 29/6, 2018 at 9:30 Comment(0)
F
-1

For Cordova and Hybrid apps you need to use this.iap.subscribe(this.productId) method to subscription InAppPurchase.

Following are the code working fine for me:

 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);
                });
        })
    }
Filaria answered 31/1, 2019 at 10:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.