Android в покупке приложения: проверка подписи не выполнена

Я попытался в течение нескольких дней решить эту проблему, используя демо-код Dungeons, который поставляется с SDK. Я попробовал Google для ответа, но не могу найти его.

  • В демо Dungeons я передал свой открытый ключ из консоли dev.
  • Подписан 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;
}

Ответ 1

Эта проблема все еще продолжается в текущей платежной версии Google. В основном android.test.purchased не работает; После покупки android.test.purchased функция verifyPurchase в Security.java всегда будет терпеть неудачу, а QueryInventoryFinishedListener остановится в строке if (result.isFailure()); это потому, что элемент android.test.purchased всегда не выполняет проверку TextUtils.isEmpty(подпись) в Security.java, поскольку он не является реальным элементом и не имеет возвращенной подписи сервером.

Мой совет (из-за отсутствия какого-либо другого решения) должен НИКОГДА не использовать "android.test.purchased". В сети есть различные настройки кода, но ни один из них не работает на 100%.

Если вы использовали android.test.purchased, то один из способов избавиться от ошибки состоит в следующем: -

  • Измените Security.java и измените строку "return false" в verifyPurchase на "return true" - это временно, мы вернем ее через минуту.
  • В вашем 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);
       }
    
  • Запустите приложение, чтобы произошло consunmeAsync, это избавит вас от "android.test.purchased" на сервере.

  • Удалить код consumeAsync (или прокомментировать его).
  • Вернитесь в Security.java, измените значение "return true" на "return false".

В QueryInventoryFinishedListener больше не будет ошибок при проверке, все возвращается к "нормальному" (если вы можете это назвать). Помните - не беспокойтесь, используя android.test.purchased снова, так как это просто вызовет эту ошибку снова... она сломалась! Единственный реальный способ проверить его покупку, чтобы загрузить APK, дождаться его появления, а затем протестировать его (тот же APK) на вашем устройстве с включенным протоколированием.

Ответ 2

Да, проблема все еще возникает. После того, как я купил android.test.purchased, я начал получать ошибку при запросе инвентаря. Вы можете исправить свой телефон, просто очистив данные приложения Google Play Store и запустив Google Play один раз. Когда вы очищаете данные Google Play, они забывают, что вы купили android.test.purchased

Ответ 3

Пожалуйста, проверьте, что base64EncodedPublicKey и один из Play Developer Console равны. После повторной загрузки APK в консоли разработчика открытый ключ может измениться, если обновить ваш base64EncodedPublicKey.

Ответ 4

Вы можете пропустить процесс проверки этих идентификаторов продуктов "android.test. *". Если вы используете пример кода из примера 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)) { ... }

Это безобидно, даже если вы забыли отменить код. Таким образом, вы можете продолжить проверку дальнейшего шага рабочего процесса.

Ответ 5

Основываясь на ответе 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);
}

Я только изменил одну строку (см. комментарий), и таким образом вы можете сохранить такой код для отладки и публиковать свои версии без изменений.

Ответ 6

Ошибка вызвана неправильным лицензионным ключом. Возможно, лицензионный ключ, вероятно, из вашего другого приложения.

Решение состоит в том, чтобы использовать правильный лицензионный ключ:

Play console > App > Средства разработки > Лицензирование и выставление счетов в приложении

Ответ 7

Что сработало для меня, используя In-app Billing v3 и включенные классы утилиты, потребляло тестовую покупку в возвращенном вызове onActivityResult.

Никаких изменений в IabHelper, Security или любых классах использования In-app Billing необходимы, чтобы избежать этого для будущих пробных покупок.

Если вы уже попробовали приобрести тестовый продукт и теперь застряли на ошибке проверки подлинности подписи на покупку, которая, скорее всего, с тех пор, как вы ищете ответы на эту ошибку, вам необходимо:

  • внести изменения, рекомендованные GMTDev
  • запустите приложение, чтобы обеспечить его использование
  • удалить/отменить изменения GMTDev
  • реализовать код ниже внутри onActivityResult.

Это не только означает, что процесс тестирования покупки является текучим, но также следует избегать любых противоречащих друг другу проблем, когда iab возвращает ошибку Элемент уже имеющейся "при попытке выкупа тестового продукта.

Если это вызвано изнутри фрагмента, и ваш фрагмент onActivityResult не вызывается, тогда обязательно вызовите YourFragmentName.onActivityResult(requestCode, resultCode, data) из родительского ActivityFragment, если это необходимо. Это более подробно объясняется в вызове 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();
            }
        }
    }
}

Он удалит только покупку, если она "isroid.test.purchased", поэтому она должна быть безопасной в использовании.

Ответ 9

Проверка подписи завершается только для тестового продукта по умолчанию. Быстрое исправление:

  • Перейти к классу IabHelper.
  • Инвертировать условия if Security.verifyPurchase.

Вот оно!

Не забудьте вернуть изменения при замене тестового продукта на фактический продукт

Ответ 10

Сегодня столкнулся с той же проблемой (проверка подписи и избавление от тестовой покупки) (30 октября 2018 г.).

Вероятно, проблема с подписью вызвана тем фактом, что эти тестовые сценарии на самом деле не являются частью вашего приложения и, следовательно, не имеют подписи вашего приложения. Я открыл билет в Google, но не уверен, что они смогут это исправить. Обходной путь, как отметили другие, заключается в замене кода

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

с

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

Относительно "как избавиться от покупки android.test.purchased SKU", я обнаружил, что простая перезагрузка устройства с последующим ожиданием в течение минуты или около того и/или повторным запуском приложения несколько раз исправляла его для меня (то есть я не должен был "потреблять" покупку по коду). Я предполагаю, что нужно подождать, чтобы магазин Play завершил синхронизацию с серверами Google. (Не уверен, будет ли это продолжать работать таким же образом в будущем, но если это работает для вас сейчас, это может помочь вам двигаться вперед.)

Ответ 11

Отметьте ответ:

Является ли основная учетная запись вашего тестового устройства такой же, как ваша Google? Играйте в аккаунт разработчика?

Если нет, вы не получите подписи в android.test. * static response если приложение не было опубликовано в Play раньше.

См. таблицу в http://developer.android.com/guide/market/billing/billing_testing.html#static-responses-tableдля полного набора условий.

И это комментарий:

Я не думаю, что статические идентификаторы возвращают подпись. Видеть https://groups.google.com/d/topic/android-developers/PCbCJdOl480/discussion

Кроме того, ранее примерный код (используемый многими большими приложениями) из Библиотеки биллинга Google Play допускал пустую подпись. Вот почему там работали статические покупки.
Но это была дыра в безопасности, поэтому, когда она была опубликована Google отправил обновление.

Ответ 12

У меня такая же проблема и следуйте @Deadolus на основе https://www.gaffga.de/implementing-in-app-billing-for-android/

Ключевым моментом является то, что нам нужно сделать 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 correct! See
             * verifyDeveloperPayload().
             */                   
        }
    };

Замените PACKAGE_NAME в приведенном выше коде с именем пакета вашего приложения.

Ответ 13

Вот что сработало для меня:

  1. Вызовите BillingClient.querySkuDetailsAsync, чтобы запросить, если элемент доступен
  2. Ждите SkuDetailsResponseListener.onSkuDetailsResponse
  3. Подождите еще 500 мс
  4. Начните покупку, используя BillingClient.launchBillingFlow...

Шаг 3 не должен быть необходимым, потому что когда я получил onSkuDetailsResponse, все должно быть в порядке, но это не так, пришлось немного подождать. После этого покупка работает, больше нет "Элемент недоступен, ошибка". Вот как я это проверил:

  1. очистить данные моего приложения
  2. очистить данные Google Play
  3. запустить приложение
  4. покупка android.test.purchased
  5. попытаться купить мои предметы (не получится, если предмет недоступен)
  6. используйте мое решение выше, оно работает

Ответ 14

Я копировал лицензионный ключ из playstore и вставлял его в свой исходный код между двойными кавычками.

Android Studio автоматически помещает символы escape-последовательности при вставке строки между двойными кавычками.

Таким образом, решение было бы сначала вставить лицензионный ключ, а затем добавить двойные кавычки вокруг него

Ответ 15

Для приложений Cordova и Hybrid необходимо использовать метод this.iap.subscribe(this.productId) для подписки InAppPurchase.

Ниже код работает нормально для меня:

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