zoukankan      html  css  js  c++  java
  • 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编码。
  • 相关阅读:
    HIVE优化学习笔记
    HIVE执行引擎TEZ学习以及实际使用
    端口状态 LISTENING、ESTABLISHED、TIME_WAIT及CLOSE_WAIT详解,以及三次握手四次挥手,滑动窗口(整理转发)
    kafka时间轮简易实现(二)
    kafka时间轮的原理(一)
    JAVA之G1垃圾回收器
    JAVA之垃圾收集器
    JAVA之内存结构
    SparkSQL学习笔记
    Python 学习 --简单购物车程序
  • 原文地址:https://www.cnblogs.com/ecalf/p/2786592.html
Copyright © 2011-2022 走看看