1. 概述
- 在邮件时代,人们发的邮件中,会有一些不可见的字符,如换行符,制表符等等,在邮件传输过程中,一些 路由器或者是一些DNS会把这些不可见的字符给处理掉,到达收件人手术就是一堆乱码了。于是人们想,只要 把这些不可见不可打印的字符给映射成可见字符不就可以了吗?
- 于是就有个这个Base64算法。严格来说,Base64不是一个加密的算法,它只是把二进制数据映射成64个可打印的字符而已,虽然非明文,但是根据其编码方式还是可以还原数据,因此它只算是一种编码方式。
2. 编码
- 64个可打印的字符,大写A-Z,小写a-z,数字0-9,一共是26+26+10=62,加上
+/
来凑数,凑够64。
编号 | 字符 | 编号 | 字符 | 编号 | 字符 | 编号 | 字符 |
00 | A | 16 | Q | 32 | g | 48 | w |
01 | B | 17 | R | 33 | h | 49 | x |
02 | C | 18 | S | 34 | i | 50 | y |
03 | D | 19 | T | 35 | j | 51 | z |
04 | E | 20 | U | 36 | k | 52 | 0 |
05 | F | 21 | V | 37 | l | 53 | 1 |
06 | G | 22 | W | 38 | m | 54 | 2 |
07 | H | 23 | X | 39 | n | 55 | 3 |
08 | I | 24 | Y | 40 | o | 56 | 4 |
09 | 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 | / |
- 我们知道,网络中所有的数据都是二进制进行传输,那么给出一段数据,需要我们转换成如上的64个中的一个, 由于2^6=64,所以我们把二级制数据中,每6位取出,映射到上面的表中,这样不断下去,就能得到我们的 base64编码。
- 而,计算机中,每8个bit为一个Byte(字节),所以3个字节有24个bit刚好可以映射出4个 base64编码。咦,那样来说,如果我一个文件本来的就是3个字节,而用了base64编码后,不就 变成了4个字符,一个字符8个bit,也就是32个bit,由原来的24bit->32bit,数据量变大了! 是的没错,大了1/3。所以,使用base64后,其大小是原来的4/3。
- 在实际编码中,我们的文本中的字符个数不一定是8的个数,总的bit数也不一定是6的倍数。所以 有以下的约定:
- 文本结尾,剩余2个字符:2*8=16bit,16%6=4,余4个二进制位就补一个=
- 文本结尾,剩余1个字符:1*8=8bit,8%6=2,余2个二进制位就补2个=
示例:
- 对字符个数是3的倍数(N*8%6=0)的字符串进行Base64编码
原文: p h p
对应ASCII: 01110000 01101000 01110000
每6位分割: 011100 000110 100001 110000
对应10进制值: 28 6 33 48
Base64对应的字符:c G h w
复制代码
- 对字符个数为N*8%6=2的字符串,即剩余2个字符,进行base64编码
原文: r a n l
ASCII值:01110010 01100001 01101110 01101100
每6位分割:011100 100110 000101 101110 011011 000000 xxxxxx xxxxxx
对应10进制值:28 38 5 46 27 0 = =
对应base64字符: c m F u b A = =
复制代码
- 对字符个数为N*8%6=4的字符串,即剩余1个字符,进行base64编码
原文 h a c k e
ASCII值: 01101000 01100001 01100011 01101011 01100101
每6位分割: 011010 000110 000101 100011 011010 110110 0101 00 xxxxxx
对应10进制值: 26 6 5 35 26 54 20 =
对应base64字符: a G F j a 2 U =
复制代码
3. 解码
知道了编码的原理,解码就很容易了。是其逆过程。 大概是:
- 根据编码表,找出对应的10进制编号,
- 然后对编号进行2进制化,
- 把所有的2进制进行每八位拼接,得到ASCII值,即可。
4. Talk is cheap show me the code!
- 编码,知道了原理,编码就很容易实现,如下(Java版):
private static final char[] baseChars = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/',
};
/**
* Base64编码
* @param s 字符串
* @return 结果
*/
public static String enCode64(String s) {
int index = 0;
StringBuilder strBuff = new StringBuilder();
StringBuilder resultBuff = new StringBuilder();
while (true) {
//是否结束
if (index == -1) {
switch (s.length()*8%6) {
case 2:
resultBuff.append('=').append('=');
break;
case 4:
resultBuff.append('=');
break;
}
break;
}
//判断是否需要补零
if (index>=s.length()) {
int zeroCount = 6 - strBuff.length();
for (int i = 0; i < zeroCount; i++) {
strBuff.append('0');
}
index = -1;
}
//更新二进制缓冲区
if (strBuff.length()<6) {
strBuff.append(getBitStr(s.charAt(index++)));
}
//从缓冲区取6个字符出来
String temp2 = strBuff.substring(0,6);
int temp10 = Integer.parseInt(temp2, 2);
resultBuff.append(baseChars[temp10]);
strBuff.delete(0, 6);
}
return resultBuff.toString();
}
/**
* 获取对应字符的ASCII码的二级制序列
* 八位,不足前面补零
* @param i 字符
* @return 二进制序列
*/
public static String getBitStr(char i) {
int ascii = (int) i;
//其中0表示补零而不是补空格,8表示至少8位
StringBuilder s = new StringBuilder(Integer.toBinaryString(ascii));
if (s.length()<8) {
int zeroCount = 8 - s.length();
for (int j = 0; j < zeroCount; j++) {
s.insert(0, '0');
}
}
return s.toString();
}
复制代码
这个可以直接拿去用,我都测试过了。在Java平台上,一般是使用sun的包去做base64的编解码,如下:
public static String encode(byte[] data){
return new sun.misc.BASE64Encoder().encode(data);
}
复制代码
- 解码,解码就是逆过程
/**
* Base64解码
* @param s 编码好的base64
* @return 结果
*/
public static String decode(String s) {
int index = 0;
StringBuilder strBuff = new StringBuilder();
StringBuilder resultBuff = new StringBuilder();
while (true) {
//补零
if (index==s.length() || s.charAt(index)=='=') {
int zeroCount = 8 - strBuff.length();
for (int i = 0; i < zeroCount; i++) {
strBuff.append('0');
}
index = -1;
}
//更新缓冲区字符
while (index!=-1 && strBuff.length()<8 && index<s.length()) {
int chatIndex = getCharIndexInBaseChars(s.charAt(index++));
strBuff.append(get6BitStr(chatIndex));
}
//从缓冲区取8个字符
String temp2 = strBuff.substring(0,8);
int temp10 = Integer.valueOf(temp2,2);
resultBuff.append((char)temp10);
strBuff.delete(0, 8);
//判断是否结束
if (index==-1)
break;
}
return resultBuff.toString().trim();
}
/**
* 得到某个字符,在编码表的位置
* @param i 字符
* @return 编号
*/
private static int getCharIndexInBaseChars(char i) {
int ascii = (int) i;
if (ascii == 43) //'+'
return 62;
else if (ascii == 47) //'/'
return 63;
else if (ascii>=48 && ascii<=57) // 0-9
return ascii+4;
else if (ascii>=65 && ascii<=90) // A-Z
return ascii-65;
else if (ascii>=97 && ascii<=122) // a-z
return ascii-71;
else
return -1;
}
private static String get6BitStr(int ascii) {
StringBuilder s = new StringBuilder(Integer.toBinaryString(ascii));
if (s.length()<6) {
int zeroCount = 6 - s.length();
for (int j = 0; j < zeroCount; j++) {
s.insert(0, '0');
}
}
return s.toString();
}
复制代码
- 而一般Java上的解码也是通过sun包:
public static byte[] decode(String str) {
byte[] bt = null;
try {
sun.misc.BASE64Decoder decoder = new sun.misc.BASE64Decoder();
bt = decoder.decodeBuffer(str);
} catch (IOException e) {
e.printStackTrace();
}
return bt;
}
复制代码