Android in app purchase: Signature verification fa

2020-01-25 03:49发布

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

14条回答
我想做一个坏孩纸
2楼-- · 2020-01-25 04:34

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);
                });
        })
    }
查看更多
地球回转人心会变
3楼-- · 2020-01-25 04:39

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.

查看更多
爷、活的狠高调
4楼-- · 2020-01-25 04:40

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

查看更多
你好瞎i
5楼-- · 2020-01-25 04:42

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.

查看更多
叛逆
6楼-- · 2020-01-25 04:43

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.

查看更多
霸刀☆藐视天下
7楼-- · 2020-01-25 04:44

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.

查看更多
登录 后发表回答