为什么当数据采用base64位编码传输时,传输的数据大小比真实文件大33%?这个在邮件传输时非常明显,经常碰到用户问为什么附件大小只有15M多一点,我服务器允许最大接收邮件时20M,但是邮件因为大小原因被拒绝。
为什么需要base64
ASCII码一共规定了128个字符的编码,这128个符号,范围在[0,127]之间.其中,[0,31],及127, 33个属于不可打印的控制字符.
在电子邮件传输信息时,有些邮件网关会把[0,31]这些控制字符给悄悄清除.还有的早期程序,收到[128,255]之间的国际字符时,甚至会发生错误.
如何在不同邮件网关之间安全的传输控制字符,国际字符,甚至二进制文件?于是作为MIME多媒体电子邮件标准的一部分—base64被开发出来.
什么是base64
Base64是网络上最常见的用于传输8Bit字节码的编码方式之一,Base64就是一种基于64个可打印字符来表示二进制数据的方法。可查看RFC2045~RFC2049,上面有MIME的详细规范。
在encoding文章中我提到每个国家读通过不同的方式在ASCII基础上扩展自己的文字编码。既然每个国家都有自己的编码表了,问题也就来了。现在都国际化了,我要用一个支持本国语言的编码系统,打开另一个编码系统编码的文本,会出现什么情况呢?这就是乱码了… 更为严重的是,随着互联网的出现,各个国家的电脑都需要通信,而通信的一种方式就是使用URL地址。每个国家都希望把这个地址写成自己国家的语言。但这会导致其他国家根本没法访问地址,因为打不出这个字符嘛。所以,人类迫切需要一种中间编码形式,既能够兼容ASCII码,又能够把任意一种编码形式转换成只使用可读字符就能表示的编码。
其中一种编码形式,就是Base64编码。
Base64编码,顾名思义,用64个可读字符进行编码。与Hex的16个字符(0-9,A-F)相比多了很多,但是比ASCII码又少了一倍,去除了不可读字符。标准Base64编码中,这些字符是:
数字(10个):0,1,2,3,4,5,6,7,8,9
小写字母(26个):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
大写字母(26个):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
加号以及斜杠(2个):+,/
有的时候,根据不同的需要,Base64还有很多变种。比如,如果浏览器地址中用“+”和“/”的话,浏览器会将其转换为%XX的形式,又多了一步。因此可以将“+”和“/”换成“-”和“_”。
这种编码形式长度也短,效率也高。这样一来,数据通信的时候,不管来的是什么语言,都转化成Base64后再发送和接收。要是别国地址什么的打不出来,就直接打Base64编码形式就好了。
原理
对传输8Bit字节码的二进制数据进行处理,每3个字节一组,一共是3x8=24bit,划为4组,每组正好6个bit:

这样我们得到4个6bit数字作为索引,然后计算机是一个字节(8bit)存数,6bit不够,自动就补两个高位0了。 然后查下面表,获得相应的4个字符,就是base64编码后的字符串。

所以,Base64编码会把3字节的二进制数据编码为4字节的文本数据,长度增加33%,好处是编码后的文本数据可以在邮件正文、网页等直接显示。
如果要编码的二进制数据不是3的倍数,最后会剩下1个或2个字节怎么办?Base64用\x00字节在末尾补足后,再在编码的末尾加上1个或2个=号,表示补了多少字节,解码的时候,会自动去掉。
转码过程例子:将字符s13编码成base64
字符:s 1 3
ascii:115 49 51
2进制: 01110011 00110001 00110011
6位一组(4组): **011100**110011**000100**110011
然后才有后面的: 011100 110011 000100 110011
高位补0: 00011100 00110011 00000100 00110011
得到: 28 51 4 51
查对下照表: c z E z
base64在线编码和解码
http://www.webatic.com/run/convert/base64.php
应用
用作HTTP表单和HTTP GET URL中的参数。
用作MIME格式邮件SMTP传输
用Base64来保密电子邮件密码
代码实现
JavaScript
if (!Shotgun)
var Shotgun = {};
if (!Shotgun.Js)
Shotgun.Js = {};
Shotgun.Js.Base64 = {
_table: [
'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', '+', '/'
],
encode: function (bin) {
var codes = [];
var un = 0;
un = bin.length % 3;
if (un == 1)
bin.push(0, 0);
else if (un == 2)
bin.push(0);
for (var i = 2; i < bin.length; i += 3) {
var c = bin[i - 2] << 16;
c |= bin[i - 1] << 8;
c |= bin[i];
codes.push(this._table[c >> 18 & 0x3f]);
codes.push(this._table[c >> 12 & 0x3f]);
codes.push(this._table[c >> 6 & 0x3f]);
codes.push(this._table[c & 0x3f]);
}
if (un >= 1) {
codes[codes.length - 1] = "=";
bin.pop();
}
if (un == 1) {
codes[codes.length - 2] = "=";
bin.pop();
}
return codes.join("");
},
decode: function (base64Str) {
var i = 0;
var bin = [];
var x = 0, code = 0, eq = 0;
while (i < base64Str.length) {
var c = base64Str.charAt(i++);
var idx = this._table.indexOf(c);
if (idx == -1) {
switch (c) {
case '=': idx = 0; eq++; break;
case ' ':
case '\n':
case "\r":
case '\t':
continue;
default:
throw { "message": "\u0062\u0061\u0073\u0065\u0036\u0034\u002E\u0074\u0068\u0065\u002D\u0078\u002E\u0063\u006E\u0020\u0045\u0072\u0072\u006F\u0072\u003A\u65E0\u6548\u7F16\u7801\uFF1A" + c };
}
}
if (eq > 0 && idx != 0)
throw { "message": "\u0062\u0061\u0073\u0065\u0036\u0034\u002E\u0074\u0068\u0065\u002D\u0078\u002E\u0063\u006E\u0020\u0045\u0072\u0072\u006F\u0072\u003A\u7F16\u7801\u683C\u5F0F\u9519\u8BEF\uFF01" };
code = code << 6 | idx;
if (++x != 4)
continue;
bin.push(code >> 16);
bin.push(code >> 8 & 0xff);
bin.push(code & 0xff)
code = x = 0;
}
if (code != 0)
throw { "message": "\u0062\u0061\u0073\u0065\u0036\u0034\u002E\u0074\u0068\u0065\u002D\u0078\u002E\u0063\u006E\u0020\u0045\u0072\u0072\u006F\u0072\u003A\u7F16\u7801\u6570\u636E\u957F\u5EA6\u9519\u8BEF" };
if (eq == 1)
bin.pop();
else if (eq == 2) {
bin.pop();
bin.pop();
} else if (eq > 2)
throw { "message": "\u0062\u0061\u0073\u0065\u0036\u0034\u002E\u0074\u0068\u0065\u002D\u0078\u002E\u0063\u006E\u0020\u0045\u0072\u0072\u006F\u0072\u003A\u7F16\u7801\u683C\u5F0F\u9519\u8BEF\uFF01" };
return bin;
}
};
BASH
base64Table=(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 + /);
function str2binary() {
idx=0;
for((i=0; i<${#str}; i++)); do
dividend=$(printf "%d" "'${str:i:1}");
for((j=0;j<8;j++)); do
let idx=8*i+7-j;
let bin[$idx]=$dividend%2;
dividend=$dividend/2;
done;
done;
let idx=${#str}*8;
for((i=0; i<appendEqualCnt*2; i++)); do
let bin[$idx]=0;
let idx++;
done;
}
function calcBase64() {
for((i=0; i<${#bin[*]}/6; i++)); do
sum=0;
for((j=0; j<6; j++)); do
let idx=i*6+j;
let n=6-1-j;
let sum=sum+${bin[$idx]}*2**n;
done;
echo -n ${base64Table[$sum]};
done
}
declare -a bin
function base64Encode() {
read -p "please enter ASCII string:" str;
let appendZero=${#str}*8%6;
let bits=${#str}*8;
appendEqualCnt=0;
if [[ $appendZero -ne 0 ]]; then
let appendEqualCnt=(6-$appendZero)/2;
fi
str2binary;
calcBase64;
if [[ $appendEqualCnt -eq 2 ]]; then
echo -n "==";
elif [[ $appendEqualCnt -eq 1 ]]; then
echo -n "=";
fi
echo;
}
Java
import java.util.Base64;
对于标准的Base64:
加密为字符串使用Base64.getEncoder().encodeToString();
加密为字节数组使用Base64.getEncoder().encode();
解密使用Base64.getDecoder().decode();
对于URL安全或MIME的Base64,只需将上述getEncoder()getDecoder()更换为getUrlEncoder()getUrlDecoder()
或getMimeEncoder()和getMimeDecoder()即可。