在 Java8中 Base64编码已经成为Java类库的标准,且内置了Base64编码的编码器和解码器。
一、什么是Base64? -- 来自百度百科
Base64是网络上最常见的用于传输 8Bit字节码的编码方式之一,
Base64就是一种基于64个可打印字符来表示二进制数据的方法。
1、什么是“可打印字符”呢?为什么要用它来传输 8Bit字节码呢?
Base64一般用于在 HTTP协议下传输二进制数据,由于 HTTP协议是文本协议,所以在 HTTP协议下传输二进制数据需要将二进制数据转换为字符数据。然而直接转换是不行的。因为网络传输只能传输可打印字符。
什么是可打印字符?在ASCII码中规定,0~31、127这33个字符属于控制字符,32~126这95个字符属于可打印字符,也就是说网络传输只能传输这95个字符,不在这个范围内的字符无法传输。那么该怎么才能传输其他字符呢?其中一种方式就是使用Base64。
2、Base64的编码原理
Base64的原理比较简单,每当我们使用Base64时都会先定义一个类似这样的数组:
['A', 'B', 'C', ... 'a', 'b', 'c', ... '0', '1', ... '+', '/']
Base64采用了 "A-Z、a-z、0-9、+、/" 64个可打印字符,这是标准的Base64协议规定。在日常使用中我们还会看到 “=”或“==” 号出现在Base64的编码结果中,“=”在此是作为填充字符出现,后面会讲到。
Base64就是使用这64个可打印字符来表示二进制数据的方法。Base64的索引与对应字符的关系如下表所示:
索引 |
对应字符 |
索引 |
对应字符 |
索引 |
对应字符 |
索引 |
对应字符 |
0 |
A |
17 |
R |
34 |
i |
51 |
z |
1 |
B |
18 |
S |
35 |
j |
52 |
0 |
2 |
C |
19 |
T |
36 |
k |
53 |
1 |
3 |
D |
20 |
U |
37 |
l |
54 |
2 |
4 |
E |
21 |
V |
38 |
m |
55 |
3 |
5 |
F |
22 |
W |
39 |
n |
56 |
4 |
6 |
G |
23 |
X |
40 |
o |
57 |
5 |
7 |
H |
24 |
Y |
41 |
p |
58 |
6 |
8 |
I |
25 |
Z |
42 |
q |
59 |
7 |
9 |
J |
26 |
a |
43 |
r |
60 |
8 |
10 |
K |
27 |
b |
44 |
s |
61 |
9 |
11 |
L |
28 |
c |
45 |
t |
62 |
+ |
12 |
M |
29 |
d |
46 |
u |
63 |
/ |
13 |
N |
30 |
e |
47 |
v |
||
14 |
O |
31 |
f |
48 |
w |
||
15 |
P |
32 |
g |
49 |
x |
||
16 |
Q |
33 |
h |
50 |
y |
3、具体转换步骤
第一步,将待转换的字符串每三个字节分为一组,每个字节占 8bit,那么共有24个二进制位。
第二步,将上面的24个二进制位每6个一组,共分为4组。
第三步,在每组前面添加两个0,每组由6个变为8个二进制位,总共32个二进制位,即四个字节。
第四步,根据Base64的索引与对应字符的关系获得对应的字符值。
从上面的步骤我们发现:
Base64字符表中的字符原本用6个bit就可以表示,现在前面添加2个0,变为8个bit,会造成一定的浪费。因此,Base64编码之后的文本,要比原文多大约三分之一。
为什么使用3个字节一组呢?因为6和8的最小公倍数为24,三个字节正好24个二进制位,每6个bit位一组,恰好能够分为4组。
4、示例说明
1)以下图的表格为示例,我们具体分析一下整个过程。
第一步:字符串“Man”为3个字节,“M”、“a”、"n"对应的ASCII码值分别为77,97,110,对应的二进制值是01001101、01100001、01101110。如图第二三行所示,由此组成一个24位的二进制字符串。
第二步:如图红色框,将24位每6位二进制位一组分成四组。
第三步:在上面每一组前面补两个0,扩展成32个二进制位,此时变为四个字节:00010011、00010110、00000101、00101110。分别对应的值(Base64编码索引)为:19、22、5、46。
第四步:用上面的值在Base64编码表中进行查找,分别对应:T、W、F、u。因此“Man”Base64编码之后就变为:TWFu。
2)位数不足情况
上面是按照三个字节来举例说明的,如果字节数不足三个,那么该如何处理?
两个字节:两个字节共16个二进制位,依旧按照规则进行分组。此时总共16个二进制位,每6个一组,则第三组缺少2位,用0补齐,得到三个Base64编码,第四组完全没有数据则用“=”补上。因此,上图中“BC”转换之后为“QKM=”;
一个字节:一个字节共8个二进制位,依旧按照规则进行分组。此时共8个二进制位,每6个一组,则第二组缺少4位,用0补齐,得到两个Base64编码,而后面两组没有对应数据,都用“=”补上。因此,上图中“A”转换之后为“QQ==”;
3)注意事项
大多数编码都是由字符串转化成二进制的过程,而Base64的编码则是从二进制转换为字符串。与常规恰恰相反,
Base64编码主要用在传输、存储、表示二进制领域,不能算得上加密,只是无法直接看到明文。也可以通过打乱Base64编码来进行加密。
中文有多种编码(比如:utf-8、gb2312、gbk等),不同编码对应Base64编码结果都不一样。
延伸:
上面我们已经看到了Base64就是用6位(2的6次幂就是64)表示字符,因此成为Base64。同理,Base32就是用5位,Base16就是用4位。大家可以按照上面的步骤进行演化一下。
二、Java8中实现Base64
在 Java8中 Base64编码已经成为Java类库的标准,且内置了Base64编码的编码器和解码器。
新的Base64API也支持URL和MINE的编码解码。我们直接调用即可。
java.util.Base64 类仅由用于获得Base64编码方案的编码器和解码器的静态方法组成。
-
-
static Base64.Decoder
getDecoder()
返回
Base64.Decoder
解码使用Basic型base64编码方案。static Base64.Encoder
getEncoder()
返回一个
Base64.Encoder
编码使用Basic型base64编码方案。static Base64.Decoder
getMimeDecoder()
返回一个
Base64.Decoder
解码使用MIME型BASE64解码方案。static Base64.Encoder
getMimeEncoder()
返回一个
Base64.Encoder
编码使用MIME型base64编码方案。static Base64.Encoder
getMimeEncoder(int lineLength, byte[] lineSeparator)
返回一个
Base64.Encoder
,它使用具有指定行长度和行分隔符的MIME类型base64编码方案进行编码。static Base64.Decoder
getUrlDecoder()
返回
Base64.Decoder
解码使用URL and Filename safe型base64编码方案。static Base64.Encoder
getUrlEncoder()
返回一个
Base64.Encoder
编码使用URL and Filename safe型base64编码方案。
-
java.util.Base64.Encoder 类使用RFC 4648和RFC 2045中规定的Base64编码方案来实现用于编码字节数据的编码器。
-
-
byte[]
encode(byte[] src)
使用
Base64
编码方案将指定字节数组中的所有字节编码为新分配的字节数组。int
encode(byte[] src, byte[] dst)
使用
Base64
编码方案对来自指定字节数组的所有字节进行编码,将生成的字节写入给定的输出字节数组,从偏移0开始。ByteBuffer
encode(ByteBuffer buffer)
使用
Base64
编码方案将所有剩余字节从指定的字节缓冲区编码到新分配的ByteBuffer中。String
encodeToString(byte[] src)
使用
Base64
编码方案将指定的字节数组编码为字符串。Base64.Encoder
withoutPadding()
返回一个编码器实例,编码器等效于此编码器实例,但不会在编码字节数据的末尾添加任何填充字符。
OutputStream
wrap(OutputStream os)
使用
Base64
编码方案包装用于编码字节数据的输出流。
-
java.util.Base64.Decoder 该类使用RFC 4648和RFC 2045中规定的Base64编码方案来实现用于解码字节数据的解码器。
-
-
byte[]
decode(byte[] src)
使用
Base64
编码方案从输入字节数组中解码所有字节,将结果写入新分配的输出字节数组。int
decode(byte[] src, byte[] dst)
使用
Base64
编码方案从输入字节数组中解码所有字节,将结果写入给定的输出字节数组,从偏移0开始。ByteBuffer
decode(ByteBuffer buffer)
使用
Base64
编码方案从输入字节缓冲区中解码所有字节,将结果写入新分配的ByteBuffer。byte[]
decode(String src)
使用
Base64
编码方案将Base64编码的字符串解码为新分配的字节数组。InputStream
wrap(InputStream is)
返回一个输入流,用于解码
Base64
编码字节流。
-
加密字符串测试 demo
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class Demo {
public static void main(String[] args) throws UnsupportedEncodingException {
String str = "hello Base64 啊啊\n" +
"ADCDEFG";
//编码加密
String encodeStr = Base64.getEncoder().encodeToString(str.getBytes("UTF-8"));
System.out.println("加密后的字符串为:" + encodeStr);
//解码解密
String decoderStr = new String(Base64.getDecoder().decode(encodeStr), StandardCharsets.UTF_8); //
// 推荐使用StandardCharsets类指定
System.out.println("解密后的字符串为" + decoderStr);
}
}
JDK8 的 Base64类是基于 rfc2045和 rfc4648实现的,根据上文列出的协议内容可以确定,该类的编码结果不会包含换行符,而且在类的注释中明确说明了不会添加换行符。解码会正常还原。
结论:针对比较长的原文进行base64编码:
jdk7的编码结果包含换行;
jdk8的编码结果不包含换行;
jdk8无法解码包含换行的编码结果;
jdk8的编码结果使用jdk7进行解码,没有任何问题,不再演示
三、其他Base64库
1、Apache Common
Apache Common中的 org.apache.commons.codec.binary.Base64类是基于 rfc2045实现的,根据类注释可以了解到此实现解码时忽略了所有不在the base64 alphabet范围内的字符,所以该实现可以处理包含换行符的base64编码结果。
同时该类的编码方法提供了参数,用于指定编码结果长度在超过76个字符的时候是否添加换行,默认不换行。
2、Spring Core
Spring Core提供了 org.springframework.util.Base64Utils类,该类只是一个工具类,并没有实现任何协议。
优先使用java8中的java.util.Base64类进行编码和解码;
如果java.util.Base64不存在,则会使用org.apache.commons.codec.binary.Base64;
如果都不存在,则会报错
3、协议简述(rfc1521、rfc2045、和rfc4648)看参考文章
参考文章:这两篇文章对理解 Base64 很有帮助,感谢前辈
站在前辈的肩膀上,每天进步一点点
ends~