JyLie

vuePress-theme-reco JyLie    2017 - 2023
JyLie

Choose mode

  • dark
  • auto
  • light
主页
分类
  • API
  • HTML
  • css
  • vue
  • Linux
  • Docker
  • Webpack
  • WebGL
  • PixiJS
  • Github
  • BOM
  • XML
  • bug
  • ie
  • uniapp
  • IE
  • mysql
  • font
  • bom
  • canvas
  • video
  • html
  • JavaScript
  • js
  • 运算符
  • RegExp
  • 编码
  • MiniApp
  • nginx
  • Tool
  • node.js
  • cat
  • nodejs
  • protocol
  • URL
  • FLOW
  • DNS
  • Protocol
  • python
  • 安全
  • linux
  • shell
  • IDE
  • Packer
  • ViteJS
  • git
  • vendor
  • WebApp
  • WebView
  • Window API
  • webview
  • 规范
标签
时光轴
GitHub
author-avatar

JyLie

74

Article

79

Tag

主页
分类
  • API
  • HTML
  • css
  • vue
  • Linux
  • Docker
  • Webpack
  • WebGL
  • PixiJS
  • Github
  • BOM
  • XML
  • bug
  • ie
  • uniapp
  • IE
  • mysql
  • font
  • bom
  • canvas
  • video
  • html
  • JavaScript
  • js
  • 运算符
  • RegExp
  • 编码
  • MiniApp
  • nginx
  • Tool
  • node.js
  • cat
  • nodejs
  • protocol
  • URL
  • FLOW
  • DNS
  • Protocol
  • python
  • 安全
  • linux
  • shell
  • IDE
  • Packer
  • ViteJS
  • git
  • vendor
  • WebApp
  • WebView
  • Window API
  • webview
  • 规范
标签
时光轴
GitHub
  • 深入浅出base64编码原理

    • base64 由来
      • base64 的编码原理
        • base64 编码对照表
          • base64 的编码转换规则
            • base64 优缺点
              • JavaScript 的 base64 转码方法
                • Web API 二进制与 base64 转换
                • base64 转二进制
                • base64 转成 Blob

            深入浅出base64编码原理

            vuePress-theme-reco JyLie    2017 - 2023

            深入浅出base64编码原理


            JyLie 2020-02-16 base64

            # 前言

            平静之下,蓦然回首,base64 却在灯火阑珊处。

            今天翻开旧项目发现挺多图片相关的插件都是用 base64 来显示图片的。谈到 base64,脑海遐想翩翩,思绪回荡之下 base64 瑕瑜互见。本文旨在记录工作中遇见的问题并加以总结,如有不妥请指正~

            # base64 由来

            base64 是网络传输 8Bit 字节代码的编码方式之一,是一种基于 64 个可打印字符来表示二进制数据的方法。在做支付系统时,报文交互都需要使用 base64 对明文进行转码,然后再进行签名或加密,之后再进行(或再次 base64 转码)传输。那么,base64 到底起到什么作用呢?

            在参数传输的过程中经常遇到的一种情况:使用全英文的字符串没问题,但一旦涉及到中文就会出现乱码的情况。与此类似,网络上传输的字符并不全是可打印的字符,比如二进制文件、图片等。base64 的出现就是为了解决此问题,它是基于 64 个可打印的字符来表示二进制的数据的一种方法。

            电子邮件刚问世的时候,只能传输英文,但后来随着用户的增加,中文、日韩俄文等文字的用户也有需求,但这些字符并不能被服务器或网关有效处理,因此 base64 就登场了。随后,base64 在 URL、Cookie、网页传输少量二进制文件中也有相应的使用。

            # base64 的编码原理

            基于a-z、A-Z、0-9、+/这 64 个字符来标识二进制数据,另外=符号用于当字节缺位时补用。

            # base64 编码对照表

            base64 编码对照表
            索引
            对应字符
            索引
            对应字符
            索引
            对应字符
            索引
            对应字符
            0
            A
            17
            R
            34
            i
            51
            z
            1
            B
            18
            S
            35
            j
            52
            0
            2
            C
            19
            T
            36
            k
            53
            1
            3
            D
            20
            U
            37
            l
            54
            2
            4
            E
            21
            V
            38
            m
            55
            3
            5
            F
            22
            W
            39
            n
            56
            4
            6
            G
            23
            X
            40
            o
            57
            5
            7
            H
            24
            Y
            41
            p
            58
            6
            8
            I
            25
            Z
            42
            q
            59
            7
            9
            J
            26
            a
            43
            r
            60
            8
            10
            K
            27
            b
            44
            s
            61
            9
            11
            L
            28
            c
            45
            t
            62
            +
            12
            M
            29
            d
            46
            u
            63
            /
            13
            N
            30
            e
            47
            v
            14
            O
            31
            f
            48
            w
            15
            P
            32
            g
            49
            x
            16
            Q
            33
            h
            50
            y

            # base64 的编码转换规则

            base64 要求把每三个 8Bit 的字节转换为四个 6Bit 的字节(3*8 = 4*6 = 24),然后把 6Bit再添两位高位 0,组成四个 8Bit 的字节(4*8=32)。

            为什么使用 3 个字节一组呢?因为 6 和 8 的最小公倍数为 24,三个字节正好 24 个二进制位,每 6 个 bit 位一组,恰好能够分为 4 组。

            同时用于分组后每组添加两个高位 0,转换后的字符串理论上将要比原来的字符长 1/3(24/32=1/3)。

            • 步骤分解:

              1. 将待转换的字符串每三个字符分为一组,每个字符字节占 8bit,那么共有 24 个二进制位。
              2. 将 24 个二进制位每 6 个字节为一组,共分为 4 组。
              3. 在每组 6 字节前面添加两个 0,每组由 6 字节变为 8 字节二进制位,组成总共 32 个二进制位,即四个字节。
              4. 根据 base64 编码对照表获得对应的值。
            • 举个栗子

              • 以标准 3 个字符LJY为例。
              1. LJY 对应的 ASCII 码值分别为 76、74、89,对应的二进制值是 01001100、01001010、01011001。由此组成一个 24 位的二进制位字符串。
              2. 将 24 位的二进制位字符串,按照每 6 位二进制位一组分成 4 组。
              3. 对 4 组 每组 6 位二进制位字符串前面补两个 0,每组扩展为 8 位二进制位,4 组共扩展成 32 个二进制位,此时 4 组二进制位分别为:00010011、00000100、00101001、00011001。其对应的 base64 编码索引为:19、4、41、25。
              4. 用 base64 编码索引值在 base64 编码表中进行查找,分别对应:T、E、p、Z。
              • 因此LJYbase64 编码之后就变为:TEpZ。
            |   文本           |    L     |    J     |    Y     |
            |  ASCII          |    76    |    74    |    89    |
            | 二进制位         | 01001100 | 01001010 | 01011001 |
            | 分组二进制       | 010011   | 000100   | 101001   | 011001   |
            | 分组二进制补2个0 | 00010011 | 00000100 | 00101001 | 00011001 |
            | 分组索引         |   19     |   4      |   41     |   25     |
            | base64编码       |   T     |   E      |    p      |   Z      |
            
            主要展示:
            转换前二进制位: 01001100 01001010 01011001
            转换后二进制位: 00010011 00000100 00101001 00011001
            
            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            • 字符位数不足情况

            上述栗子是面向刚好三个字符为一组的情况。当然不是所有时候都这么巧字符位数足够,除此以外有位数不足的情况。那么,面对字符位数不足的情况下该如何处理呢?

            base64 给出的方案是,当每组字符不足三位时,不足位数位置需要使用=符号补上。

            位数不足情况处理情景:

            • 位数缺一个字节:一个字节共 8 个二进制位,依旧按照规则进行分组。此时共 8 个二进制位,每 6 个一组,则第二组缺少 4 位,用 0 补齐,得到两个 base64 编码,而后面两组没有对应数据,都用=补上。
            • 位数缺两个字节:两个字节共 16 个二进制位,依旧按照规则进行分组。此时总共 16 个二进制位,每 6 个一组,则第三组缺少 2 位,用 0 补齐,得到三个 base64 编码,第四组完全没有数据则用=补上。

            位数不足图解如下:

            <!-- 缺2位字符,字符串以A为例转换base64后位QQ== -->
            |   文本(1Byte)  |    A     |          |          |
            | 二进制位         | 01000001 |          |          |
            | 分组二进制       | 010000   | 010000   |          |          |
            | 分组二进制补0    | 00010000 | 00010000 |          |          |
            | 分组索引         |   16     |   16     |          |          |
            | base64编码       |   Q     |   Q      |    =      |   =      |
            
            <!-- 缺1位字符,字符串以AB为例转换base64后位QUI= -->
            |   文本(1Byte)  |    A     |     B    |          |
            | 二进制位         | 01000001 | 01000010 |          |
            | 分组二进制       | 010000   | 010100   |  001000  |          |
            | 分组二进制补0    | 00010000 | 00010100 | 00001000 |          |
            | 分组索引         |   16     |   20     |    8      |          |
            | base64编码       |   Q     |   U      |    I      |   =      |
            
            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            15

            列举了一个字符到三个字符转换为 base64 ,可以发现将 base64 就是按照 base64 编码对照表来将二进制转换为字符串,使得数据不能直接明文展示出来,但也算不上是加密,而这巧好可用在传输、存储、表示二进制领域的情景。

            • 另外值得注意的是,不用语言如中文有多种编码(比如:utf-8、gb2312、gbk 等),不同编码对应 base64 编码结果都不一样。
            • 其次在推演过程中可发现 base64 即用 6 位字节(2 的 6 次幂就是 64)表示字符同理,Base32 就是用 5 位字节,Base16 就是用 4 位字节。大家可以按照上面的步骤进行演化测试。

            # base64 优缺点

            知道 base64 是什么后,也该到为什么出现了。为什么要是使用 base64 呢,这要从其优缺点入手来选择适合场景了。

            • 优势:
              • base64 适合不同平台、不同语言的传输;
              • 页面中内嵌使用 base64 格式的小图片,可减少了服务器访问次数;
              • 二进制位转换 base64 算法简单,对性能影响不大;
            • 缺点
              • 二进制文件转换为 base64 后,体积大概增加 1/3;
                • 在基于 Android6.0 及以下默认浏览器实测场景中发现,某些机型如中兴上传 base64 图片会因为字符大小过大导致上传奔溃的情况。
                • 字符长度过大的 base64 不适应使用在 URL 情景,因为 IOS 端浏览器会限制 URL 长度,当长度超过时会自动切除多余部分,导致数据丢失。
                • base64 字符过大会导致页面加载速度变慢,因此建议 10kb 以下的图片使用。
              • base64 无法缓存,要缓存只能缓存包含 base64 的文件,比如 js 或者 css;
              • 面对大文件时,会消耗一定的 CPU 进行编解码

            # JavaScript 的 base64 转码方法

            # Web API 二进制与 base64 转换

            • atob(encodedData) : 解码一个 base64 编码的字符串。

            enCodedData,是一个通过 btoa() 方法编码的字符串, 为二进制字符串包含 base64 编码的数据。并返回包含来自 encodedData 的解码数据的 ASCII 字符串。

            • btoa(stringToEncode) : 创建一个 bas64 编码的字符串。

            stringToEncode 为要编码的二进制字符串。并返回包含 stringToEncode 的 base64 表示形式的 ASCII 字符串。

            另外在 JavaScript 中,字符串使用 UTF-16 字符编码表示:在这种编码中,字符串表示为 16 位(2 字节)单元的序列。每个 ASCII 字符都可以放入其中一个单元的第一个字节,但许多其他字符不能。

            base64 在设计上需要二进制数据作为其输入。就 JavaScript 字符串而言,这意味着每个字符只占用一个字节的字符串。因此,如果将一个字符串传递到 btoa()中,其中包含占用多个字节的字符,则会出现错误,因为这不被视为二进制数据,因此超 16 位字符在使用 btoa()时需要先对字符转码为二进制位。

            // 简单数据
            const encodedData = btoa('Hello, world'); // encode a string
            const decodedData = atob(encodedData); // decode the string
            
            /* 复杂数据 */
            // convert a Unicode string to a string in which
            // each 16-bit unit occupies only one byte
            function toBinary(string) {
              const codeUnits = new Uint16Array(string.length);
              for (let i = 0; i < codeUnits.length; i++) {
                codeUnits[i] = string.charCodeAt(i);
              }
              const charCodes = new Uint8Array(codeUnits.buffer);
              let result = '';
              for (let i = 0; i < charCodes.byteLength; i++) {
                result += String.fromCharCode(charCodes[i]);
              }
              return result;
            }
            function fromBinary(binary) {
              const bytes = new Uint8Array(binary.length);
              for (let i = 0; i < bytes.length; i++) {
                bytes[i] = binary.charCodeAt(i);
              }
              const charCodes = new Uint16Array(bytes.buffer);
              let result = '';
              for (let i = 0; i < charCodes.length; i++) {
                result += String.fromCharCode(charCodes[i]);
              }
              return result;
            }
            
            // a string that contains characters occupying > 1 byte
            const myString = '☸☹☺☻☼☾☿';
            
            const converted = toBinary(myString);
            const encoded = btoa(converted);
            console.log(encoded); // OCY5JjomOyY8Jj4mPyY=
            
            const decoded = atob(encoded);
            const original = fromBinary(decoded);
            console.log(original); // ☸☹☺☻☼☾☿
            
            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            15
            16
            17
            18
            19
            20
            21
            22
            23
            24
            25
            26
            27
            28
            29
            30
            31
            32
            33
            34
            35
            36
            37
            38
            39
            40
            41
            42
            • 兼容性:atob() 方法不支持 IE9 及更早的 IE 版本。

            # base64 转二进制

            // base64编码表
            const map = {
              0: 52,
              1: 53,
              2: 54,
              3: 55,
              4: 56,
              5: 57,
              6: 58,
              7: 59,
              8: 60,
              9: 61,
              A: 0,
              B: 1,
              C: 2,
              D: 3,
              E: 4,
              F: 5,
              G: 6,
              H: 7,
              I: 8,
              J: 9,
              K: 10,
              L: 11,
              M: 12,
              N: 13,
              O: 14,
              P: 15,
              Q: 16,
              R: 17,
              S: 18,
              T: 19,
              U: 20,
              V: 21,
              W: 22,
              X: 23,
              Y: 24,
              Z: 25,
              a: 26,
              b: 27,
              c: 28,
              d: 29,
              e: 30,
              f: 31,
              g: 32,
              h: 33,
              i: 34,
              j: 35,
              k: 36,
              l: 37,
              m: 38,
              n: 39,
              o: 40,
              p: 41,
              q: 42,
              r: 43,
              s: 44,
              t: 45,
              u: 46,
              v: 47,
              w: 48,
              x: 49,
              y: 50,
              z: 51,
              '+': 62,
              '/': 63,
            };
            
            function base64to2(base64) {
              let len = base64.length * 0.75; // 转换为int8array所需长度
              base64 = base64.replace(/=*$/, ''); // 去掉=号(占位的)
            
              const int8 = new Int8Array(len); //设置int8array视图
              let arr1,
                arr2,
                arr3,
                arr4,
                p = 0;
            
              for (let i = 0; i < base64.length; i += 4) {
                arr1 = map[base64[i]]; // 每次循环 都将base644个字节转换为3个int8array直接
                arr2 = map[base64[i + 1]];
                arr3 = map[base64[i + 2]];
                arr4 = map[base64[i + 3]];
                // 假设数据arr 数据 00101011 00101111 00110011 00110001
                int8[p++] = (arr1 << 2) | (arr2 >> 4);
                // 上面的操作 arr1向左边移动2位 变为10101100
                // arr2 向右移动4位:00000010
                // | 为'与'操作: 10101110
                int8[p++] = (arr2 << 4) | (arr3 >> 2);
                int8[p++] = (arr3 << 6) | arr4;
              }
              return int8;
            }
            
            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            15
            16
            17
            18
            19
            20
            21
            22
            23
            24
            25
            26
            27
            28
            29
            30
            31
            32
            33
            34
            35
            36
            37
            38
            39
            40
            41
            42
            43
            44
            45
            46
            47
            48
            49
            50
            51
            52
            53
            54
            55
            56
            57
            58
            59
            60
            61
            62
            63
            64
            65
            66
            67
            68
            69
            70
            71
            72
            73
            74
            75
            76
            77
            78
            79
            80
            81
            82
            83
            84
            85
            86
            87
            88
            89
            90
            91
            92
            93
            94

            # base64 转成 Blob

            // base64图片转blob
            function base64toBlob(base64) {
              var arr = base64.split(','),
                mime = arr[0].match(/:(.*?);/)[1] || 'image/png',
                bstr = atob(arr[1]), // 将base64转为Unicode规则编码
                n = bstr.length,
                u8arr = new Uint8Array(n);
              while (n--) {
                u8arr[n] = bstr.charCodeAt(n); // 转换编码后才可以使用charCodeAt 找到Unicode编码
              }
              return new Blob([u8arr], { type: mime });
            }
            
            /* 优化版 */
            function base64ToBlob(base64) {
              var arr = base64.split(',');
              var mime = arr[0].match(/:(.*?);/)[1] || 'image/png';
              // 去掉url的头,并转化为byte
              var bytes = window.atob(arr[1]);
              // 处理异常,将ascii码小于0的转换为大于0
              var ab = new ArrayBuffer(bytes.length);
              // 生成视图(直接针对内存):8位无符号整数,长度1个字节
              var u8arr = new Uint8Array(ab);
            
              for (var i = 0; i < bytes.length; i++) {
                u8arr[i] = bytes.charCodeAt(i);
              }
            
              return new Blob([u8arr], { type: mime });
            }
            
            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            15
            16
            17
            18
            19
            20
            21
            22
            23
            24
            25
            26
            27
            28
            29
            30
            • MDN-btoa
            • MDN-atob
            • base64 官网
            • 原文参考