Android tail billing exception

I get this exception from crash reports:

java.lang.RuntimeException: Unable to start service com.problemio.BillingService@4132b868 with Intent { act=com.android.vending.billing.PURCHASE_STATE_CHANGED cmp=com.problemio/.BillingService (has extras) }: java.lang.IllegalArgumentException: utils.Base64DecoderException: single trailing character at offset 19 at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:2376) at android.app.ActivityThread.access$1900(ActivityThread.java:123) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1210) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:137) at android.app.ActivityThread.main(ActivityThread.java:4424) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:511) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551) at dalvik.system.NativeStart.main(Native Method) Caused by: java.lang.IllegalArgumentException: utils.Base64DecoderException: single trailing character at offset 19 at utils.Security.generatePublicKey(Security.java:199) at utils.Security.verifyPurchase(Security.java:118) at com.problemio.BillingService.purchaseStateChanged(BillingService.java:545) at com.problemio.BillingService.handleCommand(BillingService.java:421) at com.problemio.BillingService.onStart(BillingService.java:398) at android.app.Service.onStartCommand(Service.java:438) at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:2359) ... 10 more Caused by: utils.Base64DecoderException: single trailing character at offset 19 at utils.Base64.decode(Base64.java:529) at utils.Base64.decode(Base64.java:444) at utils.Base64.decode(Base64.java:390) at utils.Security.generatePublicKey(Security.java:189) ... 16 more java.lang.IllegalArgumentException: utils.Base64DecoderException: single trailing character at offset 19 at utils.Security.generatePublicKey(Security.java:199) at utils.Security.verifyPurchase(Security.java:118) at com.problemio.BillingService.purchaseStateChanged(BillingService.java:545) at com.problemio.BillingService.handleCommand(BillingService.java:421) at com.problemio.BillingService.onStart(BillingService.java:398) at android.app.Service.onStartCommand(Service.java:438) at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:2359) at android.app.ActivityThread.access$1900(ActivityThread.java:123) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1210) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:137) at android.app.ActivityThread.main(ActivityThread.java:4424) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:511) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551) at dalvik.system.NativeStart.main(Native Method) Caused by: utils.Base64DecoderException: single trailing character at offset 19 at utils.Base64.decode(Base64.java:529) at utils.Base64.decode(Base64.java:444) at utils.Base64.decode(Base64.java:390) at utils.Security.generatePublicKey(Security.java:189) ... 16 more utils.Base64DecoderException: single trailing character at offset 19 at utils.Base64.decode(Base64.java:529) at utils.Base64.decode(Base64.java:444) at utils.Base64.decode(Base64.java:390) at utils.Security.generatePublicKey(Security.java:189) at utils.Security.verifyPurchase(Security.java:118) at com.problemio.BillingService.purchaseStateChanged(BillingService.java:545) at com.problemio.BillingService.handleCommand(BillingService.java:421) at com.problemio.BillingService.onStart(BillingService.java:398) at android.app.Service.onStartCommand(Service.java:438) at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:2359) at android.app.ActivityThread.access$1900(ActivityThread.java:123) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1210) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:137) at android.app.ActivityThread.main(ActivityThread.java:4424) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:511) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551) at dalvik.system.NativeStart.main(Native Method) 

but I don’t quite understand what the problem is. Any suggestions?

He points to this method:

  /** * Decodes Base64 content using the supplied decodabet and returns * the decoded byte array. * * @param source the Base64 encoded data * @param off the offset of where to begin decoding * @param len the length of characters to decode * @param decodabet the decodabet for decoding Base64 content * @return decoded data */ public static byte[] decode(byte[] source, int off, int len, byte[] decodabet) throws Base64DecoderException { int len34 = len * 3 / 4; byte[] outBuff = new byte[2 + len34]; // Upper limit on size of output int outBuffPosn = 0; byte[] b4 = new byte[4]; int b4Posn = 0; int i = 0; byte sbiCrop = 0; byte sbiDecode = 0; for (i = 0; i < len; i++) { sbiCrop = (byte) (source[i + off] & 0x7f); // Only the low seven bits sbiDecode = decodabet[sbiCrop]; if (sbiDecode >= WHITE_SPACE_ENC) { // White space Equals sign or better if (sbiDecode >= EQUALS_SIGN_ENC) { // An equals sign (for padding) must not occur at position 0 or 1 // and must be the last byte[s] in the encoded value if (sbiCrop == EQUALS_SIGN) { int bytesLeft = len - i; byte lastByte = (byte) (source[len - 1 + off] & 0x7f); if (b4Posn == 0 || b4Posn == 1) { throw new Base64DecoderException( "invalid padding byte '=' at byte offset " + i); } else if ((b4Posn == 3 && bytesLeft > 2) || (b4Posn == 4 && bytesLeft > 1)) { throw new Base64DecoderException( "padding byte '=' falsely signals end of encoded value " + "at offset " + i); } else if (lastByte != EQUALS_SIGN && lastByte != NEW_LINE) { throw new Base64DecoderException( "encoded value has invalid trailing byte"); } break; } b4[b4Posn++] = sbiCrop; if (b4Posn == 4) { outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet); b4Posn = 0; } } } else { throw new Base64DecoderException("Bad Base64 input character at " + i + ": " + source[i + off] + "(decimal)"); } } // Because web safe encoding allows non padding base64 encodes, we // need to pad the rest of the b4 buffer with equal signs when // b4Posn != 0. There can be at most 2 equal signs at the end of // four characters, so the b4 buffer must have two or three // characters. This also catches the case where the input is // padded with EQUALS_SIGN if (b4Posn != 0) { if (b4Posn == 1) { throw new Base64DecoderException("single trailing character at offset " + (len - 1)); } b4[b4Posn++] = EQUALS_SIGN; outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet); } byte[] out = new byte[outBuffPosn]; System.arraycopy(outBuff, 0, out, 0, outBuffPosn); return out; } 

The error seems to point to these lines in my BillingService.java

 at com.problemio.BillingService.purchaseStateChanged(BillingService.java:585) at com.problemio.BillingService.handleCommand(BillingService.java:461) at com.problemio.BillingService.onStart(BillingService.java:438) 

The line at 585 is the line

 purchases = Security.verifyPurchase(signedData, signature); 

in this method:

 private void purchaseStateChanged(int startId, String signedData, String signature) { ArrayList<Security.VerifiedPurchase> purchases; purchases = Security.verifyPurchase(signedData, signature); if (purchases == null) { return; } 

and line 461 is the line:

 purchaseStateChanged(startId, signedData, signature); 

in this method:

 public void handleCommand(Intent intent, int startId) { String action = intent.getAction(); if (Consts.DEBUG) { Log.i(TAG, "handleCommand() action: " + action); } if (Consts.ACTION_CONFIRM_NOTIFICATION.equals(action)) { String[] notifyIds = intent.getStringArrayExtra(Consts.NOTIFICATION_ID); confirmNotifications(startId, notifyIds); } else if (Consts.ACTION_GET_PURCHASE_INFORMATION.equals(action)) { String notifyId = intent.getStringExtra(Consts.NOTIFICATION_ID); getPurchaseInformation(startId, new String[] { notifyId }); } else if (Consts.ACTION_PURCHASE_STATE_CHANGED.equals(action)) { String signedData = intent.getStringExtra(Consts.INAPP_SIGNED_DATA); String signature = intent.getStringExtra(Consts.INAPP_SIGNATURE); purchaseStateChanged(startId, signedData, signature); } else if (Consts.ACTION_RESPONSE_CODE.equals(action)) { long requestId = intent.getLongExtra(Consts.INAPP_REQUEST_ID, -1); int responseCodeIndex = intent.getIntExtra(Consts.INAPP_RESPONSE_CODE, ResponseCode.RESULT_ERROR.ordinal()); ResponseCode responseCode = ResponseCode.valueOf(responseCodeIndex); checkResponseCode(requestId, responseCode); } } 

and line 438 is the line:

 handleCommand(intent, startId); 

in this method:

 @Override public void onStart(Intent intent, int startId) { handleCommand(intent, startId); } 

Thanks!

+8
android exception android-billing
source share
3 answers

Does this happen all the time or just once / several times? The signature you receive purchaseStateChanged(startId, signedData, signature) is also encoded by Base64, and the error indicates that the encoding is bad. If this happens only once or twice, it could be a failure on the Google Play IAP servers.

+4
source share

For me, this happened because I used the wrong public key! Make sure that the public key you are using is the public key that you received in the market.

0
source share

I saw the error above when the IabHelper constructor (Context ctx, String base64PublicKey)

Invalid key specified.
Decision:
to go to the Google Play Developer Console web page. select application
then in the "Services and API" section you will see "Your license key for this application"
use this line.


Another solution is to go to the Security.java file and make a few changes. See the lines where I have the comment "// ADD_THIS_LINE"

  public static boolean verifyPurchase(String base64PublicKey, String signedData, String signature) { if (signedData == null) { Log.e(TAG, "data is null"); return false; } boolean verified = false; if (!TextUtils.isEmpty(signature)) { PublicKey key = Security.generatePublicKey(base64PublicKey); if( key != null) // ADD_THIS_LINE verified = Security.verify(key, signedData, signature); if (!verified) { Log.w(TAG, "signature does not match data."); return false; } } return true; } //////////////////////////////////////////////////////////////////////// // : : /////////////////////////////////////////////////////////////////////// public static PublicKey generatePublicKey(String encodedPublicKey) { try { byte[] decodedKey = Base64.decode(encodedPublicKey); KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM); return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey)); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } catch (InvalidKeySpecException e) { Log.e(TAG, "Invalid key specification."); throw new IllegalArgumentException(e); } catch (Base64DecoderException e) { Log.e(TAG, "Base64 decoding failed."); return null; // ADD_THIS_LINE // COMMENT_OUT_THIS_LINE: throw new IllegalArgumentException(e); } } ////////////////////////////////////////////////////////////// 
-2
source share

All Articles