网络信息传递时,一些二进制资源往往需要转换为Base64编码进行传输,以提高传输效率,例如webpack提供了将图片格式文件转换为Base64格式,下面将简单介绍Base64编码的原理和实现过程。
Base64编码原理
Base64是一种基于64个可打印字符来表示二进制数据的表示方法。base64要求将每三个8bits字节转换为四个6bit的字节(3 * 8 = 4 * 6 = 24),然后将转换后的6bit往高位添加2个0,组成4个8bit的字节,再根据这4个8bit字节的十进制在索引表中查找对应的值,此时得到的结果就是Base64值。
因此,理论上,转换后的字符串的长度要比原来的字符串长度长1/3,例如:转换前的4*6二进制字符串为:aaaaaa bbbbbb cccccc dddddd,在每个字节高位添加两个零后就是: 00aaaaaa 00bbbbbb 00cccccc 00dddddd,此时长度就比原字符串长度多了1/3。
一下是一段话编码为Base64前后的变化:
编码前:
Man is distinguished, not only by his reason, but by this singular passion from
other animals, which is a lust of the mind, that by a perseverance of delight
in the continued and indefatigable generation of knowledge, exceeds the short
vehemence of any carnal pleasure.
编码后:
TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz
IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg
dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu
dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo
ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=
前面提到了Base64算法的索引表,Base64的索引表由64个ASCII字符组成:0-9,26个英文小写字母a-z,26个英文大写字母:A-Z,除此之外还有额外两个字符”+”和”/”。下面是Base64的索引表:
理论上上面的索引表还要加入padding:”=”,前面提到一个字符串转成Base64时是生成4个字符,如果待转换的字符串转换后不足4个Base64字符,则空白的地方需要使用“=”补充,看下面的例子就能明白:
文本”M”转换为Base64的过程:
在这里,”M”转换后的ASCII不足以填充够24位二进制字符串,再第二个字节之后,全部用”0”补充,得到的索引值即分别为”T”,”Q”,”=”,”=”,最终的Base64编码结果为”TQ==”.
再来看两个个例子加深印象,字符串”Ma”:
转换后的Base64结果为:”TWE=”。
字符串”Man”:
转换后结果为”TWFu”。
这里进行一个小结,普通字符串文本转换为Base64的基本过程为:
字符串对应的字符转换为ASCII→将该ASCII转换为8位二进制→将转换的8位二进制进行6bit拆分,高位填充0,形成新的二进制→根据新的二进制从Base64索引表查找结果
Base64编码的实现
了解了Base64的转换规则后,我们通过JavaScript来简单实现Base的编码
以及解码
:
var Base64 = {
_keyStr: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
//将字符串转换为Base64
encode: function(input) {
var output = "";
var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
var i = 0;
while (i < input.length) {
chr1 = input.charCodeAt(i++);
chr2 = input.charCodeAt(i++);
chr3 = input.charCodeAt(i++);
enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;
if (isNaN(chr2)) {
enc3 = enc4 = 64;
} else if (isNaN(chr3)) {
enc4 = 64;
}
output = output + this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) + this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
}
return output;
},
//将Base64解码为可读字符串
decode: function(input) {
var output = "";
var chr1, chr2, chr3;
var enc1, enc2, enc3, enc4;
var i = 0;
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
while (i < input.length) {
enc1 = this._keyStr.indexOf(input.charAt(i++));
enc2 = this._keyStr.indexOf(input.charAt(i++));
enc3 = this._keyStr.indexOf(input.charAt(i++));
enc4 = this._keyStr.indexOf(input.charAt(i++));
chr1 = (enc1 << 2) | (enc2 >> 4);
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
chr3 = ((enc3 & 3) << 6) | enc4;
output = output + String.fromCharCode(chr1);
if (enc3 != 64) {
output = output + String.fromCharCode(chr2);
}
if (enc4 != 64) {
output = output + String.fromCharCode(chr3);
}
}
return output;
}
}
首先定义一个Base64的索引值_keyStr
,其中包含了A-/的64个字符,最后还加上填充值’=’,索引值供编码以及解码时使用。
首先看编码方法(encode),定义输入原始字符串的连续三个字符chr1,chr2,chr3,输出的4个二进制字符enc1,enc2,enc3,enc4,通过一个变量i
遍历输入的原始字符串,以原始字符串“abcd”为例:
第一次循环: 首先将第一次循环的三个字符转变为ASCII的十进制模式
chr1 = input.charCodeAt(i++);//chr1:a→97
chr2 = input.charCodeAt(i++);//chr2:b→98
chr3 = input.charCodeAt(i++);//chr3:c→99
console.log(chr1)//97(0110 0001)
console.log(chr2)//98(0110 0010)
console.log(chr3)//99(0110 0011)
有了ASCII的十进制值后,即可使用位运算
,将 3 * 8 的二进制拆分为4 * 6的二进制模式,下面一步一步来看位运算是如何实现4 * 6拆分的:
enc1 = chr1 >> 2;
/*计算enc1:将chr1右移两位:0110 0001→0001 1000*/
console.log(enc1)//00011000
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
/*计算enc2:
/*取chr1的最后两位作为enc2的最高位,取chr2的高四位作为enc2的低四位,将两者通过或运算结合:
/*( chr1 & 3 ) << 4= (0110 0001 & 0000 0011) << 4 = (00000001)<<4 = 0001 0000,
/*(chr2 >> 4) = (0110 0010) >> 4 = 00000110,
/*enc2=0001 0000 | 0000 0110=0001 0110
/*console.log(enc2)//00010110
*/
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
/*计算enc3:
/*取chr2的低四位和chr3的高两位组成enc2的低六位(高两位补零):
/*(chr2 & 15) << 2 = (0110 0010 & 0000 1111) << 2 = 0000 0010 << 2 = 0000 1000,
/*chr3 >> 6 = 0110 0011 >> 6 = 0000 0001,
/*enc3 = 0000 1000 | 0000 0001 = 0000 1001
/*console.log(enc3)//00001001
*/
enc4 = chr3 & 63;
/*计算enc4:取chr3余下六位作为低六位(高两位补零):
/*enc4 = 0110 0011 & 0011 1111 = 0010 1011
/*console.log(enc4)//00101011
*/
此时通过this._keyStr.charAt(encx)
进行索引查找,得到的前三个字符的编码为“YWJj”
。
分析完"abcd"
的"abc"
编码,也就是程序的第一次循环,还剩”d”未解析,此时需要进入第二次循环,由于d之后已经没有字符,故第二次循环时的chr1
、chr2
、chr3
的结果为:100
,NaN
,NaN
。同理进行3 * 8 = 4 * 6编码时得到的最后编码为“ZA==”。
经过两次循环后,原始字符串”abcd”已经被全部遍历并编码,最后得到的Base64编码为”YWJjZA==”。
理解了encode
方法后,再去进行解码’decode’就比较简单了,基本上就是编码的逆向操作,这里就不一一解释了。
总结
Base64作为一种传输二进制的编码格式,虽然编码后字符内容长度会增加大约1/3,但是在一定程度上保证了一些不可打印字符在传输时的的信息完整性,同时本文最后也通过使用JavaScript实现了Base64的编码以及解码,可以看到Base64也起到了一定的加密作用(起码不是人一眼就能看懂的),在实际项目中应根据具体需求和环境选用Base64编码。
(完)
参考资料: