base64 编码及解码
文中部分内容涉及字符的字节码,要了解相关内容在这里
http://www.cnblogs.com/ecalf/archive/2012/09/04/unicode.html
base64 编码是一种使用64个可打印字符来标记二进制数据的编码方法,该方法早期主要用于传输电子邮件,base64编码方法使用的64个字符从0至63编号依次为26个大写字母,26个小写字母,数字0-9,+ 和 / ,在不同的应用平台,最后的 + 和 / 这2个字符可能不一样,尤其是编码后用于 URL 参数传输时,可以换成 _ - * 等字符:
var base64Code = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
base64 编码的方法如下:
1、从二进制字节流中按每6bit重新转换成一个字节的方式(在这6位bit的高位加2位0),将字节流中的每3个字节转换成4个字节(8bit*3 = 6bit*4,24 是6 和 8的最小公倍数),
2、如果要转换的字节流不是3的倍数,则在低位用0补足,
3、将新的字节的bit值转换成10进制数,用该数字作索引号从base64编码表中取出对应字符,组成编码结果,
4、如果原字节流的字节数不是3的倍数,余数可能为1或2,将第3步得到的字符串的末尾1个或2个字符用 '=' 替换掉。用0补足的字节转10进制数值为0,对应base64编码表的字符A,因此,除非要编码的字节流末尾包含有空字符字节(\0),否则base64 编码的结果末尾最多只能有一个A,多出来的(1或2个)都要被 '=' 替换
例如,我们要将 字符串 'abcd' 编码为base64,过程如下:
1、把字符串转换成二进制字节码:
var str = 'abcd'; var bitArr = str.split('').map(function(v){ return (Array(8).join(0)+v.charCodeAt(0).toString(2)).slice(-8) }); // ["01100001", "01100010", "01100011", "01100100"]
2、将字节数补充至3的倍数
var tailCount = (3-bitArr.length%3)%3; bitArr = bitArr.concat(Array(tailCount+1).join('00000000').match(/\d{8}/g)) //["01100001", "01100010", "01100011", "01100100", "00000000", "00000000"]
3、按 6bit 一组重新分组
bitArr = bitArr.join('').match(/\d{6}/g); //["011000", "010110", "001001", "100011", "011001", "000000", "000000", "000000"]
4、将每一组补足至 8bit ,按二进制值传换成10进制(高位补零对转换结果没有影响,这里直接忽略了,直接用6位转换)
bitArr = bitArr.map(function(v){ return parseInt(v,2); }); //[24, 22, 9, 35, 25, 0, 0, 0]
5、以转换结果得到的十进制数为序号,从 base64 编码表取出对应字符,合并成 编码结果字符串
var base64Str = bitArr.map(function(v){ return base64Code[v]; }).join(''); // "YWJjZAAA"
6、将用0补足的字节用 '=' 替换
base64Str = base64Str.slice(0,base64Str.length-tailCount)+Array(tailCount+1).join('='); // "YWJjZA==" ,这便是 'abcd' 的base64 编码
验证一下:
window.atob("YWJjZA=="); //"abcd"
注意编码末尾的A,如果多于一个(如果只有一个,且包含‘=’,就是原始字流字节数无法被3整除,最后一个字节末尾是0,补足至6bit后得到的空字符字节,btoa('ac\0') 得到 "YWMA",这个编码末尾只有一个A ,但没有 '=',因此这个A 代表原始字节流中的空字符 \0),说明原始字节流末尾包含空字符,因为空字符无法打印出,不要被你的眼睛骗了:
window.atob("YWJjZAAAAAA=="); //"abcd" window.atob("YWJjZAAAAAA==").length //8 window.atob("YWJjZAAAAAA==")==='abcd\0\0\0\0' \\true //javascript 函数中,base64 编码函数为 btoa() : btoa('abcd'); // "YWJjZA=="
base64字符串最末尾字符:
试考察这个字符串的来历,例如上面 "YWJjZA==" 中的 A,
1、在字节数刚好是 3 的倍数的情况下,不需要补0,这种情况下,二进制字节流无论加 1 还是减 1,都会对应不同的字符串,例如 ,
'abc' 的二进制字节 : 01100001 01100010 01100011,
如果加上1,就是 : 01100001 01100010 01100100 , 对应 'abd'
减去1,就是 : 01100001 01100010 01100010,对应 'abb'
2、在字节数余 1 的情况下,这个生成这个字符的字节码需要在低位补4个0 (8bit-6bit=2bit ,多余的2bit 补足 6bit 需要加 4 位 0),
如果将该字符替换为比该字符uniocde 编码大1的字符(例如将abcd 替换为 abce),同样编码为base64之后,所得字节流二进制数之差为 4位二进制数,即16,亦即 unicode 编码相差1的字符,编码为base64 编码之后,如果字节数除 3 余 1,将有 15 个base64 编码位(如果余2,则2*8-2*6 =4, 补 2bit,有2^2-1=3个码位)没有真实的原始字节流可以对应,例如,
// 余 1 的情况,可以看到,"YWJjZA==" 的 A 与 "YWJjZQ==" 的 Q 之间,相隔了 15个字符。 btoa('abcd');// "YWJjZA==" btoa('abce'); // "YWJjZQ==" // 余 2 的情况,"YWI=" 的 I 与 "YWM=" 的 M 之间相隔了 3 个字符 btoa('ab') ;// "YWI=" btoa('ac'); // "YWM=" // 余 0 的情况,"YWJj" 的 j 与 "YWJk" 的 k 之间没有任何字符 btoa('abc'); //"YWJj" btoa('abd'); // "YWJk"
3、对于没有真实字节流对应的 base64 编码码位,javascript 的解码函数是这样处理的:全部按上一个有对应字节流的低码位解码,例如,
"YWJjZA==" 至 "YWJjZP==" 全部按 "YWJjZA==" 解码,
"YWI=" 至 "YWL=" 全部按 "YWI=" 解码
btoa('ab') //"YWI=" atob("YWI="); // "ab" atob("YWJ="); // "ab" atob("YWK="); // "ab" atob("YWL="); //"ab" btoa('abcd') //"YWJjZA==" atob('YWJjZA==') //"abcd" atob('YWJjZB==') //"abcd" atob('YWJjZC==') //"abcd" atob('YWJjZP==') //"abcd"
4、因此,base64 编码可能不是1对1的,在做 base64 解码时,需要正确剔除增加的补位
var base64Str = 'YWJjZC=='; var tailCount = (base64Str.match(/\=+$/)||[''])[0].length; base64Str.replace(/\=/g,'A').split('').map(function(v){ return (Array(6).join(0)+base64Code.indexOf(v).toString(2)).slice(-6) }).slice(0,base64Str.length-tailCount ).join('').match(/\d{8}/g) .map(function(v){ return String.fromCharCode(parseInt(v,2)); }).join(''); // 'abcd'
.slice(0,base64Str.length-tailCount ) 已经把补 0 的字节丢弃.
.match(/\d{8}/g) 已经把被base64 转换时,因不足6位而补充的bit位丢弃,因为转 base64 时,字节码位数必然是8的倍数.
base64 编码是针对字节开始的,因此他无法编码双字节字符串。留意一下 javascript 中base64 函数的函数名 :
btoa() // Binary-to-ASCII
atob() //ASCII-to-Binary
因为将3字节编码为4字节,因此base64编码后的字符串会更长。
知道 base64 的编码方式后,对编码后的字符串进行解码很容易,所以 base64 其实没有加密功能,他只是让字符串看上去没有那么直观,不过我们可以稍微修改 base64 的编码的字符对照表来衍生类似 base64 的编码方式,或者改变编码的字节数得到 base32(5bit分组 2^5=32) 、base16(4bit分组) 的编码方法
html 的 base64 格式的URLData 数据:
data:mini-type;base64,base64Str
例如,base64 格式的 gif 图片
<img src="data:image/gif;base64,[这里是base64字符串]" />
javascript 的 file API 中的 FileReader ,和 canvas 都有将文件或、图片转换成 base64 数据的方法。
function encodeBase64(str){//base64编码 var base64Code = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; var bitArr = str.split('').map(function(v){ return (Array(8).join(0)+v.charCodeAt(0).toString(2)).slice(-8) }); var tailCount = (3-bitArr.length%3)%3; return bitArr.concat(Array(tailCount+1).join('00000000').match(/\d{8}/g)).join('').match(/\d{6}/g).map(function(v){ return base64Code[parseInt(v,2)]; }).join('').replace(new RegExp('\\w{'+tailCount+'}\$'),Array(tailCount+1).join('=')); } function decodeBase64(base64Str){//base64解码 var base64Code = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; var tailCount = (base64Str.match(/\=+$/)||[''])[0].length; return base64Str.replace(/\=/g,base64Code[0]).split('').map(function(v){ return (Array(6).join(0)+base64Code.indexOf(v).toString(2)).slice(-6) }).slice(0,base64Str.length-tailCount ).join('').match(/\d{8}/g) .map(function(v){ return String.fromCharCode(parseInt(v,2)); }).join(''); }
一个动态的 base64 加密方法,每次使用不同的 base64Code 产生密文
var Base64 = { baseCode:function(skip){ skip = Math.abs(parseInt(skip))%10; // 0-9 var base64Code = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; if(!skip){ return base64Code; } var code1 = base64Code.slice(0,-2).split(''); var code2 = []; var index = 0; while(code1.length){ code2.push(code1.splice(index,1)); index = (index+skip)%code1.length; } return code2.join('')+'+/'; }, encodeBase64:function (str){//base64编码 var skip = parseInt(Math.random()*10)||1; //1-9,如果使用原始 base64,skip 设为 0 var base64Code = this.baseCode(skip); var bitArr = str.split('').map(function(v){ return (Array(8).join(0)+v.charCodeAt(0).toString(2)).slice(-8) }); var tailCount = (3-bitArr.length%3)%3; var encodeStr = bitArr.concat(Array(tailCount+1).join('00000000').match(/\d{8}/g)).join('').match(/\d{6}/g).map(function(v){ return base64Code[parseInt(v,2)]; }).join('').replace(new RegExp('\\w{'+tailCount+'}\$'),Array(tailCount+1).join('=')); return String.fromCharCode(('a').charCodeAt(0)+skip)[['toLowerCase','toUpperCase'][skip%2]]()+encodeStr; }, decodeBase64:function(base64Str){//base64解码 var skip = base64Str.charAt(0).toLowerCase().charCodeAt(0)-('a').charCodeAt(0); base64Str = base64Str.slice(1); var base64Code = this.baseCode(skip); var tailCount = (base64Str.match(/\=+$/)||[''])[0].length; return base64Str.replace(/\=/g,base64Code[0]).split('').map(function(v){ return (Array(6).join(0)+base64Code.indexOf(v).toString(2)).slice(-6) }).slice(0,base64Str.length-tailCount ).join('').match(/\d{8}/g) .map(function(v){ return String.fromCharCode(parseInt(v,2)); }).join(''); } }; // test var str = 'http://www.stonline.com/elearning/my#'; var encodeStr = Base64.encodeBase64(str); var decodeStr = Base64.decodeBase64(encodeStr); console.log('str>>>',str); console.log('encodeStr>>>',encodeStr); console.log('decodeStr>>>',decodeStr); console.log('str==decodeStr>>>',str==decodeStr);
附: data url
请参考这里: http://www.cnblogs.com/oneroundseven/archive/2011/05/09/2040911.html
data类型Url的形式
data:text/html,<html><body><p><b>Hello, world!</b></p></body></html>
data:text/plain,<文本数据>
完整的语法定义
dataurl := "data:" [ mediatype ] [ ";base64" ] "," data
mediatype := [ type "/" subtype ] *( ";" parameter )
data := *urlchar
parameter := attribute "=" value
data:text/plain;charset=UTF-8;base64,5L2g5aW977yM5Lit5paH77yB