Base64 不是加密:它是干什么用的,真要保密该用什么

Base64 把字节变成文本,好让它们在只认文本的系统里活下来。任何人一行代码就能还原。它该出现在哪(邮件、JSON、data URL)、它多花的那 33%,以及编码、哈希、加密三者的区别。

一句话

Base64 是用 64 个安全字符(A–Z、a–z、0–9、+、/)写出任意字节序列的一种方法。它的存在是为了让二进制数据——图片、PDF、密钥——能穿过只处理文本的系统:邮件正文、JSON 字符串、URL、HTML 属性。它是编码,不是加密:没有密钥,解码就是一个函数调用,每种语言、每个浏览器的开发者控制台里都有。“用 Base64 存”的密码,就是多绕了一步的明文密码。

它为什么存在

电子邮件是 1970 年代为 7 位 ASCII 设计的。附件出现(MIME,1992 年)之后,一张 JPEG 的字节——用到全部 256 个值,其中一些在邮件服务器眼里是“行结束”或“消息结束”——必须伪装成无害的文本。Base64 就是答案:取三个字节(24 位),拆成四组 6 位,每组映射到 64 个字符之一。每 3 字节变 4 字符,这就是著名的 33% 开销:3 MB 的附件是 4 MB 的邮件,也是为什么“25 MB”的邮件上限只装得下 18 MB 的文件。

凡是只有文本这一条通道的地方都用同一个把戏:把小图直接嵌进网页(data:image/png;base64,…)、把二进制令牌放进 HTTP 头(Authorization: Basic …)、在 PEM 文件里装证书和密钥、把字节数组塞进 JSON 字段。

让人栽跟头的几种变体

  • 标准版 vs URL 安全版。 标准 Base64 用 + 和 /,两个在 URL 里都有含义。URL 安全字母表(RFC 4648 §5)换成 - 和 _。一方产生、按另一方解码的令牌,会在这两个字符上失败。JWT 用的是 URL 安全版。
  • 填充。 输出长度永远是 4 的倍数,用 = 补齐。有些生成方把填充去掉了(又是 JWT);有些解码器坚持要它。一个看起来合法的字符串解不开,试着补 = 到长度能被 4 整除。
  • 换行。 MIME 每 76 个字符换行,PEM 每 64 个。解码器一般忽略空白,但不是全部。
  • 底下的文本编码。 Base64 编码的是字节。要把一个字符串 Base64,得先把它变成字节,而选 UTF-8、UTF-16 还是 Latin-1 会改变结果。JavaScript 里那个经典 bug——btoa("é") 抛错——正是这个:btoa 只接受 Latin-1。先编成 UTF-8 字节。

Base64 工具处理两种字母表、填充、换行和 UTF-8,解码结果可以按文本显示也可以下载成文件,所以一个 data: URL 或一个邮件附件能在浏览器里还原成原件。

编码、哈希、加密

这三个词在随意的写作里被混用,意思完全不同。

可逆? 要密钥? 用途
编码(Base64、hex、URL 编码) 可逆,任何人都行 不要 让数据适配通道
哈希(SHA-256、MD5) 不可逆 不要 给数据取指纹;验证它没被改过
加密(AES、RSA) 可逆,需要密钥 要 让没有密钥的人看不到数据

文件的哈希让你核对下载的东西和发布者放上去的是否一致——哈希计算在浏览器里算 MD5、SHA-1 和 SHA-256/384/512,并与粘贴的校验值比对。它不隐藏任何东西:文件还是那个文件。三者中只有加密提供保密性,而它的强度取决于密钥——这就是真正随机的密码派上用场的地方。

如果你正打算把秘密 Base64 一下

别。具体说:

  • 配置文件里的 Base64 密码(老式 Java 和 .NET 应用的常见做法),对任何打开文件的人来说就是明文密码。
  • 前端 JavaScript 里“混淆”过的 API 密钥,在网络面板里一览无余,Base64 与否没区别。
  • 明文 HTTP 上的 Basic 认证,用户名和密码是 Base64 编码发送的,也就是等于明文发送。只有在 HTTPS 里它才可接受,因为加密由传输层完成。

数据必须存储或发送并且保密,就用真正的密码算法加密(平台加密库里的 AES-256-GCM);然后,如果它必须穿过文本通道,再把密文 Base64。加密之后编码没问题。用编码代替加密才是错误。

本文用到的工具