先把结论放在最前面:Base64 是一种编码,不是加密。它没有任何密钥,任何人拿到那串字符都能在零点几秒内还原出原文。如果用它来"保护"密码或者密钥,等于什么都没做。
那它解决的是什么问题
计算机世界里有两类东西经常打架:一类只能处理 0 和 1 的字节流(图片、压缩包、二进制协议),另一类只能安全传输一部分字符(早期的电子邮件、JSON、URL、XML)。
Base64 就是这两者之间的桥。它把任意字节流重新表示为 64 个"安全字符"组成的字符串:
A–Z a–z 0–9 + /
再加上一个填充符 =。这 64 个字符在几乎所有文本协议里都是安全的,不会被转义、截断或者误解。
为什么体积会变大
每 3 个字节(24 位)被切成 4 组、每组 6 位,每一组映射成一个字符。所以 3 字节进、4 字符出,体积固定膨胀到原来的 4/3,也就是大约 133%。
这就是为什么一封带 3 MB 附件的邮件,编码后大约会变成 4 MB。
常见的三种变体
| 变体 | 改动 | 典型场景 |
|---|---|---|
| 标准 | + / = | 通用 |
| URL 安全 | +→-,/→_,去掉 = | URL、文件名 |
| MIME | 每 76 字符插入换行 | 电子邮件 |
URL 安全变体存在的原因很直接:+ 和 / 在 URL 里有自己的含义,直接放进去会被误解。+ 在查询串里常被解释成空格,/ 则是路径分隔符。
一个容易踩的坑:多字节字符
在浏览器里最容易写错的实现是这样的:
btoa('中文') // 抛错:InvalidCharacterError
btoa 只接受每个字符都落在 0–255 范围内的字符串,而 JavaScript 的字符串是 UTF-16 的,中文根本塞不进去。正确做法是先用 TextEncoder 转成字节,再逐个字节送进 btoa:
const bytes = new TextEncoder().encode(text);
let bin = '';
for (const b of bytes) bin += String.fromCharCode(b);
const out = btoa(bin);
反向解码时同理,要先把 atob 的结果还原成字节数组,再用 TextDecoder 解码回字符串,否则中文会变成乱码。
那到底什么时候该用加密
如果需要的是"别人看不到",请用正经的加密:传输层用 TLS,对称加密用 AES-GCM,密码存储用 bcrypt / argon2 这类专门的算法(而且严格来说它们是哈希而非加密)。
Base64 该出现的地方是:把二进制数据塞进 JSON 字段、在 data: URI 里内嵌小图标、给签名结果做一个不破坏协议的封装。这些场景里它做得很好 —— 只是别指望它保密。
想自己验证一下?可以到 Base64 编解码 里粘一段中文试试,顺便勾上 URL 安全对比一下输出差异。