彻底弄懂base64的编码与解码原理

背景

base64的编码原理网上讲解较多,但解码原理讲解较少,想要彻底了解base64的编码与解码原理,请耐心看完此文,你一定会有所收获。

编码原理

Base64编码将字符串以每3个8字节子序列拆分成4个6字节(6个有效字节,最左边两个永远为0,其实也是8字节)子序列,不够4个字符就以‘=’进行补全。为什么命名为base64呢?因为6字节的二进制为2的6次方,也就是64。Base64的索引表,字符选用了”A-Z、a-z、0-9、+、/” 64个可打印字符来代表这64个二进制数。即

    let base64EncodeChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
复制代码
文本 n i c e
ascII编码 110 105 99 101
二进制 01101110 01101001 01100011 01100101
1 2 3 4 5 6 7 8 9
二进制(6) 011011 100110 100101 100011 011001 010000
十进制 27 38 37 35 25 16
base64字符 b m l j Z Q = =

字符串nice对应的ascII编码经过将每3个8位二进制数转4个6位二进制数之后得到的base64字符串位bmljZQ==,我们将上面转换通过代码逻辑分析实现吧。

思路

分析映射关系:abc -> xyzi
x: a的前六位
y: a的后两位 + b的前四位
z: b的后四位 + c的前两位
i: c的后六位

  1. 将字符对应的ascII编码转为8位二进制数
  2. 将每三个8位二进制数进行以下操作
    1. 将第一个数右移位2位,得到第一个6位有效字节二进制数
    2. 将第一个数 & 0x3之后左移位4位,得到第二个6位有效字节二进制数的第一个和第二个有效字节,将第二个数 & 0xf0之后右移位4位,得到第二个6位有效字节二进制数的后四位有效字节,两者取且得到第二个6位有效字节二进制
    3. 将第二个数 & 0xf之后左移位2位,得到第三个6位有效字节二进制数的前四位有效字节,将第三个数 & 0xC0之后右移位6位,得到第三个6位有效字节二进制数的后两位有效字节,两者取且得到第三个6位有效字节二进制
    4. 将第三个数 & 0x3f,得到第四个6位有效字节二进制数
  3. 将获得的6位有效字节二进制数转十进制,查找对呀base64字符

左移位:<<,有符号的移位操作左移操作时将运算数的二进制码整体左移指定位数,左移之后的空位用0补充.

右移位:>>,有符号的移位操作

右移操作是将运算数的二进制码整体右移指定位数,右移之后的空位用符号位补充,如果是正数用0补充,负数用1补充。

代码实现

// 输入字符串
let str = 'hao'
// base64字符串
let base64EncodeChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
// 定义输入、输出字节的二进制数
let char1, char2, char3, out1, out2, out3, out4, out
// 将字符对应的ascII编码转为8位二进制数
char1 = str.charCodeAt(0) & 0xff // 104  01101000
char2 = str.charCodeAt(1) & 0xff // 97  01100001
char3 = str.charCodeAt(2) & 0xff // 111  01101111
// 输出6位有效字节二进制数
6out1 = char1 >> 2 // 26  011010
out2 = (char1 & 0x3) << 4 | (char2 & 0xf0) >> 4 // 6  000110
out3 = (char2 & 0xf) << 2 | (char3 & 0xc0) >> 6 // 5  000101
out4 = char3 & 0x3f // 47 101111

out = base64EncodeChars[out1] + base64EncodeChars[out2] + base64EncodeChars[out3] + base64EncodeChars[out4] // aGFv
复制代码

算法剖析

  1. out1: char1 >> 2
    01101000 -> 00011010
    复制代码
  2. out2 = (char1 & 0x3) << 4 | (char2 & 0xf0) >> 4
    // 且运算
    01101000        01100001
    00000011        11110000
    --------        --------
    00000000        01100000
    
    // 移位运算后得
    00000000        00000110
    
    // 或运算
    00000000
    00000110
    --------
    00000110
    复制代码

第三个字符第四个字符同理

整理上述代码,扩展至多字符字符串

// 输入字符串
let str = 'haohaohao'
// base64字符串
let base64EncodeChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'

// 获取字符串长度
let len = str.length
// 当前字符索引
let index = 0
// 输出字符串
let out = ''
while(index < len) {
    // 定义输入、输出字节的二进制数
    let char1, char2, char3, out1, out2, out3, out4
    // 将字符对应的ascII编码转为8位二进制数
    char1 = str.charCodeAt(index++) & 0xff // 104  01101000
    char2 = str.charCodeAt(index++) & 0xff // 97  01100001
    char3 = str.charCodeAt(index++) & 0xff // 111  01101111
    // 输出6位有效字节二进制数
    out1 = char1 >> 2 // 26  011010
    out2 = (char1 & 0x3) << 4 | (char2 & 0xf0) >> 4 // 6  000110
    out3 = (char2 & 0xf) << 2 | (char3 & 0xc0) >> 6 // 5  000101
    out4 = char3 & 0x3f // 47 101111

    out = out + base64EncodeChars[out1] + base64EncodeChars[out2] + base64EncodeChars[out3] + base64EncodeChars[out4] // aGFv
}
复制代码

原字符串长度不是3的整倍数的情况,需要特殊处理

    ...
    char1 = str.charCodeAt(index++) & 0xff // 104  01101000
    if (index == len) {
        out2 = (char1 & 0x3) << 4
        out = out + base64EncodeChars[out1] + base64EncodeChars[out2] + '=='
        return out
    }
    char2 = str.charCodeAt(index++) & 0xff // 97  01100001
    if (index == len) {
        out1 = char1 >> 2 // 26  011010
        out2 = (char1 & 0x3) << 4 | (char2 & 0xf0) >> 4 // 6  000110
        out3 = (char2 & 0xf) << 2
        out = out + base64EncodeChars[out1] + base64EncodeChars[out2] + base64EncodeChars[out3] + '='
        return out
    }
    ...

复制代码

全部代码

function base64Encode(str) {
    // base64字符串
    let base64EncodeChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'

    // 获取字符串长度
    let len = str.length
    // 当前字符索引
    let index = 0
    // 输出字符串
    let out = ''
    while(index < len) {
        // 定义输入、输出字节的二进制数
        let char1, char2, char3, out1, out2, out3, out4
        // 将字符对应的ascII编码转为8位二进制数
        char1 = str.charCodeAt(index++) & 0xff
        out1 = char1 >> 2
        if (index == len) {
            out2 = (char1 & 0x3) << 4
            out = out + base64EncodeChars[out1] + base64EncodeChars[out2] + '=='
            return out
        }
        char2 = str.charCodeAt(index++) & 0xff
        out2 = (char1 & 0x3) << 4 | (char2 & 0xf0) >> 4 
        if (index == len) {
            out3 = (char2 & 0xf) << 2
            out = out + base64EncodeChars[out1] + base64EncodeChars[out2] + base64EncodeChars[out3] + '='
            return out
        }
        char3 = str.charCodeAt(index++) & 0xff
        // 输出6位有效字节二进制数
        out3 = (char2 & 0xf) << 2 | (char3 & 0xc0) >> 6
        out4 = char3 & 0x3f

        out = out + base64EncodeChars[out1] + base64EncodeChars[out2] + base64EncodeChars[out3] + base64EncodeChars[out4]
    }
    return out
}
base64Encode('haohao') // aGFvaGFv
base64Encode('haoha') // aGFvaGE=
base64Encode('haoh') // aGFvaA==
复制代码

解码原理

逆向推导,由每4个6位有效字节的二进制数合并成3个8位二进制数,根据ascII编码映射到对应字符后拼接字符串

思路

分析映射关系 xyzi -> abc
a: x + y前两位
b: y后四位 + z前四位
c: z后两位 + i

  1. 将字符对应的base64字符集的索引转为6位有效字节二进制数
  2. 将每四个6位有效字节二进制数进行以下操作
    1. 第一个字符左移位2位,得到新字符的前6位,第二个字符 & 0x30之后右移位4位,取或集得到第一个新字符
    2. 第二个字符 & 0xf之后左移位4位,第三个字符 & 0x3c之后右移位2位,取或集得到第二个新字符
    3. 第二个字符 & 0x3之后左移位6位,与第四个字符取或集得到第二个新字符
  3. 根据ascII编码映射到对应字符后拼接字符串

代码实现

// base64字符串
let str = 'aGFv'
// base64字符集
let base64CharsArr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split('')
// 获取索引值
let char1 = base64CharsArr.findIndex(char => char==str[0]) & 0xff // 26  011010
let char2 = base64CharsArr.findIndex(char => char==str[1]) & 0xff // 6  000110
let char3 = base64CharsArr.findIndex(char => char==str[2]) & 0xff // 5  000101
let char4 = base64CharsArr.findIndex(char => char==str[3]) & 0xff // 47  101111
let out1, out2, out3, out
// 位运算
out1 = char1 << 2 | (char2 & 0x30) >> 4
out2 = (char2 & 0xf) << 4 | (char3 & 0x3c) >> 2
out3 = (char3 & 0x3) << 6 | char4
console.log(out1, out2, out3)
out = String.fromCharCode(out1) + String.fromCharCode(out2) + String.fromCharCode(out3)
复制代码

遇到有用’=’补过位的情况时

function base64decode(str) {
    // base64字符集
    let base64CharsArr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split('')
    let char1 = base64CharsArr.findIndex(char => char==str[0])
    let char2 = base64CharsArr.findIndex(char => char==str[1])
    let out1, out2, out3, out
    if (char1 == -1 || char2 == -1) return out
    char1 = char1 & 0xff
    char2 = char2 & 0xff
    let char3 = base64CharsArr.findIndex(char => char==str[2])
    // 第三位不在base64对照表中时,只拼接第一个字符串
    if (char3 == -1) {
        out1 = char1 << 2 | (char2 & 0x30) >> 4
        out = String.fromCharCode(out1)
        return out
    }
    let char4 = base64CharsArr.findIndex(char => char==str[3])
    // 第三位不在base64对照表中时,只拼接第一个和第二个字符串
    if (char4 == -1) {
        out1 = char1 << 2 | (char2 & 0x30) >> 4
        out2 = (char2 & 0xf) << 4 | (char3 & 0x3c) >> 2
        out = String.fromCharCode(out1) + String.fromCharCode(out2)
        return out
    }
    // 位运算
    out1 = char1 << 2 | (char2 & 0x30) >> 4
    out2 = (char2 & 0xf) << 4 | (char3 & 0x3c) >> 2
    out3 = (char3 & 0x3) << 6 | char4
    console.log(out1, out2, out3)
    out = String.fromCharCode(out1) + String.fromCharCode(out2) + String.fromCharCode(out3)
    return out
}
复制代码

解码整个字符串,整理代码后

function base64decode(str) {
    // base64字符集
    let base64CharsArr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split('')
    let i = 0
    let len = str.length
    let out = ''
    while(i < len) {
        let char1 = base64CharsArr.findIndex(char => char==str[i])
        i++
        let char2 = base64CharsArr.findIndex(char => char==str[i])
        i++
        let out1, out2, out3
        if (char1 == -1 || char2 == -1) return out
        char1 = char1 & 0xff
        char2 = char2 & 0xff
        let char3 = base64CharsArr.findIndex(char => char==str[i])
        i++
        // 第三位不在base64对照表中时,只拼接第一个字符串
        out1 = char1 << 2 | (char2 & 0x30) >> 4
        if (char3 == -1) {
            out = out + String.fromCharCode(out1)
            return out
        }
        let char4 = base64CharsArr.findIndex(char => char==str[i])
        i++
        // 第三位不在base64对照表中时,只拼接第一个和第二个字符串
        out2 = (char2 & 0xf) << 4 | (char3 & 0x3c) >> 2
        if (char4 == -1) {
            out = out + String.fromCharCode(out1) + String.fromCharCode(out2)
            return out
        }
        // 位运算
        out3 = (char3 & 0x3) << 6 | char4
        console.log(out1, out2, out3)
        out = out + String.fromCharCode(out1) + String.fromCharCode(out2) + String.fromCharCode(out3)
    }
    return out
}
base64decode('aGFvaGFv') // haohao
base64decode('aGFvaGE=') // haoha
base64decode('aGFvaA==') // haoh
复制代码

上述解码核心是字符与base64字符集索引的映射,网上看到过使用AccII编码索引映射base64字符索引的方法

let base64DecodeChars = [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 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, -1, -1, -1, -1, -1, -1, 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, -1, -1, -1, -1, -1]
// 
let char1 = 'hao'.charCodeAt(0) // h -> 104
base64DecodeChars[char1] // 33 -> base64编码表中的h
复制代码

由此可见,base64DecodeChars对照accII编码表的索引存放的是base64编码表的对应字符的索引。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享