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的形式

 
既然是Url,当然也可以直接在浏览器的地址栏中输入。
data:text/html,<html><body><p><b>Hello, world!</b></p></body></html> 
在浏览器中输入以上的Url,会得到一个加粗的"Hello, world!"。也就是说,data:后面的数据直接用做网页的内容,而不是网页的地址。
注:Internet Explorer 不支持
简单的说,data类型的Url大致有下面几种形式:
data:,<文本数据>
data:text/plain,<文本数据>
data:text/html, 
data:text/html;base64, 
data:text/css, 
data:text/css;base64, 
data:text/javascript, 
data:text/javascript;base64, 
data:image/gif;base64,base64编码的gif图片数据
data:image/png;base64,base64编码的png图片数据
data:image/jpeg;base64,base64编码的jpeg图片数据
data:image/x-icon;base64,base64编码的icon图片数据
因为Url是一种基于文本的协议,所以gif/png/jpeg这种二进制属于需要用base64进行编码。换句话说,引入base64以后,就可以支持任意形式的数据格式。

完整的语法定义

 
在RFC中,完整的语法定义如下。
dataurl    := "data:" [ mediatype ] [ ";base64" ] "," data

mediatype := [ type "/" subtype ] *( ";" parameter )

data := *urlchar

parameter := attribute "=" value
urlchar指的就是一般url中允许的字符,有些字符需要转义,例如"="要转义为"%3D",不过我测试下来,至少在Firefox里面,不转义也是可以的。
parameter可以对mediatype进行属性的扩展,常见的是charset,用来定义编码格式,在多语言情况下需要用到。例如下面的例子。
data:text/plain;charset=UTF-8;base64,5L2g5aW977yM5Lit5paH77yB
这个例子会显示出"你好,中文!"。如果吧charset部分去掉,就会显示乱码,因为我用的是UTF-8编码。
posted @ 2012-11-24 23:21  ecalf  阅读(1660)  评论(0编辑  收藏  举报