Base64原理及实现

2019/02/03

base64

Base64是用64个字符来表示数据的方法。Base64要求把每三个8Bit的字节转换为四个6Bit的字节,然后把6Bit再添两位高位0,组成四个8Bit的字节,也就是说,转换后的字符串理论上将要比原来的长1/3。如果要转码数据字节不是3的倍数,最后一组填充1到2个0字节,并在最后编码完成后在结尾添加1到2个=号。 转换后,我们每个字节范围为[00000000-00111111],所以我们可以用一个64码表来得到我们想要的字符串(也就是最终的Base64编码)标准的Base64用[A-Z,a-z,0-9,+,/]64个字符编码。

编码

深入了解base64前,我们需要先了解编码。编码是信息从一种形式或格式转换为另一种形式的过程。计算机中,所有的数据在存储和运算时都使用二进制示,n位二进制组合成2的n次方个不同的信息,给每个信息规定一个具体码组,这种过程叫编码。而具体用哪些二进制数字表示哪个符号,每个人都可以约定自己的一套编码。

如常用的ASCII是由美国国家标准学会制定的一种单字节字符编码,标准ASCII码使用7位二进制数(剩下的1位二进制为0)来表示所有的大写和小写字母,数字0到9、标点符号。在有的语言中128个符号是不够的,有些编码方案利用字节中闲置的最高位编入新的符号,有的利用两个(GBK等)或更多字节(utf-32等)表示一个符号(汉字)

Base64编码

base64编码过程:

base64解码过程:

base64编码过程逆向即为解码

javascript代码实现

Base64 = {
  _table: [
    'A',
    'B',
    'C',
    'D',
    'E',
    'F',
    'G',
    'H',
    'I',
    'J',
    'K',
    'L',
    'M',
    'N',
    'O',
    'P',
    'Q',
    'R',
    'S',
    'T',
    'U',
    'V',
    'W',
    'X',
    'Y',
    'Z',
    'a',
    'b',
    'c',
    'd',
    'e',
    'f',
    'g',
    'h',
    'i',
    'j',
    'k',
    'l',
    'm',
    'n',
    'o',
    'p',
    'q',
    'r',
    's',
    't',
    'u',
    'v',
    'w',
    'x',
    'y',
    'z',
    '0',
    '1',
    '2',
    '3',
    '4',
    '5',
    '6',
    '7',
    '8',
    '9',
    '+',
    '/'
  ],
  _getReg() {
    return new RegExp(`^[${this._table.join('')}]+={0,2}$`);
  },
  encode(bin) {
    const codes = [];
    const binLength = bin.length;
    un = binLength % 3;

    for (let i = 2; i < binLength; i += 3) {
      let c = bin[i - 2] << 16;
      c |= bin[i - 1] << 8;
      c |= bin[i];

      // 0x3f代表16进制数3F即0011 1111
      codes.push(this._table[c >> 18 & 0x3F]);
      codes.push(this._table[c >> 12 & 0x3F]);
      codes.push(this._table[c >> 6 & 0x3F]);
      codes.push(this._table[c & 0x3F]);
    }

    if (un == 1) {
      const c = bin[binLength - 1] << 16;
      codes.push(this._table[c >> 18 & 0x3F]);
      codes.push(this._table[c >> 12 & 0x3F]);
      codes.push('=');
      codes.push('=');
    }
    if (un == 2) {
      let c = bin[binLength - 2] << 16;
      c |= bin[binLength - 1] << 8;
      codes.push(this._table[c >> 18 & 0x3F]);
      codes.push(this._table[c >> 12 & 0x3F]);
      codes.push(this._table[c >> 6 & 0x3F]);
      codes.push('=');
    }

    return codes.join('');
  },
  decode(base64Str) {
    const bin = [];
    const base64StrLen = base64Str.length;

    if (!this._getReg().test(base64Str)) {
      throw 'Base64编码格式错误';
    }
    if (base64StrLen % 4 !== 0) {
      throw 'Base64编码长度错误';
    }

    const eqCount = base64StrLen - base64Str.indexOf('=');

    for (let i = 3; i < base64StrLen; i += 4) {
      let code = 0;

      const [c1, c2, c3, c4] = [base64Str.charAt(i - 3), base64Str.charAt(i - 2), base64Str.charAt(i - 1), base64Str.charAt(i)];

      code = code << 6 | this._table.indexOf(c1);
      code = code << 6 | this._table.indexOf(c2);

      if (c3 !== '=' && c4 !== '=') {
        code = code << 6 | this._table.indexOf(c3);
        code = code << 6 | this._table.indexOf(c4);
      }
      if (c3 !== '=' && c4 === '=') {
        code = code << 6 | this._table.indexOf(c3);
        code = code << 6 | 0;
      }
      if (c3 === '=') {
        code = code << 6 | 0;
        code = code << 6 | 0;
      }

      // 0xff即11111111
      bin.push(code >> 16);
      // 取得低八位
      bin.push(code >> 8 & 0xFF);
      bin.push(code & 0xFF);
    }

    switch (eqCount) {
      case 1:
        bin.pop();
        break;
      case 2:
        bin.pop();
        bin.pop();
        break;
      default:
        break;
    }

    return bin;
  }
};

const str = `1!@#$%^ghghna三个卡就能`;
const ba = Base64.encode(Buffer.from(str));

console.log(`编码前:${str}`); // 1!@#$%^ghghna三个卡就能
console.log(`编码后:${ba}`); // MSFAIyQlXmdoZ2huYeS4ieS4quWNoeWwseiDvQ==
console.log(`解码:${Buffer.from(Base64.decode(ba)).toString()}`); // 1!@#$%^ghghna三个卡就能