本文共 4757 字,大约阅读时间需要 15 分钟。
在 Java8中 Base64编码已经成为Java类库的标准,且内置了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编码已经成为Java类库的标准,且内置了Base64编码的编码器和解码器。
新的Base64API也支持URL和MINE的编码解码。我们直接调用即可。
java.util.Base64 类仅由用于获得Base64编码方案的编码器和解码器的静态方法组成。
static | () 返回解码使用型base64编码方案。 |
static | () 返回一个编码使用型base64编码方案。 |
static | () 返回一个解码使用型BASE64解码方案。 |
static | () 返回一个编码使用型base64编码方案。 |
static | (int lineLength, byte[] lineSeparator) 返回一个 ,它使用具有指定行长度和行分隔符的类型base64编码方案进行编码。 |
static | () 返回解码使用型base64编码方案。 |
static | () 返回一个编码使用型base64编码方案。 |
java.util.Base64.Encoder 类使用RFC 4648和RFC 2045中规定的Base64编码方案来实现用于编码字节数据的编码器。
byte[] | (byte[] src) 使用编码方案将指定字节数组中的所有字节编码为新分配的字节数组。 |
int | (byte[] src, byte[] dst) 使用编码方案对来自指定字节数组的所有字节进行编码,将生成的字节写入给定的输出字节数组,从偏移0开始。 |
| ( buffer) 使用编码方案将所有剩余字节从指定的字节缓冲区编码到新分配的ByteBuffer中。 |
| (byte[] src) 使用编码方案将指定的字节数组编码为字符串。 |
| () 返回一个编码器实例,编码器等效于此编码器实例,但不会在编码字节数据的末尾添加任何填充字符。 |
| ( os) 使用编码方案包装用于编码字节数据的输出流。 |
java.util.Base64.Decoder 该类使用RFC 4648和RFC 2045中规定的Base64编码方案来实现用于解码字节数据的解码器。
byte[] | (byte[] src) 使用编码方案从输入字节数组中解码所有字节,将结果写入新分配的输出字节数组。 |
int | (byte[] src, byte[] dst) 使用编码方案从输入字节数组中解码所有字节,将结果写入给定的输出字节数组,从偏移0开始。 |
| ( buffer) 使用编码方案从输入字节缓冲区中解码所有字节,将结果写入新分配的ByteBuffer。 |
byte[] | ( src) 使用编码方案将Base64编码的字符串解码为新分配的字节数组。 |
| ( is) 返回一个输入流,用于解码编码字节流。 |
加密字符串测试 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进行解码,没有任何问题,不再演示
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~转载地址:http://hncgn.baihongyu.com/