Base64 编码原理:为什么中文解码会乱码
Base64 天天在用,但绝大多数「乱码」问题并不是编码算错了,而是字符集对不上。
一、先纠正一个最常见的误解:Base64 不是加密
Base64 只是把二进制字节重新排列成 64 个可打印字符,任何人拿到密文都能立刻还原。它解决的是「如何在只能传文本的通道里安全传二进制」,不是保密问题。把密码用 Base64 存储等同于明文存储。
二、编码过程:为什么 3 个字节变成 4 个字符
Base64 的名字来自它的字符表:A-Z、a-z、0-9、+、/,一共 64 个。每个字符能表达 6 个二进制位(26=64),而 1 个字节是 8 位,所以每 3 个字节(24 位)正好切成 4 组 6 位:
原始字节: 01001000 01101001 00100001 ("Hi!")
重新切分: 010010 000110 100100 100001
对应索引: 18 6 36 33
查表结果: S G k h
最终 Base64: SGkh
如果原文长度不是 3 的倍数,就用 = 补齐。所以你会看到结尾出现一个或两个等号——SGk=(2 字节)、SGkh(3 字节)。
补齐的
= 不是数据,只是占位符。很多解码器允许省略,但严格实现会拒绝长度不合法(非 4 的倍数)的输入——这就是「解码失败」的常见原因之一。三、中文乱码的真正原因
关键点:Base64 编码的是字节,不是字符。同一个汉字在不同字符集里字节完全不同:
| 字符 | UTF-8 字节 | GBK 字节 |
|---|---|---|
| 中 | E4 B8 AD | D6 D0 |
| 文 | E6 96 87 | CE C4 |
“中文”在 UTF-8 下是 6 个字节,Base64 结果是 5Lit5paH;在 GBK 下同样是 4 个字节,结果是 1tDOxA==。两边都能正确编解码,但如果用 UTF-8 去解 GBK 的字节,得到的自然是一串乱码或直接抛错。
所以排查乱码的顺序应该是:
- 先确认 Base64 解码本身成功(不报错);
- 再确认解出来的字节该用哪种字符集解释(UTF-8 / GBK / ISO-8859-1);
- 最后才怀疑编码环节。
浏览器里
atob() 返回的是「每字符一字节」的二进制字符串,直接当文本用会乱码。正确做法是把它转成 Uint8Array,再用 TextDecoder('utf-8') 解码。四、URL 里的 Base64:为什么要换掉 + 和 /
标准 Base64 的 + 和 / 在 URL 中有特殊含义(+ 会被当成空格,/ 是路径分隔)。所以出现了 URL 安全变体(Base64URL,RFC 4648 §5):
| 标准 | URL 安全 |
|---|---|
+ | - |
/ | _ |
保留 = | 通常省略 = |
JWT 就用的这套变体。如果你拿标准解码器去解 JWT 的 payload,遇到 -、_ 会报「非法字符」——不是 token 坏了,是变体不同。
五、几个高频踩坑
- 把换行当数据:邮件或配置文件里的 Base64 常带换行(每 76 字符一行),解码前要先去空白。
- Data URL 前缀:
data:image/png;base64,后面才是数据,连前缀一起解码必然失败。 - 体积膨胀 33%:4/3 的固定开销。图片转 Base64 塞进 HTML 会让文件大三分之一,且无法利用浏览器缓存。
- 误当加密用:Base64 编码的 API Key 依旧是明文,日志里泄露就是泄露。
六、动手验证
下面这些工具可以直接对照本文结论做实验,全部在浏览器本地运行,内容不会上传: