base64是加密还是编码?我用JS来讲给你听。
写于2023年04月25日

是编码。

base64根本不是什么加密,就是一种编码方式,你问为什么,且听我细细道来。

二进制
我们都知道大家应该知道,机器语言一般都是二进制的,在一个字节( byte )上,是八个位( bit ),每位上可以存放 0 或者 1,通常一个字节可以存入一个 ASCII 码,两个字节存放一个国标汉字,现在大多数是 UTF-8 的编码,用的是三个字节。所以下面咱们以汉字为例,来说明 base64 为什么只是一种编码而不是一种加密方式。


以《现代汉语词典》第5版第一页第一个字「吖」为例,UTF-8 编码下转为二进制,咱们先写一段简单的代码:

1
2
3
4
5
6
for (let value of new Buffer.from('吖').values()) {
console.log(value.toString(2));
}
// 11100101
// 10010000
// 10010110

简单来说就是「吖」占用了三个字节,每个字节分别是 229,144,150,转换为二进制编码就是「11100101 10010000 10010110」,共计二十四个位。

当使用 base64 编码之后,会以每三个字节为一组,把「11100101 10010000 10010110」转换成「111001 011001 000010 010110」,然后三字节就变成了四字节。用代码来写一下就是:

1
2
3
4
5
6
7
8
9
10
let binary = '';
for (let value of new Buffer.from('吖').values()) {
binary += value.toString(2);
}
// 刚好一个汉字 24 位,我就不写补 0 的方法了。
const base64Arr = [];
for (let i = 0; i < binary.length / 6; i++) {
base64Arr.push(binary.slice(6 * i, 6 * (i + 1)));
}
console.log(base64Arr); // [ '111001', '011001', '000010', '010110']

简单点来说就是三八二十四换成了四六二十四。那每个字节前面剩下的两个位,怎么办呢?嗯…我有 10 块,和我有 00000010 块,代表的意思一样,所以可以简单理解为在计算机中,没有就是 0。

但凭空多了一个字节,有什么好处呢?因为 base64 编码每个字节只写了六个位,所以一共对应的只有 2^6=64 个可打印的字符(其实 base64 的 64 是这个意思啦),在 Base64 中的可打印字符包括字母 A-Za-z、数字 0-9,这样共有 62 个字符,此外两个可打印符号在不同的系统中而不同。在 JavaScript 中,这两个字符是 +/

所以先把「111001 011001 000010 010110」转换为十进制,咱们来简单的修改一下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let binary = '';
for (let value of new Buffer.from('吖').values()) {
binary += value.toString(2);
}
// 刚好一个汉字 24 位,我就不写补 0 的方法了。
const base64Arr = [];
for (let i = 0; i < binary.length / 6; i++) {
base64Arr.push(binary.slice(6 * i, 6 * (i + 1)));
}

base64Arr.forEach(str => console.log(parseInt(str, 2)));

// 57
// 25
// 2
// 22

按照编码表(第一位是 0 位,为 A)就能知道「吖」转为 base64 之后是「5ZCW」。

那么我上面算的这些对不对呢?咱们来验证下,JavaScript 提供了简单的方法:

1
2
3
new Buffer.form('吖').toString('base64');

// 5ZCW

所以反过来一样可以知道「5ZCW」是「吖」,这也是 base64 只是一种编码而不是一种加密方式的原因。

注意:在浏览器中可以直接使用 btoa 来直接把普通字符串转换成 base64 字符串,但无法转换汉字,这时候需要先额外把汉字转换成其他编码(比如 URI),但这样和直接转为 base64 的结果是不同的。