Base64
其实就是二进制数据以字符串的形式表现出来。你可以完全这样理解: Base64
就是数据的多种表现方式之一。还有一个理解误区就是,它由于阅读上的不便利性,使很多人会这样说: “把数据使用Base64加密”。严格来说,Base64
并不是加密,毕竟可以完全由Base64
内容解析得到原文内容嘛。
既然 Base64
可以把二进制数据以字符串的形式表现出来,而我们计算机的任何数据都是以二进制保存的,所以说,计算机里的任何数据都可以使用 Base64
来表现。而由于其阅读的不便利性,往往我们会使用这项技术来进行数据交换的时候使用。常见的场景就是通过 Http
请求交换 Base64
数据。
一、Base64 的编码方法
要灵活的、正确的使用 Base64
来进行数据交换,首先我们必须要知道 Base64
数据本身是如何由原始的二进制数据编码成为Base64数据的。
1、原理
把源数据转换成二进制后,每三个8位(也就是每24位)的数据转换为四个6位的数据(3×8 == 4×6 == 24),然后把转换后的每个6位前面(高位)补两个0,形成四个八位。这就是是为什么Base64结果会比原始数据大33%。
这么说可能很迷糊,看看下面的图你就知道怎么转换了:
我们尝试将这段数据(三个8位的数据)进行转换:10110101 00101011 10101111
通过这个动画,相信你一看就知道如何将原来的 三个8位 转换为最后的 四个8位,现在,我们只需要将转换后的结果:00101101 00010010 00101110 00101111
转换为字符就成功了。那么如何把这些转换为字符呢。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 |
现在我们把转换后的四个8位,每个8位按 二进制值转换位十进制值的方法计算出结果,即:
00101101=45, 00010010=18, 00101110=46, 00101111=47
最终通过对照表取出对于字符,可以得到我们的原始数据转换为Base64后为: tSuv
如何由二进制计算得到十进制结果,可参考:《【二进制】二进制的认识和使用》。
2、进阶
上面的转换是建立在原始数据刚好是三个8位(24位)的基础上,而有时候,我们的数据不一定能平均的被分成三个八位,有可能分到最后的时候发现最后只有了一个8位或者两个8位。由于Base64
编码总是以三个8位输入,所以,当数据结尾发现不足三个8位时,在后面通过继续补0,以达到三个八位。
如果数据最终只有两个8位,则继续在后面补0,计算后的结果应是3个数据字符加一个"="号
如果数据最终只有一个8位,则继续在后面补0,计算后的结果应是2个数据字符加两个"="号
当然了,如果数据最终正好三个8位,也就不用填充,结尾没有"="号。
3、你要小心
通过整个Base64
编码过程的了解,你发现,在Base64
编码本上上不存在具体的什么格式之说,也就是只要二进制数据确定,无论在什么语言,什么平台上,应该出来的结果都一样。而实际上,有朋友在对数据进行Base64
处理的时候发现自己的结果和别人的结果不一样,这又是着怎么导致的呢?
这里你就必须细心了,要知道,Base64
只处理二进制到最终结果,不会处理 源数据怎么转换成二进制的。如果你发现你都结果和别人的结果不一样,那么一定是在把源数据转换为二进制数据的时候使用了不同的转换编码或方式。
二、Base64 的优劣
由于Base64
的不便于阅读性,我们的却能够将一部分敏感参数通过Base64的格式进行传递,这在一定程度上增加了安全性。但由于Base64
编码数据会比原始数据大33%,导致用户会为这多出的33%付出流量费用,但在大多数情况下,这都是微乎其微的。
三、使用代码实现
在了解了Base64
的原理之后,相信你都可以不依靠公共方法自己实现如何把源数据转换成Base64
结果了。
1、Java
这份Java代码是笔者根据Base64
原理自己实现的,主要体现实现思路和过程:
// 先定义好数据对应字符的映射。
private static char[] cs = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray();
public static String encode(byte[] src) {
StringBuilder sbu = new StringBuilder();
for (int i = 0; i < src.length; i+=3) {
byte byte1 = src[i];
byte byte2 = -1;
byte byte3 = -1;
// 这里有判断,就是为了处理数据结尾存在位数不足的。
// 待会儿就好补"="号。
boolean noByte2 = false;
boolean noByte3 = false;
if ((i+1) >= src.length) {
noByte2 = true;
} else {
byte2 = src[i + 1];
}
if ((i+2) >= src.length) {
noByte3 = true;
} else {
byte3 = src[i + 2];
}
// 现在,根据这三个 byte, 得到转换后的4个Base64 的byte。
// 奥里给↓
// Base64 第一个值,取byte1高6位。直接 byte1 右移2位即可,高位通过 & 00111111 实现补0. 00111111 就是 0x3F
byte base64Byte1 = (byte)((byte1 >> 2) & 0x3F); // >> 右移运算符,返回int类型,所以这里强制转换一下。
// Base64 第二个值,取 byte1 的低2位和 byte2 的高4位结合。如果没有byte2 则可以不用结合,后面会全是0
byte base64Byte2;
if (noByte2) {
// 没有byte2,只需要取 byte1 的低2位。 将 byte1 左移 4 位,再把高位置为0.后面的自动是0
base64Byte2 = (byte)((byte1<<4) & 0xFF);
} else {
// 有byte2,就的让byte1和byte2部分值进行拼接,这里是这样的:
// 假如 byte1=10011011 byte2=11100101 则我们要的结果是:00111110
// 下面的代码是这个逻辑:
// 1、10011011 << 4 得到:10110000,使用0xFF是为了消除更高位的1,因为 << 运算符返回int型,通过 &0xFF 来消除这些高位。
// 2、11100101 >> 4 得到:11111110, 使用0xF去掉高4位的1,得到:00001110
// 3、10110000 | 00001110 得到:10111110
// 4、10111110 & 0x3F 消除高2位 得到最终需要的结果: 00111110
base64Byte2 = (byte)((((byte1<<4) & 0xFF) | ((byte2>>4)&0xF)) & 0x3F);
}
// Base64 第三个值,取byte2的低4位与byte3的高2位结合。但如果 byte2 和 byte3 都没有,则使用 127 表示这是填充位。
byte base64Byte3;
if (noByte2 && noByte3) {
base64Byte3 = 127;
} else if (!noByte2 && noByte3) {
// 有byte2,没有byte3,只需要取byte2的低四位即可。
base64Byte3 = (byte)((byte2<<2) & 0x3F);
} else {
// byte2 和 byte3 都有,取 byte2 的 低四位 和 byte3 的高2位结合。
base64Byte3 = (byte)((((byte2<<2) & 0x3F)|((byte3>>6)&0x3)));
}
// Base64 第四个值,取 byte3 的 后6位即可。如果byte3没有值,则使用127占位表示这是填充位。
byte base64Byte4;
if (noByte3) {
base64Byte4 = 127;
} else {
base64Byte4 = (byte)(byte3&0x3f);
}
// 开始根据值从字符映射表中进行选字符填充。
sbu.append(cs[base64Byte1]);
sbu.append(cs[base64Byte2]);
// 对于可能为填充位的数,使用“=”号填充。
if (base64Byte3 == 127) {
sbu.append("=");
} else {
sbu.append(cs[base64Byte3]);
}
if (base64Byte4 == 127) {
sbu.append("=");
} else {
sbu.append(cs[base64Byte4]);
}
}
return sbu.toString();
}
这份代码严格来说并不能算是标准的实现方法,但它显示了实现过程。现在不妨试试一结果:
public static void main(String[] args) throws Exception {
String src = "Base64加密测试。";
String encode = encode(src.getBytes("UTF-8"));
System.out.println(src + "\r\n结果:" + encode);
// 输出:
// Base64加密测试。
// 结果:QmFzZTY05Yqg5a+G5rWL6K+V44CC
}
三、更多
本文仅做了一些简单入门,在ref2045协议之还规定了Base64
的输出结果必须每76个字符就换一行,而本文并未做出此行为。更详细的规范定义你可以通过rfc2045定义文档查到,本文让你基本了解了Base64
,但你仍然需要通过其原文定义文档来了解更深入的注意事项。