← 全部文章

Base64 不是加密:开发者常见的误解

Base64 编码看起来像乱码,但任何人一行函数就能解开。学习 Base64 到底是什么、为什么经常被误认为是加密,以及真正需要保护数据时该用什么。

「我把它 Base64 编码一下,这样就没人能读了。」这句话出现在代码评审、Slack 消息以及 Stack Overflow 答案里的频率,比它应该的高得多。Base64 不是加密。它不是混淆。它不是任何形式的安全机制。把它解码回来只需要一次函数调用,且不需要任何密钥或秘密。本文讲清楚 Base64 到底是什么、误解为何根深蒂固,以及当你真的需要保护数据时该用什么。

Base64 实际是什么

Base64 是一种编码方案 —— 二进制数据与 64 个可打印 ASCII 字符(A–Za–z0–9+/,以及 = 用作填充)之间的可逆映射。

每 3 字节的输入会变成 4 个 Base64 字符。算法是完全确定性的,不需要任何密钥:

// 编码
btoa("hello")          // → "aGVsbG8="
btoa("secret password") // → "c2VjcmV0IHBhc3N3b3Jk"

// 解码 —— 一次调用,无需密钥
atob("c2VjcmV0IHBhc3N3b3Jk") // → "secret password"

就这样。拿到编码字符串的任何人都能立刻解码,在任何语言、任何环境中,无需任何额外信息。

误解为何存在

Base64 编码后的字符串看起来像加密后的数据。它们由大小写字母、数字和特殊字符混合而成,与原始输入毫无可见相似。这种视觉噪声足以迷惑一个非技术观察者 —— 而出人意料的是,常常也能迷惑技术人员。

这一模式还会被「位置邻近」所强化。Base64 在安全相关的场合里到处都是:

  • HTTPS 证书是 Base64 编码的(PEM 格式)
  • JWT token 的 header 和 payload 使用 Base64url 编码
  • HTTP Basic 认证把凭据按 Base64 发送
  • SSH 密钥按 Base64 存放
  • 加密后的密文经常用 Base64 作为传输形式

在这些与安全相关的位置反复见到 Base64,开发者就把编码与安全混为一谈。编码只是外包装;安全来自底层的密码学操作。

编码 vs 加密:核心区别

区别简单且绝对:

编码(Base64)加密(AES、RSA…)
目的把二进制数据表示为文本对未授权方隐藏数据
需要密钥?
任何人都能逆转?能 —— 一次函数调用只有掌握正确密钥才能
提供机密性?是(正确使用时)
输出大小比输入大约多 33%因算法而异

加密用密钥变换数据,使得在没有密钥的情况下恢复原文在计算上不可行。Base64 编码没有这样的属性。

现实世界的安全错误

把「编码过的」秘密放在源代码里

// ❌ 这并不秘密 —— 任何人都能解码
const API_KEY = atob("c2stbGl2ZS1hYmMxMjM0NTY3ODk=");

// 任何读到这段代码的人都会跑:
atob("c2stbGl2ZS1hYmMxMjM0NTY3ODk=") // → "sk-live-abc123456789"

自动化的密钥扫描工具(GitHub 的 secret scanning、GitGuardian、truffleHog)在检测时都会解码 Base64 字符串。如果你指望 Base64 能拦住它们,办不到。

HTTP Basic 认证

// Authorization 头看起来像这样:
Authorization: Basic dXNlcjpwYXNzd29yZA==

// 这段 Base64 一解就明:
atob("dXNlcjpwYXNzd29yZA==") // → "user:password"

HTTP Basic 认证只有在 HTTPS 上才安全 —— 真正的加密由 TLS 层提供。头里的 Base64 纯粹是为了把 username:password 编码为单个文本值,并不是为了隐藏。在明文 HTTP 上,任何截获流量的人都能立刻读到凭据。

把 JWT「token」当作秘密对待

// 一个 JWT 看起来是用点号连接的三段 Base64url:
eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjQyfQ.abc123

// 中间一段(payload)解码后是:
atob("eyJ1c2VySWQiOjQyfQ") // → '{"userId":42}'

JWT payload 没有被加密 —— 它们是 Base64url 编码并被签名的。任何拿到 token 的人都能读 payload。签名能防篡改,但不提供机密性。永远不要把敏感数据(密码、信用卡号、个人信息)放进 JWT payload,除非你在用 JWE(JSON Web Encryption)—— 那是另一个、相当少见的标准。

在 URL 中混淆数据

// 老应用里常见的模式
/profile?data=eyJ1c2VySWQiOjQyfQ==

// 贴到任何解码器里:
atob("eyJ1c2VySWQiOjQyfQ==") // → '{"userId":42}'

URL 里的 Base64 没有安全增益。它还很容易出错 —— 标准 Base64 里的 +/ 不是 URL 安全的,必须做百分号编码,因此才有 Base64url 这个变体(+-/_,不填充)。

Base64 真正的用途

Base64 有合理且重要的用途 —— 只是不用来做安全:

  • 把二进制数据嵌入文本格式中 —— 把图片、字体、文件作为 data: URI 嵌入 HTML/CSS,或附在 JSON API 响应中
  • 邮件附件 —— MIME 编码用 Base64 在只支持 7 位 ASCII 的协议上传输二进制文件
  • 传输安全 —— 某些通道会破坏二进制数据(空字节、控制字符);Base64 保证内容完整通过
  • 不透明标识符 —— 把 ID 或游标编码为 Base64,向 API 使用者传达「别试着解析它」,尽管它并不提供任何安全性

真正需要安全时该用什么

如果你的目标是防止未授权访问数据,请使用真正的密码学工具:

目标用什么
对静态数据加密AES-256-GCM(对称加密)
对传输中数据加密TLS 1.3(HTTPS)
哈希密码bcrypt、scrypt 或 Argon2 —— 永远不要只用 SHA-256
签名 tokenHMAC-SHA256(如 JWT 的 HS256)或 RS256
存放秘密环境变量、秘密管理器(Vault、AWS Secrets Manager)
端到端加密 tokenJWE(JSON Web Encryption)—— 而不是普通 JWT

当你确实需要加密 token 时:JWE 与 JOSE

标准 JWT 是签名的(JWS),不是加密的 —— payload 可读。如果你真的需要让 claims 具有机密性,请改用 JWE(JSON Web Encryption,RFC 7516)。JWE 属于 JOSE 标准家族:

  • JOSE 算法 —— 内容加密(例如 A256GCM)与密钥管理(例如 RSA-OAEP-256ECDH-ESA256KW)。「alg」决定内容加密密钥如何被包装;「enc」决定 payload 本身如何加密。
  • 密钥包装 —— JWE 为每条消息生成新鲜的内容加密密钥,并把它在接收方的密钥下包装(非对称或对称)。被包装的密钥随 token header 传送。这样同一明文可以加密给不同接收方,而不必重新加密正文。
  • JWE 紧凑形式 —— 用点号连接的五段 Base64url(header.key.iv.ciphertext.tag),视觉上像 JWT,但是四段而不是三段。

请选择经过审计的库(jose、node-jose、python-jose),不要自己实现 JWE —— 算法与密钥格式组合很微妙,悄无声息地回退到弱算法是经典的 JOSE 漏洞。

针对你自己代码的快速自查

在你的代码库里搜 btoa(atob(Buffer.from(…, 'base64')。对每一处问自己:

  • 我这样做是为了让文本能塞进某个字段完整通过传输?→ 没问题。
  • 我这样做是为了对某人隐藏数据?→ 改用真正的加密或访问控制。

常见问题

Base64 是加密吗?

不是。Base64 是一种无密钥的可逆编码 —— 任何人一次函数调用(atob())就能解码。加密需要密钥,没有密钥时恢复原文是不可行的。

用 Base64 存密码或 API 密钥安全吗?

不安全。解码极其容易,密钥扫描器还会自动解码 Base64。请使用秘密管理器或环境变量,并用 bcrypt、scrypt 或 Argon2 哈希密码。

任何人都能读 JWT payload 吗?

能。JWT payload 是 Base64url 编码并被签名的,不是加密 —— 任何拿到 token 的人都能读到每一个 claim。除非用 JWE,否则永远不要把秘密放进 JWT。把 token 贴进在线工具有风险;见 为什么不要把敏感 JSON 贴到在线工具里

Base64 究竟是用来做什么的?

安全地把二进制数据表示为文本 —— data: URI、邮件(MIME)附件、以及在只支持 ASCII 的通道上传输。它是包装,不是安全措施。

在浏览器里编解码

现在就想检视一段 Base64 字符串?fixjson.org 上的 Base64 编码与解码 工具能即时编码或解码任意字符串 —— 完整 Unicode 与 UTF-8 都支持 —— 而且不需要安装任何东西。在调试 JWT payload、检视 API 响应,或者看 data: URI 里藏了什么时都有用。