由一次Base64编码异常引起的小bug

base64是一种基本的加密算法,在Java中可以使用java自带的base64编码,也可以用apache 的commons-codec包。最近在使用commons-codec 1.10 版本能正常解密微信的消息,升级为1.13后出现了不能正常decode,出现异常

1
2
3
4
5
6
java.lang.IllegalArgumentException: Last encoded character (before the paddings if any) is a valid base 64 alphabet but not a possible value

at org.apache.commons.codec.binary.Base64.validateCharacter(Base64.java:798)
at org.apache.commons.codec.binary.Base64.decode(Base64.java:477)
at org.apache.commons.codec.binary.BaseNCodec.decode(BaseNCodec.java:411)
at org.apache.commons.codec.binary.BaseNCodec.decode(BaseNCodec.java:395)
  1. 具体场景

    处理微信公众号消息时,对消息内容进行必须的加解密,出现的问题是处理aesKey时出现的,具体demo如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public WxOpenCryptUtil(WxOpenConfigStorage wxOpenConfigStorage) {
    /*
    * @param token 公众平台上,开发者设置的token
    * @param encodingAesKey 公众平台上,开发者设置的EncodingAESKey
    * @param appId 公众平台appid
    */
    String encodingAesKey = wxOpenConfigStorage.getComponentAesKey();
    String token = wxOpenConfigStorage.getComponentToken();
    String appId = wxOpenConfigStorage.getComponentAppId();

    this.token = token;
    this.appidOrCorpid = appId;
    this.aesKey = Base64.decodeBase64(encodingAesKey + "=");
    }

    当升级commons-codec版本为1.13及以上时,会出现上述异常

  2. 出现的原因

    1.13出现异常的方法

    1
    2
    3
    4
    5
    6
    7
    private long validateCharacter(final int numBitsToDrop, final Context context) {
    if ((context.ibitWorkArea & numBitsToDrop) != 0) {
    throw new IllegalArgumentException(
    "Last encoded character (before the paddings if any) is a valid base 64 alphabet but not a possible value");
    }
    return context.ibitWorkArea >> numBitsToDrop;
    }

    仔细分析可以看出编解码是在BaseNCodec.java是Base64和Base32的基类

    image-20200330115937553

    可以看出唯一的差别就是在解码时对参数做了校验。有必要了解下这个参数校验做了些什么?

    1
    context.ibitWorkArea & numBitsToDrop

    ibitWorkArea: 位处理的基本位数

    numBitsToDrop: 应该为空的低位数目

    可以看出当 context.ibitWorkArea & numBitsToDrop不为0时就会抛出异常,实际上只有base64严格模式编码下,才可能会为0,松散模式不会为0

  3. 解决办法

    降低版本到1.12以下可以解决该问题,或者等commons-codec版本更新到1.15,最新的源码已经处理了该问题

    1
    2
    3
    4
    5
    6
    7
    private void validateCharacter(final int emptyBitsMask, final Context context) {
    if (isStrictDecoding() && (context.ibitWorkArea & emptyBitsMask) != 0) {
    throw new IllegalArgumentException(
    "Strict decoding: Last encoded character (before the paddings if any) is a valid base 64 alphabet but not a possible encoding. " +
    "Expected the discarded bits from the character to be zero.");
    }
    }
  4. 扩展

    base64的严格模式和松散模式定义,直接引用源码了

    Lenient: Any trailing bits are composed into 8-bit bytes where possible.
    The remainder are discarded.
    Strict: The decoding will raise an {@link IllegalArgumentException} if trailing bits
    are not part of a valid encoding. Any unused bits from the final character must
    be zero. Impossible counts of entire final characters are not allowed.

References

使用java8的java.util.Base64报“java.lang.IllegalArgumentException: Illegal base64 character d”的问题

Base64笔记

Base64.decode fails on Java11 for certain valid base 64 encoded String