概述

Base64是一种字符串编码格式,采用了A-Z,a-z,0-9,“+”和“/”这64个字符来编码原始字符(还有垫字符“=”)。一个字符本身是一个字节,也就是8位,而base64编码后的一个字符只能表示6位的信息。也就是原始字符串中的3字节的信息编码会变成4字节的信息。Base64的主要作用是满足MIME的传输需求。
在Java8中Base64编码已经成为Java类库的标准,且内置了Base64编码的编码器和解码器。

问题

偶然发现使用jdk8内置的Base64解码器进行解析的时候,会抛出java.lang.IllegalArgumentException: Illegal base64 character a异常。
这非常奇怪,因为原文是使用jdk7里面的编码器进行编码的,理论上不至于发生这种不兼容的状况。

测试程序

还是来写程序测试一下问题到底在哪里。

测试程序使用了一个比较长的原文,主要是这个问题在原文较长的时候才会出现,如果原文较短(字节长度不超过57),那么不会有这个问题。

1 使用jdk7进行编码:

import sun.misc.BASE64Encoder;
public class TestBase64JDK7 {private static final String TEST_STRING = "0123456789,0123456789,0123456789,0123456789,0123456789,0123456789,0123456789";public static void main(String[] args) {BASE64Encoder base64Encoder = new BASE64Encoder();String base64Result = base64Encoder.encode(TEST_STRING.getBytes());System.out.println(base64Result);}
}

2 jdk7编码结果:

MDEyMzQ1Njc4Oe+8jDAxMjM0NTY3ODnvvIwwMTIzNDU2Nzg577yMMDEyMzQ1Njc4Oe+8jDAxMjM0
NTY3ODnvvIwwMTIzNDU2Nzg577yMMDEyMzQ1Njc4OQ==

3 使用jdk8对上面的编码结果进行解码:

import java.util.Base64;
public class TestBase64JDK8 {public static void main(String[] args) {String base64Result = "MDEyMzQ1Njc4Oe+8jDAxMjM0NTY3ODnvvIwwMTIzNDU2Nzg577yMMDEyMzQ1Njc4Oe+8jDAxMjM0\n" +"NTY3ODnvvIwwMTIzNDU2Nzg577yMMDEyMzQ1Njc4OQ==";Base64.getDecoder().decode(base64Result);}
}

4 结果就如最开始描述的那样,会抛出异常:

Exception in thread "main" java.lang.IllegalArgumentException: Illegal base64 character aat java.util.Base64$Decoder.decode0(Base64.java:714)at java.util.Base64$Decoder.decode(Base64.java:526)at java.util.Base64$Decoder.decode(Base64.java:549)at com.francis.TestBase64JDK8.main(TestBase64JDK8.java:14)

难道说jdk7和jdk8在base64的处理上有什么不一样???

5 继续来看一下jdk8对原文的编码:

import java.util.Base64;
public class TestBase64JDK8 {private static final String TEST_STRING = "0123456789,0123456789,0123456789,0123456789,0123456789,0123456789,0123456789";public static void main(String[] args) {String base64Result = Base64.getEncoder().encodeToString(TEST_STRING.getBytes());System.out.println(base64Result);}
}

6 jdk8编码结果:

MDEyMzQ1Njc4Oe+8jDAxMjM0NTY3ODnvvIwwMTIzNDU2Nzg577yMMDEyMzQ1Njc4Oe+8jDAxMjM0NTY3ODnvvIwwMTIzNDU2Nzg577yMMDEyMzQ1Njc4OQ==

至此针对比较长的原文进行base64编码可以得到如下结论:

  • jdk7的编码结果包含换行;
  • jdk8的编码结果不包含换行;
  • jdk8无法解码包含换行的编码结果;

jdk8的编码结果使用jdk7进行解码,没有任何问题,不再演示。

现在问题原因基本清楚了,是由于jdk7的编码结果包含换行,导致jdk8解码的时候抛出异常。
但是为什么会有这种差异呢?难道使用的base64的标准还不一样?

问题排查

继续排查问题,先从类注释入手,看看是不是理解有误。

1 先来看看jdk8中的Base64类注释,这里只列出一些关键内容:

/*** This class consists exclusively of static methods for obtaining* encoders and decoders for the Base64 encoding scheme. The* implementation of this class supports the following types of Base64* as specified in* <a href="http://www.ietf.org/rfc/rfc4648.txt">RFC 4648</a> and* <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>.** <ul>* <li><a name="basic"><b>Basic</b></a>* <p> Uses "The Base64 Alphabet" as specified in Table 1 of*     RFC 4648 and RFC 2045 for encoding and decoding operation.*     The encoder does not add any line feed (line separator)*     character. The decoder rejects data that contains characters*     outside the base64 alphabet.</p></li>...* @author  Xueming Shen* @since   1.8*/

大意是说:

这个类包含了base64编码格式的编码方法和解码方法,而且实现是按照rfc4648和rfc2045两个协议来实现的。
编码和解码操作是照着两个协议中的'Table 1'中指定的'The Base64 Alphabet'来的。编码器不会添加任何换行符,解码器只会处理'The Base64 Alphabet'范围内的数据,如果不在这个范围内,解码器会拒绝处理。

看到这里就可以理解为什么jdk8的编码结果不包含换行了。

另外,基本上可以猜到为什么jdk8无法解码jdk7的编码结果了(换行符应该不在The base64 alphabet当中)。

2 先来看一眼两个标准中的the base64 alphabet(两个标准中的这个表是一样的):

                         Table 1: The Base 64 AlphabetValue Encoding  Value Encoding  Value Encoding  Value Encoding0 A            17 R            34 i            51 z1 B            18 S            35 j            52 02 C            19 T            36 k            53 13 D            20 U            37 l            54 24 E            21 V            38 m            55 35 F            22 W            39 n            56 46 G            23 X            40 o            57 57 H            24 Y            41 p            58 68 I            25 Z            42 q            59 79 J            26 a            43 r            60 810 K            27 b            44 s            61 911 L            28 c            45 t            62 +12 M            29 d            46 u            63 /13 N            30 e            47 v14 O            31 f            48 w         (pad) =15 P            32 g            49 x16 Q            33 h            50 y

并不包含换行符,这就可以解释为什么jdk8无法解码包含换行的编码结果

3 再来看一下jdk7中sun.misc.BASE64Encoder的类注释:

   This class implements a BASE64 Character encoder as specified in RFC1521. This RFC is part of the MIME specification as published by the Internet Engineering Task Force (IETF). Unlike some other encoding schemes there is nothing in this encoding that indicates where a buffer starts or ends. This means that the encoded text will simply start with the first line of encoded text and end with the last line of encoded text.

这个实现是按照RFC1521来的,类注释中并没有关于编码或者解码约束的说明。

4 那继续看一下rfc1521的关键部分(链接:https://tools.ietf.org/html/rfc1521)。

5.2. Base64 Content-Transfer-Encoding章节有如下内容:

   The output stream (encoded bytes) must be represented in lines of nomore than 76 characters each.  All line breaks or other charactersnot found in Table 1 must be ignored by decoding software.  In base64data, characters other than those in Table 1, line breaks, and otherwhite space probably indicate a transmission error, about which awarning message or even a message rejection might be appropriateunder some circumstances.

这里明确规定了:

  • 编码结果的每一行不能超过76个字符;
  • 解码的字符必须在:Tbale 1(也就是之前提到的the base64 alphabet)、换行符和空白符这个范围内;

这就是为什么jdk7的编码结果包含换行
这样根据类注释和rfc协议内容,就可以解释上面通过测试代码得到的结论,也就可以理解为什么会产生这个问题。

‘sun’开头的包并不属于java规范,是sun公司的实现,所以jdk7中的这种base64编码方式并不是java的规范。

解决办法

那么,怎么解决这个问题呢:
1. 使用apache common包中的org.apache.commons.codec.binary.Base64类进行编码和解码;
2. 编码之后或解码之前去除换行符;
3. 编码和解码使用相同的jdk版本;

其他Base64库

看看其他类库是怎么处理base64的。
1. Apache Common

Apache Common中的org.apache.commons.codec.binary.Base64类是基于rfc2045实现的,根据类注释可以了解到此实现解码时忽略了所有不在the base64 alphabet范围内的字符,所以该实现可以处理包含换行符的base64编码结果。
同时该类的编码方法提供了参数,用于指定编码结果长度在超过76个字符的时候是否添加换行,默认不换行。

  1. Spring Core

Spring Core提供了Base64Utils类,该类只是一个工具类,并没有实现任何协议。

  • 优先使用java8中的java.util.Base64类进行编码和解码;
  • 如果java.util.Base64不存在,则会使用org.apache.commons.codec.binary.Base64
  • 如果都不存在,则会报错

协议简述

通过上面的排查步骤可以看到,rfc1521rfc2045rfc4648中关于base64的部分似乎不太一样,接下来分别简单看一下这三个协议是如何规范base64编码的换行的。

  1. rfc1521(链接:https://tools.ietf.org/html/rfc1521)
    该协议是关于MIME的,Base64是MIME支持的一种编码类型。关键内容5.2. Base64 Content-Transfer-Encoding章节已经在上文中简单阐述过了,主要是规定了:编码结果每行长度和解码字符的范围。
    该协议已经被淘汰。
    jdk7基于该协议实现base64,所以编码结果会包含换行符。

    MIME:Multipurpose Internet Mail Extensions,多用途互联网邮件扩展类型。是一个互联网标准,最早用于电子邮件系统,后来被应用到浏览器。服务器会将它们发送的多媒体数据的类型告诉浏览器,而通知手段就是说明该多媒体数据的MIME类型。

  2. rfc2045(链接:https://tools.ietf.org/html/rfc2045)

    该协议同样是关于MIME的,是rfc1521的更新版本,关键内容6.8. Base64 Content-Transfer-Encoding章节,其中关于编码结果长度和解码字符范围的规定与rfc1521并没有什么差别。

  3. rfc4648

    该协议是关于base16、base32和base64编码的。关于编码结果每行长度的说明在3.1. Line Feeds in Encoded Data章节:

   MIME is often used as a reference for base 64 encoding.  However,MIME does not define "base 64" per se, but rather a "base 64 Content-Transfer-Encoding" for use within MIME.  As such, MIME enforces alimit on line length of base 64-encoded data to 76 characters.  MIMEinherits the encoding from Privacy Enhanced Mail (PEM) [3], statingthat it is "virtually identical"; however, PEM uses a line length of64 characters.  The MIME and PEM limits are both due to limits withinSMTP.

Implementations MUST NOT add line feeds to base-encoded data unless
the specification referring to this document explicitly directs base
encoders to add line feeds after a specific number of characters.

大意是:

   MIME协议通常作为base64协议的引用。但是MIME协议并没有定义'base64',而是定义了'base64 内容传输编码'。因此MIME将base64编码的数据的长度限制为76个字符。...MIME和PEM关于长度的限制都是用于SMTP的。该协议的实现在编码结果中不能添加换行符,除非引用了该文档的实现中,明确说明在特定长度之后添加换行符。

jdk8的Base64类是基于rfc2045和rfc4648实现的,根据上文列出的协议内容可以确定,该类的编码结果不会包含换行符,而且在类的注释中明确说明了不会添加换行符。

升级JDK8的坑--base64相关推荐

  1. 升级JDK8的坎坷之路

    为更好的适应JAVA技术的发展,使用更先进及前沿的技术.所以推出将我们现在使用的JDK1.6(1.7)及tomcat6(7)升级至JDK1.8及tomcat8,使我们的系统获得更好的性能,更好适应未来 ...

  2. vue1升级vue2踩坑指南

    vue1升级vue2踩坑指南 升级原因 版本选择 老的package.json: 新的package.json: vue官方说明 语法/组件使用变化 ready钩子 $index & $key ...

  3. .NET Core3.1升级.NET5,坑还真不少...

    11月11号是电商狂欢的日子,也是.NET5正式发布的日子,媳妇儿等着零点秒杀,我却在刷新着微软官网等更新,然后第一时间开始折腾.此前Scott Hunter在博客信誓旦旦.NET Core3.1平滑 ...

  4. centos7恢复mysql数据库_MySQL数据库升级迁移填坑记

    原库:*.*.101.73/74 系统环境: Suse 12.4 MySQL: 5.7.29 新库:*.*.110.46/47 系统环境:CentOS7.7 64位 MySQL版本: 5.7.30 [ ...

  5. 墨瞳漫画 升级vue2 踩坑

    概述 升级的话呢,还是比较简单,就按官方文档来,改写一下某些api,本文主要讲文档中说的不清晰的一些坑 vue-router 官方文档中推荐使用watcher来监测$route的改变,当路由变化时就去 ...

  6. Android3彩蛋,Android升级gradle5的坑+Androidstudio3.4小彩蛋

    升级gradle的的一些坑 我只是举些例子,一些类似的坑可以举一反三,不必一一细说.别自己不去找不去问,就怪我没告诉你,我没说还是我的错了? 1 studio3.2直接去升级gradle5.报错: C ...

  7. android 升级androidx 埋坑总结

    一. 突然这边有一个需求,老项目要嵌入新型的一些AndriodX的插件 不升的话 报好多问题,但是都很清楚老项目升的话会有很多坑. 所以我提前去网上找了一些相关的操作尽管已经看了好多,但是难免还会遇到 ...

  8. mysql版本升级对数据的影响_MySQL数据库升级的一些坑

    对于商业数据库而言,数据库升级是一个优先级很高的事情,有版本升级路线图,有相应的补丁,而且对于方案还有一系列的演练,陷入是一场硬仗.而在MySQL方向上,升级这件事情就被淡化了许多,好像只能证明它的存 ...

  9. 关于JDK8采坑JCE加密限制版本问题

    文章目录 一.解决方案1 1. 调研 2. 常见的异常 3. 安全性机制导致的访问https会报错 4. 解决方案 5. 操作流程 6. 移动jar配置策略 二.解决方案2 2.1. 声明 2.2. ...

最新文章

  1. usaco party lamps
  2. java 动态规划视频_157-动态规划算法解决背包问题1
  3. Python3 嵌套函数
  4. Debian on VirtualBox下共享win7文件夹设置
  5. 培养创造性思维20个技巧
  6. 2021牛客寒假算法基础集训营3,签到题DGHIJ
  7. 个人知识管理系统Version1.0开发记录(07)
  8. IDEA 配置SVN ,SVN安装后没有svn.exe
  9. 烽火超微信息科技 服务器,智算升级 烽火超微发布新一代V6服务器
  10. 科学计算机弧度,科学计算器角度换算(学生计算器怎么算角度)
  11. educoder—web:页面元素和属性
  12. 【搬家】写一个技术博客
  13. 麦肯锡全球研究院 人机共存的新纪元: 自动化、就业和生产力
  14. Docker未授权漏洞复现(合天网安实验室)
  15. HDS发布视频分析软件HVA 为企业提供运营智能与安全洞察
  16. 后缀自动机+DP BZOJ 3238 差异
  17. 隔壁住着一个过气的明星是什么体验?
  18. 中国期货市场风险回顾之三(海南棕榈油M506事件)
  19. Python 3 显示图像的方法
  20. armadillo matlab,科学网—C++下媲美MATLAB矩阵运算的Armadillo 库 - 吴泓润的博文

热门文章

  1. 快速排序、归并排序、插入排序与基数排序
  2. MAYA渲染 mantel ray材质球 mia_material_x
  3. java List转字符串
  4. 扎实基础深入篇(六):while循环带动生产力
  5. Bilibili笔试
  6. 计算机艺术文化主题名称,艺术文化晚会主题口号
  7. 更改开发者账号中的公司名称, 也就是苹果商店显示的开发商名称
  8. Android 获取手机的 IMEI 值
  9. 四种好心态让你的工作能量源源不绝
  10. 怎样在html中插入广告,用div+css+js在文章中间插入广告