2018-10-09 · Tools

Base64 初探

Base64 编码由来

由于有些网络传送渠道并不支持所有的字节,例如传统的邮件只支持可见字符的传送,比如 ASCII 码的控制字符就不能通过邮件传送。这样用途就受到了很大的限制,比如图片二进制流的每个字节不可能全部是可见字符,所有就传送不了。最好的方法就是在不改变传统协议的情况下,做一种扩展方案来支持二进制文件的传送。把不可打印的字符也能用可打印字符来表示,问题就解决了。Base64 编码就应运而生,Base64 就是一种基于64个可打印字符来表示二进制数据的表示方法。

Base64 的索引表

看一下 Base64 的索引表,字符选用了"A-Z、a-z、0-9、+、/" 64个可打印字符。数值代表字符的索引,这个是标准 Base64 协议规定的,不能更改。64个字符用6个 bit 位就可以全部表示,一个字节有8个 bit 位,剩下两个 bit 就浪费掉了,这样就不得不牺牲一部分空间了。这里需要弄明白的就是一个 Base64 字符是8个 bit ,但是有效部分只有右边的6个 bit,左边两个永远是0。

Base的索引表

Base64的原理

Base64 的码表只有64个字符, 如果要表达64个字符的话,使用6的 bit 即可完全表示(2的6次方为64)。
因为 Base64 的编码只有6个 bit 即可表示,而正常的字符是使用8个 bit 表示, 8和6的最小公倍数是24,所以4个 Base64 字符可以表示3个标准的 ascll 字符;
如果是字符串转换为 Base64 码, 会先把对应的字符串转换为 ascll 码表对应的数字, 然后再把数字转换为2进制, 比如 a 的 ascll 码为97, 97的二进制是:01100001, 把8个二进制提取成6个,剩下的2个二进制和后面的二进制继续拼接, 最后再把6个二进制码转换为 Base64 对于的编码, 以下为具体的解析过程案例:

字符长度为能被3整除时:比如“Tom”,浏览器 F12 运行:btoa('Tom') = "VG9t";

String T o m
ASCII: 84 111 109
8bit字节: 01010100 01101111 01101101
6bit字节: 010101 000110 111101 101101
十进制: 21 6 61 45
对应编码: V G 9 t

字符串长度不能被3整除时,比如“Lucy”:

String L u c y
ASCII: 76 117 99 121
8bit字节: 01001100 01110101 01100011 01111001 00000000 00000000
6bit字节: 010011 000111 010101 100011 011110 010000 000000 000000
十进制: 19 7 21 35 30 16 (异常) (异常)
对应编码: T H V j e Q = =

由于 Lucy 只有4个字母,所以按3个一组的话,第二组还有两个空位,所以需要用0来补齐。这里就需要注意,因为是需要补齐而出现的0,所以转化成十进制的时候就不能按常规用base64编码表来对应,所以不是a, 可以理解成为一种特殊的“异常”,编码应该对应“=”。
自己实现的编码:

/**
 * base64 encoding & decoding
 * for fixing browsers which don't support Base64 | btoa |atob
 */

(function (win, undefined) {

     var Base64 = function () {
        var base64hash = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';

        // btoa method
        function _btoa (s) {
            if (/([^\u0000-\u00ff])/.test(s)) {
                throw new Error('INVALID_CHARACTER_ERR');
            }
            var i = 0,
                prev,
                ascii,
                mod,
                result = [];

            while (i < s.length) {
                ascii = s.charCodeAt(i);
                mod = i % 3;

                switch(mod) {
                    // 第一个6位只需要让8位二进制右移两位
                    case 0:
                        result.push(base64hash.charAt(ascii >> 2));
                        break;
                    //第二个6位 = 第一个8位的后两位 + 第二个8位的前4位
                    case 1:
                        result.push(base64hash.charAt((prev & 3) << 4 | (ascii >> 4)));
                        break;
                    //第三个6位 = 第二个8位的后4位 + 第三个8位的前2位
                    //第4个6位 = 第三个8位的后6位
                    case 2:
                        result.push(base64hash.charAt((prev & 0x0f) << 2 | (ascii >> 6)));
                        result.push(base64hash.charAt(ascii & 0x3f));
                        break;
                }

                prev = ascii;
                i ++;
            }

            // 循环结束后看mod, 为0 证明需补3个6位,第一个为最后一个8位的最后两位后面补4个0。另外两个6位对应的是异常的“=”;
            // mod为1,证明还需补两个6位,一个是最后一个8位的后4位补两个0,另一个对应异常的“=”
            if(mod == 0) {
                result.push(base64hash.charAt((prev & 3) << 4));
                result.push('==');
            } else if (mod == 1) {
                result.push(base64hash.charAt((prev & 0x0f) << 2));
                result.push('=');
            }

            return result.join('');
        }

        // atob method
        // 逆转encode的思路即可
        function _atob (s) {
            s = s.replace(/\s|=/g, '');
            var cur,
                prev,
                mod,
                i = 0,
                result = [];

            while (i < s.length) {
                cur = base64hash.indexOf(s.charAt(i));
                mod = i % 4;

                switch (mod) {
                    case 0:
                        //TODO
                        break;
                    case 1:
                        result.push(String.fromCharCode(prev << 2 | cur >> 4));
                        break;
                    case 2:
                        result.push(String.fromCharCode((prev & 0x0f) << 4 | cur >> 2));
                        break;
                    case 3:
                        result.push(String.fromCharCode((prev & 3) << 6 | cur));
                        break;

                }

                prev = cur;
                i ++;
            }

            return result.join('');
        }

        return {
            btoa: _btoa,
            atob: _atob,
            encode: _btoa,
            decode: _atob
        };
    }();

    if (!win.Base64) { win.Base64 = Base64 }
    if (!win.btoa) { win.btoa = Base64.btoa }
    if (!win.atob) { win.atob = Base64.atob }

 })(window)

参考:

关于base64编码的原理及实现

点击我来测试一下