Base64算法的由来
Base64算法最开始是被用于解决电子邮件数据传输问题。在早期,由于历史原因问题,电子邮件只允许使用ASCII字符,如果在邮件中出现了非ASCII字符,在通过某些网关进行数据转发的时候,网关会对这些非ASCII字符做出调整,例如,把ASCII码8位二进制码的最高位置为0。此时接收方在收到邮件时就会出现乱码。基于这个原因,产生了Base64算法。
Base64算法定义
Base64编码的思路说白了,就是把传输数据的每个字节映射成ASCII码表中的某些字符,这样在传输的过程中,就不会出现乱码的问题了。Base64算法定义了一个映射表,如下所示。
索引 | 编码 | 索引 | 编码 | 索引 | 编码 | 索引 | 编码 |
---|---|---|---|---|---|---|---|
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 | 63 | / |
由上表可以看出,之所以称为Base64编码,实际上是把原数据映射成了ASCII码表中的64个字符。但是,64个字符最多能映射的位数是6bit。但是每个数据是8bit的,那怎么转换呢?Base64编码的基本思想:将原数据每3个字节(24bit)分为一组,然后将这24bit数据按照每6bit一组,重新划分为4组,分组完成之后,再将每每6bit数据为单元进行映射。
Base64编码的基本流程如下:
- 将给定的数据转换成二进制编码,转换成二进制编码的方式可以是ASCII,UTF-8等。
- 对给定的编码做分组转换操作,每3个字节(24bit)分为一组,然后将这24bit划分为4组6bit。
- 对获得的4组6bit编码进行补位,向6bit编码的高位补2bit 0,变成4组8bit编码。
- 将每个8bit编码转换为十进制编码。
- 以十进制编码为索引,映射为上表中对应的字符。
Base64编码举例
当原文字节长度是3的整数倍
例如,将字符串"ABC"进行Base64编码流程如下。
- 使用ASCII编码方式将字符串"ABC"转换成二进制数据 01000001 | 01000010 | 01000011
- 将步骤1的二进制数据进行分组,每个分组6bit 010000 | 010100 | 001001 | 000011
- 将步骤2的4组6bit二进制编码数据进行补位(高位补0),变成4组8bit二进制 00010000 | 00010100 | 00001001 | 00000011
- 将步骤3中的4组8bit转换成十进制。16 | 20 | 9 | 3
- 以步骤4的十进制数据为索引,去Base64编码映射表中寻找对应的字符。16在编码表中映射的字符是Q,20映射的字符是U,9映射的字符是J,3映射的字符是D。
所以,字符串"ABC"经过Base64编码后的数据是"QUJD"。
当原文字节长度不是3的整数倍
从Base64编码的原理可以看到,Base64实际上就是把原来数据中的每3个字节一组进行Base64编码转换,编码之后变成4个Base64字符。但是如果原文数据长度不是3的整数倍的时候该怎么办呢?Base64算法规定,如果待加密数据不是3的整数倍,就在原文数据后面补0,直到长度凑够3的整数倍为止,然后再进行Base64编码转换。待编码转换完成之后,在结果末尾补充相同个数的"="。
例如,将字符串"ABCD"进行Base64编码流程如下。
- 使用ASCII编码方式将"ABC"转换成二进制 01000001 | 01000010 | 01000011 | 01000100
- 将步骤1的二进制数据进行分组,由于数据长度是4个字节,不是3的整数倍,无法直接进行分组,所以分组之前要在数据末尾进行补0,凑够23的整数倍,这里需要补充两个0字节,补充0之后,待编码数据变为01000001 | 01000010 | 01000011 | 01000100 | 00000000 | 00000000
- 将步骤2中的数据按每组6bit进行分组,010000 | 010100 | 001001 | 000011 | 010001 | 000000 | 000000 | 000000
- 将步骤3的每组6bit分组数据进行高位补0,变成8bit二进制数据分组。 00010000 | 00010100 | 00001001 | 00000011 | 00010001 | 00000000 |00000000 | 00000000
- 将步骤4中的8bit数据分组转换成十进制。16 | 20 | 9 | 3 | 17 | 0 | 0(填充) | 0(填充)
- 以步骤5中的十进制数据为索引,去Base64编码映射表中寻找对应的字符。16映射的字符是Q,20映射的字符是U,9映射的字符是J,3映射的字符是D,17映射的字符是R,0映射的字符是A,这里需要注意,上述数据最后两个字节也是0,但是,这里不能直接映射字符A,因为这两个0字节是都是填充数据,Base64规定,对于这种情况使用=来代替填充位的0字节。所以,映射的结果是"QUJDRA=="。
所以,字符串"ABC"经过Base64编码后的字符串是"QUJDRA=="。
其实这里有个规律,当原文的数据长度除以3余数为0时,编码之后后面没有"=";当余数为1时,后面有两个"=",当余数是2时,后面有一个"=","="的个数也就是补充的字节数。
传输效率
通过Base64的原理可以看到,Base64编码实际上是把原数据的3个字节映射成了4个字节,所以相比于原数据长度,编码后的长度会增加1/3。这也会降低传输效率。
Url Base64算法
Get方式和Post方式是Http请求常用的两种方式,某些情况下会要求使用Get方式来传递二进制数据。这时,可以先通过Base64编码来将二进制数据转换成字符串数据。由于符号"+"和符号"/"是不允许出现在Url中的,所以,产生了Url安全的Base64算法,所谓的Url安全的Base64算法,其实主要包含两个方面。
- 首先,"+"和"/"是不能出现在Url中的,所以Url安全的Base64算法将原映射表中的"+"和"/"替换成了"-"和"_"。
- 其次,在原来的Url算法中,当数据长度不能被3整除时,编码结果会在末尾填充"=",而在Url中,"="是有特殊含义的,所以"="不能出现在结果中。对于这个问题,目前有两种解决方案。
(1) 将"="替换为其他字符,例如,可以用其他符号替代,例如可以用"","."等符号替代,但是""与文件系统冲突,不能使用,有的文件系统会认为连续的两个"."是错误。
(2) 去掉后面的填充的"=",去掉”=“后怎么解码呢?因为Base64是把3个字节变为4个字节,所以,Base64编码的长度永远是4的倍数,因此,解码时,如果数据长度不是4的整数倍,在数据后面填充"=",把Base64字符串的长度变为4的倍数,就可以正常解码了。
由于Url Base64算法并没有形成统一的规范,有的软件可能会使用自定义的映射表。
Java Base64算法实现
目前,在Java中,我们可以通过以下方式来是使用Base64算法。
JDK类库中的Base64
在java8之前,JDK官方库中都没有内置Base64算法,其实Base64实现很简单,这个不知道为什么。但是Java8内置了Base64编码器和解码器。
在Java8中,Base64工具类提供了三种BASE64编解码器:
1.基本Base64编码
也就是完全按照标准Base64的映射规则来编解码,不添加任何行标。
public static void javaUtilBase64() {
try {
Base64.Encoder encoder = Base64.getEncoder();
String text = "AB>CD?";
byte[] textByte = text.getBytes("UTF-8");
String encodedText = encoder.encodeToString(textByte);
System.out.println(encodedText);
} catch (Exception e) {
}
}
// 输出结果:
QUI/Q0Q+
2.Url Base64编码
JDK标准类库中的Url Base64编码是用"-"和"_"取代了"+"和"/"
public static void javaUtilUrlBase64() {
try {
Base64.Encoder encoder = Base64.getUrlEncoder();
String text = "AB>CD?";
byte[] textByte = text.getBytes("UTF-8");
String encodedText = encoder.encodeToString(textByte);
System.out.println(encodedText);
} catch (Exception e) {
}
}
//输出结果:
QUI-Q0Q_
3.MIME Base64编码
Java类库中还提供了一种格式更友好的Base64编码,这种编码输出每行不超过76字符,并且使用'\r'并跟随'\n'作为分割。
public static void javaUtilMimeBase64() {
try {
Base64.Encoder encoder = Base64.getMimeEncoder();
String text = "";
for(int i=0;i<10;i++) {
text += "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
}
byte[] textByte = text.getBytes("UTF-8");
String encodedText = encoder.encodeToString(textByte);
System.out.println(encodedText);
} catch (Exception e) {
}
}
//输出结果:
QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWkFCQ0RF
RkdISUpLTE1OT1BRUlNUVVZXWFlaQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVpBQkNERUZHSElK
S0xNTk9QUVJTVFVWV1hZWkFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaQUJDREVGR0hJSktMTU5P
UFFSU1RVVldYWVpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWkFCQ0RFRkdISUpLTE1OT1BRUlNU
VVZXWFlaQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVo=
4.去除填充符的Base64
在Java标准类库中,还提供了一种方式来去除编码末尾的"=",就是在构建Encoder 对象后调用withoutPadding()方法,例如:
public static void javaUtilBase64WithoutPadding() {
try {
Base64.Encoder encoder = Base64.getEncoder().withoutPadding();
String text = "ABCD";
byte[] textByte = text.getBytes("UTF-8");
String encodedText = encoder.encodeToString(textByte);
System.out.println(encodedText);
} catch (Exception e) {
}
}
// 输出结果:
QUJDRA,如果不调用withoutPadding(),输出结果为QUJDRA=
Commons Codec中 的Base64
Commons Codec是Apache为Java开发者提供的一个开源软件类库,该类库中主要是一些常用的编码工具类包,例如DES、SHA1、MD5、Base64,URL等。在使用该类库之前需要首先在Eclipse中添加依赖。Commons Codec提供了以下Base64编码方式。
1.基本Base64编码
Commons Codec和Java标准类库提供给的Base64编码方式是一样的。
public static void commonsCodecBase64() {
try {
String text = "AB>CD?";
byte[] textByte = text.getBytes("UTF-8");
String encodedText = Base64.encodeBase64String(textByte);
System.out.println(encodedText);
} catch (Exception e) {
}
}
//输出结果:
QUI+Q0Q/
2.Url Base64编码
Url Base64编码和Java类库也是一样的,把"+"和"/"替换成了"-"和"_",有一个不同的地方是Commons Codec中的Url Base64默认去掉了后面的"=",相当于Java类库中调用了withouPadding方法,例如:
public static void commonsCodecBase64() {
try {
String text = "AB>CD?E";
byte[] textByte = text.getBytes("UTF-8");
String encodedText = Base64.encodeBase64URLSafeString(textByte);
System.out.println(encodedText);
} catch (Exception e) {
}
}
//输出结果:
QUI-Q0Q_RQ
3.类MIME格式输出
Commons Codec中也提供了类似于Java类库中的MIME的格式化输出,在Commons Codec中有一个方法:
encodeBase64(final byte[] binaryData, final boolean isChunked)
这里的isChunked置为true,就表示是按照MIME格式输出编码结果。
public static void commonsCodecBase64() {
try {
String text = "";
for(int i=0;i<10;i++ ) {
text += "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
}
byte[] textByte = text.getBytes("UTF-8");
byte[] encodedTextByte = Base64.encodeBase64(textByte, true);
String encodedText= new String(encodedTextByte,"UTF-8");
System.out.println(encodedText);
} catch (Exception e) {
}
}
//输出结果
QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWkFCQ0RF
RkdISUpLTE1OT1BRUlNUVVZXWFlaQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVpBQkNERUZHSElK
S0xNTk9QUVJTVFVWV1hZWkFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaQUJDREVGR0hJSktMTU5P
UFFSU1RVVldYWVpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWkFCQ0RFRkdISUpLTE1OT1BRUlNU
VVZXWFlaQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVo=
h