【问题标题】:Android billing exception about a trailing character关于尾随字符的 Android 计费异常
【发布时间】:2012-07-02 11:11:36
【问题描述】:

我从我的崩溃报告中得到了这个异常:

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)

但我不太明白问题出在哪里。有什么建议吗?

它指向这个方法:

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

错误似乎指向我的 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)

585上的那条线就是那条线

purchases = Security.verifyPurchase(signedData, signature);

在这个方法中:

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

第 461 行就是这一行:

purchaseStateChanged(startId, signedData, signature);

在这个方法中:

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

第 438 行就是这一行:

handleCommand(intent, startId);

在这个方法中:

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

谢谢!

【问题讨论】:

  • 问题不在于您上面包含的代码,而在于您的(Base64 编码)公钥。我不是 Android 开发人员,但是: utils.Security 是第三方计费库的一部分吗?它传递给generatePublicKey 的公钥是什么?
  • @MichaelPetrotta Android 提供了一个公钥以粘贴到代码中。我粘贴并检查没有空格。 utils.Security 是 Android 代码的一部分,用于处理应用内计费内容。
  • 您的 Base64 公钥不正确。试着自己解码看看。除了嵌入的空格之外,Base64 字符串还有很多可能出错的地方。
  • @MichaelPetrotta 有趣...我如何解码它? :) 问题是我将它粘贴在那里没有解码.....也许这就是问题 - 也许我应该自己编码并将编码的字符串粘贴在那里?
  • 也许在您使用密钥的地方发布代码。我为utils.Security.verifyPurchase 找到的示例代码将Base64 字符串传递给Security.generatePublicKey。如果这与您正在做的事情相匹配,那么您肯定需要在将 PK 放入课程之前对其进行 Base64 编码。

标签: android exception android-billing


【解决方案1】:

对我来说,这是因为我使用了错误的公钥! 确保您使用的公钥是您从市场上获得的公钥。

【讨论】:

    【解决方案2】:

    我在构造函数时看到了上面的错误
    IabHelper(Context ctx, String base64PublicKey)

    获得了不正确的密钥。
    解决办法是:
    转到 Google Play、开发者控制台网页。
    选择应用程序,
    然后在“服务和 API”下,您会看到“您的此应用程序的许可证密钥”
    使用那个字符串。


    另一个解决方案是进入 Security.java 文件并进行一些更改。查看我有评论“// 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);
        }
    }
    
    
    //////////////////////////////////////////////////////////////
    

    【讨论】:

    • 如果您没有密钥,您对添加的行所做的所有操作都是跳过签名检查,而是让它静默失败。
    【解决方案3】:

    这种情况是一直发生还是只发生一次/几次?您得到的签名 purchaseStateChanged(startId, signedData, signature) 也是 Base64 编码的,错误表示编码错误。如果它只发生一次或两次,则可能是 Google Play IAP 服务器出现故障。

    【讨论】:

      猜你喜欢
      • 2012-12-17
      • 2023-04-09
      • 2012-11-28
      • 2016-09-20
      • 2011-08-15
      • 2015-05-25
      • 1970-01-01
      • 1970-01-01
      • 2017-07-18
      相关资源
      最近更新 更多