1. Base64的由来
Base64最早用于解决电子邮件传输问题。由于“历史问题”,早期的电子邮件网关只允许传输ASCII(二进制为00000000-01111111)字符,如果有非ASCII字符经过这种网关时, 字符的二进制位可能会被篡改(如将10000001改为00000001)。由此产生了Base64编码来保证非ASCII字符的传输。
2. 原理
Base64顾名思义是一种基于64个字符的编码算法。
如下是Base64的字符映射表,详情参见RFC 2045
Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding | |||
---|---|---|---|---|---|---|---|---|---|---|
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 | (pad) | = | |||
15 | P | 32 | g | 49 | x | |||||
16 | Q | 33 | h | 50 | y |
其中的value是10进制的值,Encoding是与之相对应的编码字符
因为Base64中共有64个字符,所以只需要6个bit就可以表示一个base64字符(*1<<6=64*)。另外,由于计算机基本处理单位为字节,所以字符编码是以字节为单位对字符进行编码,比如,字符’A’的ASCII编码为01000001
,汉字’你’的UTF-8编码为11100100 10111101 10100000
, GBK编码为11000100 11100011
。由此,Base64的编码步骤如下:
step1 将待转码字符串以某种编码格式(如UTF-8)进行编码,得到一个字节数组
step2 将字节数组按三个字节为一组进行分组(24个bit)
step3 将每个分组按6 bit进行划分(不足6位时低位补0),得到N(*2<=N<=4*)个6位二进制码(以下称为Base64编码单元)。
step4 将每个Base64编码单元转换为10进制值,并根据上表得到相应的Base64编码字符,不足4个编码单元的分组要用=进行填充。
Base64编码过程示例 | - |
---|---|
原始字符串 | 我x |
UTF-8编码,分组 | 0xe6 0x88 0x91; 0x78 |
二进制表示 | 11100110, 10001000, 10010001; 01111000 |
划分编码单元 | 111001,101000,100010,010001; 011110,0000000(补位) |
Value | 57,40,34,17; 30,0 |
对应Encoding | 5,o,i,R; e,A, =,=(填充符) |
最终结果 | 5oiReA== |
3. 用途
1) 保存二进制数据,如密钥等。
2) 使用Http协议在url中传输二进制数据。
将Base64编码中不符合URL规定的字符替换成其他合法的字符,并去掉回车换行就得到了UrlBase64编码。经过UrlBase64编码,就可以将二进制参数直接放在url中。
这些功能也可以用hex编码实现,但base64编码的结果要比hex结果短。
4. 实现
jdk中的类sun.misc.BASE64Encoder
和sun.misc.BASE64Decoder
提供了Base64的编码解码实现,但jdk中sun开头的包是sun公司的内部实现,该包下的代码不保证与其他jave平台的兼容性(见Why Developers Should Not Write Programs That Call ‘sun’ Packages),所以需要使用第三方实现,也可将这两个类复制到自己的代码中。
笔者测试环境中使用的maven依赖如下:
<!--bouncycastle依赖-->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk16</artifactId>
<version>1.46</version>
</dependency>
<!--commons-codec-->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.10</version>
</dependency>
4.1 BouncyCastle实现
包org.bouncycastle.util.encoders
下提供了工具类Base64,UrlBase64与Hex用于Base64,UrlBase64与hex编码/解码操作。
final String charset="UTF-8";
String input="Base64编/解码";
//编码
String encoded=new String(Base64.encode(input.getBytes(charset)),"ASCII");
String urlSafeEncoded=new String(UrlBase64.encode(input.getBytes(charset)),"ASCII");
//解码
Assert.assertEquals(input,new String(Base64.decode(encoded),charset));
Assert.assertEquals(input,new String(UrlBase64.decode(urlSafeEncoded),charset));
4.2 commons-codec实现
工具类org.apache.commons.codec.binary.Base64
提供了Base64与UrlBase64的编码解码方法。
final String charset="UTF-8";
String input="Base64编/解码";
String encoded= Base64.encodeBase64String(input.getBytes(charset));
String urlSafeEncoded=Base64.encodeBase64URLSafeString(input.getBytes(charset));
Assert.assertEquals(input, new String(Base64.decodeBase64(encoded), charset));
Assert.assertEquals(input, new String(Base64.decodeBase64(urlSafeEncoded), charset));
4.3 BouncyCastle与commons-codec区别
标准Base64编码后的字符串每76个字符为一行(称为分块),行尾要添加一个回车换行符“\r\n”。但bouncycastle并没有这样做,而commons-codec支持标准实现,但默认并没有使用。
++++++++++++ | BouncyCastle | commons-codec |
---|---|---|
标准(分块)编码 | 不支持 | Base64.encodeBase64Chunked 或 Base64.encodeBase64(binaryData, true) |
UrlBase64 | 没有回车换行,‘+’变为’-‘, ’/‘变为’_‘, ’=‘变为’.’ | 与bouncyCastle相同,但去掉了填充符 |