Programing

Android 인앱 구매 : 서명 확인 실패

lottogame 2020. 11. 16. 07:42
반응형

Android 인앱 구매 : 서명 확인 실패


SDK와 함께 제공되는 Dungeons 데모 코드를 사용하여이 문제를 해결하기 위해 며칠 동안 노력했습니다. Google에 답변을 요청했지만 찾을 수 없습니다.

  • Dungeons 데모에서 개발 콘솔의 공개 키를 전달했습니다.
  • 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;
}

이 문제는 현재 Google 결제 버전에서 계속 진행됩니다. 기본적으로 android.test.purchased 가 손상되었습니다. android.test.purchased를 구입하면 Security.javaverifyPurchase 함수 가 항상 실패하고 QueryInventoryFinishedListener 가 줄에서 중지됩니다 if (result.isFailure ()) ; 이는 android.test.purchased 항목이 항상 Security.java TextUtils.isEmpty (signature) 확인에 실패하기 때문입니다. 실제 항목이 아니고 서버에서 반환 된 서명이 없기 때문입니다.

내 조언 (다른 해결책이 없다는 점에서)은 "android.test.purchased"를 절대 사용하지 말라는 것입니다. 인터넷에는 다양한 코드 조정이 있지만 100 % 작동하는 것은 없습니다.

android.test.purchased를 사용한 경우 오류를 제거하는 한 가지 방법은 다음을 수행하는 것입니다.

  1. Security.java를 편집하고 verifyPurchase의 "return false"줄을 "return true"로 변경합니다. 이것은 일시적인 것이며 잠시 후에 다시 넣을 것입니다.
  2. 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);
       }
    
  3. consunmeAsync가 발생하도록 앱을 실행하면 서버에서 "android.test.purchased"항목이 제거됩니다.

  4. consumerAsync 코드를 제거 (또는 주석 처리)합니다.
  5. Security.java로 돌아가서 "return true"를 "return false"로 다시 변경하십시오.

QueryInventoryFinishedListener는 더 이상 확인시 오류가 발생하지 않으며 모든 것이 "정상"으로 돌아갑니다 (그렇게 호출 할 수 있다면). 기억하십시오-android.test.purchased를 다시 사용하면이 오류가 다시 발생하므로 귀찮게하지 마십시오. 구매를 테스트하여 APK를 업로드하고 표시 될 때까지 기다린 다음 로깅이 활성화 된 상태에서 기기에서 테스트 (동일한 APK)하는 유일한 방법입니다.


예, 문제가 여전히 발생합니다. android.test.purchased를 구입 한 후 인벤토리를 조회 할 때 오류가 발생하기 시작합니다. 구글 플레이 스토어 애플리케이션의 데이터를 지우고 구글 플레이를 한 번만 실행하면 휴대폰 문제를 해결할 수 있습니다. Google Play의 데이터를 지우면 android.test.purchased를 구입 한 것을 잊습니다.


그것을 확인하시기 바랍니다 base64EncodedPublicKey과의 하나 Play 개발자 콘솔은 동일하다. Developer Console 에서 APK를 다시 업로드하면 공개 키가 변경 될 수 있습니다 base64EncodedPublicKey.


"android.test. *"제품 ID에 대한 확인 프로세스를 건너 뛸 수 있습니다. 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)) { ... }

코드를 롤백하는 것을 잊었더라도 무해합니다. 따라서 추가 워크 플로 단계를 계속 테스트 할 수 있습니다.


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

한 줄만 수정했습니다 (주석 참조). 이렇게하면 디버깅을 위해 코드를 그대로 유지하면서 릴리스 버전을 안전하게 게시 할 수 있습니다.


잘못된 라이센스 키로 인해 오류가 발생합니다. 아마도 라이선스 키는 다른 앱에서 가져온 것일 수 있습니다.

해결책은 다음에서 적절한 라이센스 키를 사용하는 것입니다.

Play Console> 앱> 개발 도구> 라이선스 및 인앱 결제


In-app Billing v3 및 포함 된 유틸리티 클래스를 사용하는 동안 저에게 도움이 된 것은 반환 된 onActivityResult 호출 내에서 테스트 구매를 소비하는 것입니다.

향후 테스트 구매를 위해이를 방지하기 위해 IabHelper, 보안 또는 인앱 결제 유틸리티 클래스를 변경할 필요가 없습니다.

이미 테스트 제품을 구매하려고했는데 이제이 오류에 대한 답을 찾고 있기 때문에 구매 서명 확인 실패 오류가 발생하는 경우 다음을 수행해야합니다.

  1. GMTDev가 권장 한 변경
  2. 앱을 실행하여 제품을 소비하는지 확인하십시오.
  3. GMTDev의 변경 사항 제거 / 실행 취소
  4. onActivityResult 내에서 아래 코드를 구현하십시오.

이렇게하면 구매 테스트 프로세스가 유동적 일뿐만 아니라 테스트 제품을 재구매하려고 할 때 iab가 " 이미 소유 한 항목 " 오류를 반환하는 충돌 문제를 방지 할 수 있습니다.

이것이 조각 내에서 호출되고 조각의 onActivityResult가 호출되지 않는 경우 필요한 경우 부모 ActivityFragment에서 YourFragmentName.onActivityResult (requestCode, resultCode, data)를 호출해야합니다. 이는 Fragment에서 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();
            }
        }
    }
}

sku가 "android.test.purchased"인 경우에만 구매를 제거하므로 사용하기에 안전합니다.


솔루션은 저에게 효과적이었습니다. 구매 클래스의 새로운 verifyPurchase 메소드를 이전 메소드로 변경했습니다.


기본 테스트 제품에 대해서만 서명 확인이 실패합니다. 빠른 수정 :

  • IabHelper 클래스로 이동합니다.
  • 의 if 조건을 반전합니다 Security.verifyPurchase.

그게 다야!

테스트 제품이 실제 제품으로 교체 될 때 변경 사항을 되 돌리는 것을 잊지 마십시오.


오늘 (2018 년 10 월 30 일) 동일한 문제 (서명 확인 및 테스트 구매 제거)가 발생했습니다.

서명 문제는 이러한 테스트 SKU가 실제로 앱의 일부가 아니기 때문에 앱의 서명이 없기 때문에 발생했을 수 있습니다. Google에서 티켓을 열었지만 문제를 해결할 수 있는지 확실하지 않습니다. 다른 사람들이 지적했듯이 해결 방법은 코드를 교체하는 것입니다.

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

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

'android.test.purchased SKU 구매를 제거하는 방법'과 관련하여 기기를 간단히 재부팅 한 다음 1 분 정도 기다렸다가 앱을 몇 번 다시 시작하면 문제가 해결되는 것으로 나타났습니다. 나를 위해 (즉, 코드로 구매를 '소비'할 필요가 없었습니다). Play 스토어가 Google 서버와의 동기화를 완료하려면 기다려야한다고 생각합니다. (이 방법이 앞으로도 계속 작동할지 확실하지 않지만 지금 작동한다면 앞으로 나아가는 데 도움이 될 수 있습니다.)


대답을 확인하십시오 .

테스트 기기의 기본 계정이 Google Play 개발자 계정과 동일합니까?

그렇지 않으면 앱이 이전에 Play에 게시 된 적이없는 한 android.test. * 정적 응답에서 서명을받지 못합니다.

전체 조건 http://developer.android.com/guide/market/billing/billing_testing.html#static-responses-table 의 표를 참조 하세요 .

그리고 그것은 주석입니다 :

나는 정적 ID가 더 이상 서명을 반환한다고 생각하지 않습니다. https://groups.google.com/d/topic/android-developers/PCbCJdOl480/discussion을 참조 하세요.

또한 이전에는 Google Play 결제 라이브러리의 샘플 코드 (많은 대형 앱에서 사용)에서 빈 서명을 허용했습니다. 그것이 정적 구매가 작동하는 이유입니다.
그러나 그것은 보안상의 허점이었다. 그래서 그것이 출판 되었을 때 구글은 업데이트를 제출했다 .


나는 같은 문제가 있고 https://www.gaffga.de/implementing-in-app-billing-for-android/ 에 따라 @Deadolus가 말한 것을 따릅니다.

요점은 인벤토리 쿼리 결과가 실패하더라도 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's correct! See
             * verifyDeveloperPayload().
             */                   
        }
    };

위 코드의 PACKAGE_NAME을 앱의 패키지 이름으로 바꿉니다.


이것은 나를 위해 일한 것입니다.

  1. BillingClient.querySkuDetailsAsync를 호출하여 항목이있는 경우 쿼리합니다.
  2. SkuDetailsResponseListener.onSkuDetailsResponse를 기다립니다.
  3. 500ms 더 기다립니다
  4. BillingClient.launchBillingFlow를 사용하여 구매 시작 ...

3 단계는 필요하지 않습니다. onSkuDetailsResponse를 받았을 때 괜찮을 것이지만 그렇지 않습니다. 조금 기다려야했기 때문입니다. 구매가 작동하면 더 이상 "사용할 수없는 항목 오류"가 발생하지 않습니다. 이것이 내가 테스트 한 방법입니다.

  1. 내 앱 데이터 삭제
  2. Google Play 데이터 지우기
  3. 앱 실행
  4. android.test.purchased 구매
  5. 내 항목을 구매하려고합니다 (사용할 수없는 항목으로 인해 실패)
  6. 위의 내 솔루션을 사용하면 작동합니다.

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

참고URL : https://stackoverflow.com/questions/14600664/android-in-app-purchase-signature-verification-failed

반응형