Base64
Base64 是一种将二进制数据转换为文本的优雅方法,主要用于对 8 bit(一个字节)为单位的数据进行编码
编码
通过 Base64 编码的出来的文本只包含 a-z、A-Z、0-9、+、/,共 26 + 26 + 10 + 2 等于 64 个字符,这就是为什么它被称为 Base64
在 JS 中,浏览器和 NodeJs 都直接提供了编码的 API:
btoa("peterroe") //=> cGV0ZXJyb2U=
atob("cGV0ZXJyb2U=") //=> peterroe即然 Base64 的文本字符至多只有 64 种,所以我们给每一个字符重新编码:
由于只有 64 个字符可以编码,而且 2^6 = 64,所以每个字符可以用 6 个 bit 表示
编码算法
用 "peterroe" 这个字符串举例子,在 ASCII 中的编码是:
01110000 01100101 01110100 01100101 01110010 01110010 01101111 01100101
p e t e r r o e我们将上面的 8 bit 一组在拆分为 6 bit 一组:
011100 000110 010101 110100 011001 010111 001001 110010 011011 110110 0101在代入我们上面的字母表,就得到对应的 Base64 编码:
011100 000110 010101 110100 011001 010111 001001 110010 011011 110110 0101(00) 不足6位补0
c G V 0 Z X J y b 2 U但是 Base64 还有一个条件,得到的结果长度需要满足是 4 的倍数, 而"cGV0ZXJyb2U".length % 4 = 3,所以需要在后面补 1 个 = ,得到最终结果 cGV0ZXJyb2U=
根据上面的编码规则计算,可以算出编码后的字符大约比编码前大了 33%,但是这个代价换来的是安全
安全性
为什么被选择加入字母表的 64 个字符是这 26 个?因为它们被认为是安全的
ASCII 码的 128 ~ 255 之间的值是不可见字符,在网络传输过程中,这些字符很有可能因为设备对字符的处理方式不同,导致传输的数据出错
URL安全的 base64
有上面 Base64 的字符集可以看到,编码的结果可能包含 +、/ 和 =,而这些字符是 URL 的保留字符或不安全字符,所以直接将 Base64 的结果放到 URL 中是不安全的
根据 RFC4648 描述,在 URL 安全的 Base64 编码中,将 + 替换为中划线 -,将 / 替换为下划线 _,所以我们有第二张编码表:
所以通过如下方法,可以得到 URL 安全的 Base64 编码
btoa("data").replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');但是 btoa 不是完美的
btoa 的限制
在 JS 中要使用 btoa 要小心谨慎,根据 MDN-btoa 中的介绍
btoa() 函数将一个 JavaScript 字符串作为其参数。而 JavaScript 字符串使用 UTF-16 字符编码表示:在这种编码中,字符串使用一串 16 比特(2 字节)的单元来表示。每一个 ASCII 字符会被填充到每个单元的第一个字节中,而很多其他的字符则不然
根据设计,Base64 仅将二进制数据作为其输入。而在 JavaScript 字符串中,这意味着每个字符只能使用一个字节表示。所以,如果你将一个字符串传递给 btoa(),而其中包含了需要使用超过一个字节才能表示的字符,你就会得到一个错误,因为这个字符串不能被看作是二进制数据
这意味着,比如字符串中包含像 ✓ 这样的非 ASCII 范围的字符,则会抛出错误
btoa("✓") // throws exceptionURL 的绝对安全
encodeURIComponent
为了追寻 URL 的绝对安全,在 JS 中,可以使用 encodeURIComponent,对任意包括中文等特殊字符进行编码
const b64 = btoa("peterroe") //=> cGV0ZXJyb2U=
const safeB64 = encodeURIComponent(b64) //=> cGV0ZXJyb2U%3D
const encodedChars = encodeURIComponent("🌟8$K!ПрB#y2&🚀C9zち1qحبًp")
// => '%F0%9F%8C%9F8%24K!%D0%9F%D1%80B%23y2%26%F0%9F%9A%80C9z%E3%81%A11q%D8%AD%D8%A8%D9%8Bp'
decodeURIComponent(encodedChars)
// => '🌟8$K!ПрB#y2&🚀C9zち1qحبًp'encodeURIComponent 对所有特殊字符进行编码,这很有用,但是某些情况下我们可能不需要将特殊字符全部编码,尤其是编码一个 URI:
encodeURIComponent("https://peterroe.icu? hello")
//=> 'https%3A%2F%2Fpeterroe.icu%3F%20hello'上面使用 encodeURIComponent之后,:/ 等字符串也进行了编码,导致浏览器不能直接识别这个 URI,所以 JS 还提供了另一个方法: encodeURI
encodeURI
用于对整个 URI 进行编码,但会保留某些字符,如冒号 :、正斜杠 /、问号? 和井号 #,因为它们在 URI 中具有特殊含义
const encodedChars = encodeURI("https://peterroe.icu? hello")
// => 'https://peterroe.icu?%20hello'
decodeURI(encodedChars)
// => 'https://peterroe.icu? hello'这样就可以在保证 URI 合法的情况下,编码特殊字符
TIP
encodeURI 适合对 URI 整体进行编码,而 encodeURIComponent 适合局部编码,这也符合它的名称(编码URI的组件)
二进制 和 Base64 转换
有的时候,需要从前端传入图片到后端,以及后端读取本地图片返回前端,这里是二者相互转换的方法
if (isBinaryObject(node.filename)) {
const buffer = readFileSync(item.path, null);
node.content = buffer.toString('base64');
} else {
node.content = readFileSync(item.path, 'utf-8');
}
if(isBinaryObject(node.filename)) {
// 需要去掉前缀
const buffer = Buffer.from(node.content.replace(/^data:image\/\w+;base64,/, ''), 'base64');
writeFileSync(node.path, buffer);
} else {
writeFileSync(node.path, node.content, 'utf-8');
}