← 返回博客
语言: English 中文
Encoding 2026-06-09 7 分钟

Base64 不是加密:字母表、填充与真实传输故障

Base64 比 HTTP 还老。1989 年它出现在 RFC 1113 里,为一个早已失败的邮件加密项目(PEM)服务;两年后被 MIME 标准化。它存在的目的远比今天的用法狭窄:当年的 SMTP 协议只能传 7-bit ASCII,二进制(图片、附件、密钥)必须想办法塞进这条 7-bit 通道。Base64 就是当年最后胜出的偷渡方案。

Base64Base64urlMIMERFC 4648JWT

今天大部分 Base64 用法其实都在重复 1989 年那件事:把二进制塞进只懂文本的通道。CSS 里嵌一张 PNG、JWT 头部里塞一段 JSON、webhook 在字符串里夹一段二进制 payload——本质都是这件事。

它到底是什么

Base64 把 3 个字节(24 bit)重新表达成 4 个字符,这 4 个字符来自一个 64 个符号的字母表:AZaz09 加上两个特殊字符。24 bit 切成 4 组 6 bit,每组 6 bit 正好是 64 种可能,"64"这个数字就是这么来的。代价是输出比输入大 33%——这是用纯可打印 ASCII 表达任意字节的固定成本。

如果输入字节数不是 3 的整数倍,编码器会用 1 到 2 个 = 把最后一组补齐,让输出长度永远是 4 的倍数。整个机制就这些。没有更多花活。

也就是说,把 Base64 叫做"加密"的人,错得有点尴尬。Base64 是一个语法变换,字母表是公开固定的。任何人盯着 aGVsbG8= 看一会儿就能在心里把它解出来,任何解码器都能在微秒级完成。它不藏任何东西。

两套字母表

RFC 4648 §4 定义的标准 Base64 用 +/ 作为两个非字母数字符号,= 做填充。在邮件和大多数数据格式里这没问题,但 +/= 在 URL 里都有特殊含义——+ 在某些场景被解析成空格、/ 是路径分隔符、= 在查询串里是保留字符。把标准 Base64 直接放进 URL,需要再做一层百分号编码,否则就会出错——而大多数人不会做。

同一份 RFC 在 §5 定义了 Base64url:把 + 换成 -/ 换成 _,并允许省略填充。JWT、WebAuthn、OAuth state、绝大多数现代 API 都用 Base64url,原因正是上面那条。两套字母表互不兼容:用标准 Base64 解码器去解 Base64url 字符串,运气好出乱码,运气差直接报错(取决于解码器是否严格校验字母表)。

如果你拿到一个看起来像 Base64 的串解不出来,第一步永远是切换字母表试试

它最常被滥用的地方

我见过最多的反模式:有人想把一个 JSON 对象塞进另一个 JSON、或者塞进 header、或者塞进 URL 参数。为了避开转义,他把这段 JSON Base64 编一下。

这能跑,但几乎总是错的选择。接收方现在要先 Base64 解码再 JSON 解析,错误被埋深了一层;payload 多出 33% 的体积;语义可读性归零。JSON 嵌 JSON 应该是带正确转义的字符串,header 应该用结构化字段,URL 参数应该用百分号编码。

只有数据本来就是二进制时——公钥、证书、图片字节、加密 blob——Base64 才是正确工具。对于本来就是文本、又走文本通道的数据,Base64 是把"开销"伪装成"修复"。

第二个常见滥用是 data URI。把一张 200KB 的图片以 data:image/png;base64,... 内联到 HTML 或 CSS,会让 200KB 变 270KB,并且这张图永远不会被浏览器单独缓存。极小图标(< 4KB 量级)省下一次 HTTP 请求时是合理的;大图直接就是反优化。

真实环境下解码失败的几种姿势

实际拿到的 Base64 串里掺杂的奇形怪状超出多数人的想象:

  • 空白字符。RFC 4648 说解码器可以忽略换行和空格,注意是 MAY 不是 MUST。多数解码器忽略,但不是全部。从邮件里复制出来的 Base64 经常解不出来,原因就在这里——Outlook 在第 76 列处插 CRLF。PEM 文件也是故意分行的。
  • 错的字母表。标准 vs URL-safe,两者悄悄地不兼容。
  • 缺失或多余的填充。严格的解码器拒绝 aGVsbG8(无填充),宽松的接受。Base64url 经常完全省略填充,所以一个要求填充的 Base64url 解码器是写错了。
  • 非规范编码。当输入字节数不是 3 的倍数,最后一组里会有不用的 bit 位。RFC 4648 要求编码器把这些 bit 位置零;不是所有编码器都遵守。这意味着两个编码器可能对同一份输入产生两个都"合法"的 Base64 串。绝大多数解码器都接受。但安全敏感的场景(签名 JWT、去重 key)应该拒绝非规范编码,否则有 malleability 攻击空间。

如果一个 Base64 串解不出来,按这个清单跑一遍就能修复绝大多数情况:先 tr -d '[:space:]' 去空白,再把 -_ 换回 +/,再把长度补齐到 4 的倍数(用 =)。

它不做的事

  • 它不加密。(值得说两遍。)
  • 它不压缩。输出永远比输入大 33%。
  • 它不解决字符编码问题。如果你有一段 UTF-8 字节再 Base64,得到的是"UTF-8 字节的 Base64 串"——接收方拿到后还得先 Base64 解、再 UTF-8 解。
  • 它不让二进制"安全可打印"。Base64 串可以放进 7-bit ASCII 通道和大多数文本字段,但 +/= 仍然会在 shell 引号、SQL 标识符、URL 解析里翻车。

实用规则

  • URL、header、文件名、所有"路径形状"的载体:用 Base64url。
  • 邮件、MIME、证书、PEM:用标准 Base64。
  • 不要为了避开转义,把已经在文本通道里的文本数据再 Base64 包一层。该转义的层是哪一层就在哪一层做。
  • 不要把超过几 KB 的资源做成 data URI。
  • 解不出来时先换字母表,再断言数据损坏。
  • 永远不要指望 Base64 隐藏什么。它是传输约定,不是秘密。

主要参考资料

用于核对本文技术细节的标准与官方文档。

在浏览器里直接试

本站 Base64 工具支持两套字母表的本地编码 / 解码。当某个看起来是 Base64 的串解不出来时,先试试切换字母表,再判断它是不是真的损坏。所有处理都在浏览器里,数据不会发到任何服务器。

打开 Base64 工具

相关文章

继续阅读同一主题领域的实践指南。

查看全部文章

Cookie 同意

我们使用 Cookie 来增强您的体验并展示相关广告。您可以自定义您的偏好。