场景

对于很多场景,比如说图片展示,还有一些前后端请求,有时候通过 url 会比较麻烦。

通过 Base64 转换处理之后比较方便,当然也有把这个当做一种加密策略的。(实际上只是转码,不是严格意义的加密)

Base64是一种能将任意Binary资料用64种字元组合成字串的方法,而这个Binary资料和字串资料彼此之间是可以互相转换的,十分方便。

在实际应用上,Base64除了能将Binary资料可视化之外,也常用来表示字串加密过后的内容。

java 实现方式

早期作法

早期在Java上做Base64的编码与解码,会使用到JDK里sun.misc套件下的BASE64Encoder和BASE64Decoder这两个类别,用法如下:

  [java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
final BASE64Encoder encoder = new BASE64Encoder(); final BASE64Decoder decoder = new BASE64Decoder(); final String text = "字串文字"; final byte[] textByte = text.getBytes("UTF-8"); //编码 final String encodedText = encoder.encode(textByte); System.out.println(encodedText); //解码 System.out.println(new String(decoder.decodeBuffer(encodedText), "UTF-8")); final BASE64Encoder encoder = new BASE64Encoder(); final BASE64Decoder decoder = new BASE64Decoder(); final String text = "字串文字"; final byte[] textByte = text.getBytes("UTF-8"); //编码 final String encodedText = encoder.encode(textByte); System.out.println(encodedText); //解码 System.out.println(new String(decoder.decodeBuffer(encodedText), "UTF-8"));

从以上程式可以发现,在Java用Base64一点都不难,不用几行程式码就解决了!

只是这个sun.misc套件所提供的Base64功能,编码和解码的效率并不太好,而且在以后的Java版本可能就不被支援了,完全不建议使用。

Apache Commons Codec作法

Apache Commons Codec有提供Base64的编码与解码功能,会使用到org.apache.commons.codec.binary套件下的Base64类别,用法如下:

  [java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
final Base64 base64 = new Base64(); final String text = "字串文字"; final byte[] textByte = text.getBytes("UTF-8"); //编码 final String encodedText = base64.encodeToString(textByte); System.out.println(encodedText); //解码 System.out.println(new String(base64.decode(encodedText), "UTF-8")); final Base64 base64 = new Base64(); final String text = "字串文字"; final byte[] textByte = text.getBytes("UTF-8"); //编码 final String encodedText = base64.encodeToString(textByte); System.out.println(encodedText); //解码 System.out.println(new String(base64.decode(encodedText), "UTF-8"));

以上的程式码看起来又比早期用sun.misc套件还要更精简,效能实际执行起来也快了不少。

缺点是需要引用Apache Commons Codec,很麻烦。

Java 8之后的作法

Java 8的java.util套件中,新增了Base64的类别,可以用来处理Base64的编码与解码,用法如下:

  [java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
final Base64.Decoder decoder = Base64.getDecoder(); final Base64.Encoder encoder = Base64.getEncoder(); final String text = "字串文字"; final byte[] textByte = text.getBytes("UTF-8"); //编码 final String encodedText = encoder.encodeToString(textByte); System.out.println(encodedText); //解码 System.out.println(new String(decoder.decode(encodedText), "UTF-8")); final Base64.Decoder decoder = Base64.getDecoder(); final Base64.Encoder encoder = Base64.getEncoder(); final String text = "字串文字"; final byte[] textByte = text.getBytes("UTF-8"); //编码 final String encodedText = encoder.encodeToString(textByte); System.out.println(encodedText); //解码 System.out.println(new String(decoder.decode(encodedText), "UTF-8"));

与 sun.misc 套件和Apache Commons Codec所提供的Base64编解码器来比较的话,Java 8提供的Base64拥有更好的效能。

实际测试编码与解码速度的话,Java 8提供的Base64,要比sun.mis c套件提供的还要快至少11倍,比Apache Commons Codec提供的还要快至少3倍。

因此在Java上若要使用Base64,这个Java 8底下的java.util套件所提供的Base64类别绝对是首选!

原理

完整的BASE64定义可见RFC 1421和RFC 2045。编码后的数据比原始数据略长,为原来的4/3。

在电子邮件中,根据RFC 822规定,每76个字符,还需要加上一个回车换行。可以估算编码后数据长度大约为原长的135.1%。

转换的时候,将三个byte的数据,先后放入一个24bit的缓冲区中,先来的byte占高位。数据不足3byte的话,于缓冲器中剩下的bit用0补足。然后,每次取出6(因为26=64)个bit,按照其值选择 ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ 中的字符作为编码后的输出。不断进行,直到全部输入数据转换完成。

当原数据长度不是3的整数倍时, 如果最后剩下一个输入数据(原始数据按3个一组,剩下一个),在编码结果后加2个”=”;如果最后剩下两个输入数据(原始数据按3个一组,剩下两个),编码结果后加1个”=”;如果没有剩下任何数据,就什么都不要加,这样才可以保证数据还原的正确性。

Base64 索引表

Base64索引表:

  [plaintext]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
0 A 16 Q 32 g 48 w 1 B 17 R 33 h 49 x 2 C 18 S 34 i 50 y 3 D 19 T 35 j 51 z 4 E 20 U 36 k 52 0 5 F 21 V 37 l 53 1 6 G 22 W 38 m 54 2 7 H 23 X 39 n 55 3 8 I 24 Y 40 o 56 4 9 J 25 Z 41 p 57 5 10 K 26 a 42 q 58 6 11 L 27 b 43 r 59 7 12 M 28 c 44 s 60 8 13 N 29 d 45 t 61 9 14 O 30 e 46 u 62 + 15 P 31 f 47 v

不能整除的情况

如果要编码的字节数不能被3整除,最后会多出1个或2个字节,那么可以使用下面的方法进行处理:

先使用0字节值在末尾补足,使其能够被3整除,然后再进行Base64的编码。

在编码后的Base64文本后加上一个或两个’=’号,代表补足的字节数。

也就是说,当最后剩余一个八位字节(一个byte)时,最后一个6位的Base64字节块有四位是0值,最后附加上两个等号;

如果最后剩余两个八位字节(2个byte)时,最后一个6位的base字节块有两位是0值,最后附加一个等号。

参考下表:

  [plaintext]
1
2
3
4
5
6
7
8
文本(1 Byte) A 二进制位 0 1 0 0 0 0 0 二进制位(补0) 0 1 0 0 0 0 0 1 0 0 0 0 Base64编码 Q 文本(2 Byte) B 二进制位 0 1 0 0 0 0 1 0 0 1 0 0 0 0 1 1 x x x x x x 二进制位(补0) 0 1 0 0 0 0 1 0 0 1 0 0 0 0 1 1 0 0 x x x x x x Base64编码 Q

一些拓展

标准的Base64并不适合直接放在URL里传输,因为URL编码器会把标准Base64中的”/”和”+”字符变为形如”%XX”的形式,而这些”%”号在存入数据库时还需要再进行转换,因为ANSI SQL中已将”%”号用作通配符。

为解决此问题,可采用一种用于URL的改进Base64编码,它不在末尾填充’=’号,并将标 准Base64中的”+”和”/”分别改成了”*“和”-“,这样就免去了在URL编解码和数据库存储时所要作的转换,避免了编码信息长度在此过程中的增加,并统一了数据库、表单等处对象标识符的格式。

另有一种用于正则表达式的改进Base64变种,它将”+”和”/”改成了”!”和”-“,因为”+”,”*“以及前面在IRCu中用到的”[“和”]”在正则表达式中都可能具有特殊含义。

此外还有一些变种,它们将”+/”改为”-“或”.”(用作编程语言中的标识符名称)或”.-“(用于XML中的Nmtoken)甚至”_:”(用于XML中的Name)。

实现

  [java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
public class Base64 { /** * 将原始数据编码为base64编码 */ static public char[] encode(byte[] data) { char[] out = new char[((data.length + 2) / 3) * 4]; for (int i = 0, index = 0; i < data.length; i += 3, index += 4) { boolean quad = false; boolean trip = false; int val = (0xFF & (int) data[i]); val <<= 8; if ((i + 1) < data.length) { val |= (0xFF & (int) data[i + 1]); trip = true; } val <<= 8; if ((i + 2) < data.length) { val |= (0xFF & (int) data[i + 2]); quad = true; } out[index + 3] = alphabet[(quad ? (val & 0x3F) : 64)]; val >>= 6; out[index + 2] = alphabet[(trip ? (val & 0x3F) : 64)]; val >>= 6; out[index + 1] = alphabet[val & 0x3F]; val >>= 6; out[index + 0] = alphabet[val & 0x3F]; } return out; } /** * 将base64编码的数据解码成原始数据 */ static public byte[] decode(char[] data) { int len = ((data.length + 3) / 4) * 3; if(data.length > 0 && data[data.length - 1] == '=') --len; if(data.length > 1 && data[data.length - 2] == '=') --len; byte[] out = new byte[len]; int shift = 0; int accum = 0; int index = 0; for(int ix = 0; ix < data.length; ix++) { int value = codes[data[ix] & 0xFF]; if(value >= 0) { accum <<= 6; shift += 6; accum |= value; if(shift >= 8) { shift -= 8; out[index++] = (byte)((accum >> shift) & 0xff); } } } if(index != out.length) throw new Error("miscalculated data length!"); return out; } static private char[] alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".toCharArray(); static private byte[] codes = new byte[256]; static { for (int i = 0; i < 256; i++) codes[i] = -1; for (int i = 'A'; i <= 'Z'; i++) codes[i] = (byte) (i - 'A'); for (int i = 'a'; i <= 'z'; i++) codes[i] = (byte) (26 + i - 'a'); for (int i = '0'; i <= '9'; i++) codes[i] = (byte) (52 + i - '0'); codes['+'] = 62; codes['/'] = 63; } }

参考资料

关于base64编码Encode和Decode编码的几种方式

Base64 详解

BASE64 编码详解

Css中路径data:image/png;base64的用法详解