[对比] Base64 vs Base64URL vs Base32
三种编码,出自同一份 RFC。本文讲清它们实际的区别——字母表、开销、可读性、大小写敏感性——并告诉你在 URL、文件名、邮件、二维码和口头播报的场景下应该选哪一个。
// 速查对比表
Base64 Base64URL Base32
(RFC 4648 §4) (RFC 4648 §5) (RFC 4648 §6)
────────────────────────────────────────────────────────────────
Alphabet size 64 64 32
Bits per char 6 6 5
Characters used A-Z a-z 0-9 A-Z a-z 0-9 A-Z 2-7
+ / - _ (+ = for pad)
Padding = (required) = (optional) = (optional)
Case-sensitive Yes Yes No (A = a)
Size overhead ~33% ~33% ~60%
URL-safe No Yes Yes
Filename-safe No Yes Yes
DNS-safe No Yes Yes
Human-readable Medium Medium High
Human-spoken Painful Painful Doable
QR-code friendly OK OK Excellent
(alphanumeric) (alphanumeric) (alphanumeric mode)
// 它们的共性
三种都在 RFC 4648 里定义——这是一份 2006 年的单一文档,统一了"base-N 二进制到文本编码"家族。它们共享同一条底层算法:把二进制输入按 log₂(字母表大小) 的宽度重新分组,把每一组映射到一个字符,再按输出字的边界做填充。
• Base64:6-bit 一组 → 每 3 字节对应 4 个字符(4/3 比例,膨胀 ~33%)
• Base32:5-bit 一组 → 每 5 字节对应 8 个字符(8/5 比例,膨胀 ~60%)
• Base16(十六进制):4-bit 一组 → 每 1 字节对应 2 个字符(2/1 比例,膨胀 100%)
三者都是可逆的、确定性的,并且保留输入的每一个字节。它们是编码,不是压缩,也不是加密。任何拿到字符串的人都能解码回原数据。
// 什么时候用 BASE64(标准版,§4)
标准 Base64 是任何非 URL 文本通道的默认选项。MIME 邮件正文、PEM 包裹的证书、HTTP Basic Auth、S/MIME、XML-DSIG、data URI,以及大多数把二进制塞进文本的文件格式,用的都是它。字符 + / = 在带引号的属性值、带换行的邮件头、XML/JSON 字符串里都安全。
当下游消费方是一个能接受任意可打印 ASCII 的文本协议时,请用标准 Base64——特别是 RFC 级规范明确点名要求的场景(MIME:RFC 2045;PEM:RFC 7468;HTTP Basic Auth:RFC 7617;data URI:RFC 2397)。
- > 邮件附件(MIME base64 传输编码)
- > PEM 证书、密钥、CRL(-----BEGIN ... -----)
- > HTTP Basic Authentication 头的值
- > Data URI:data:image/png;base64,...
- > S/MIME 与 XML-ENC 载荷
- > 老式 SOAP / XML-DSIG 签名
- > JSON 文档里任何不会随后再塞进 URL 的二进制字段
// 什么时候用 BASE64URL(§5)
一旦你的 Base64 输出要接触 URL、Cookie 值、文件名、DNS 标签,或任何 + / = 会被迫做百分号编码的场景,就切到 Base64URL。它使用同样的 64 字符字母表,但替换了两个字符(+ → -、/ → _),并按惯例省略填充。
- > JWT 令牌——头、载荷、签名均为 base64url(RFC 7515)
- > OAuth 2.0 PKCE 的 code_challenge(RFC 7636)
- > OpenID Connect 的 state 与 nonce 参数
- > 魔法链接、邀请令牌、密码重置令牌
- > 可能被复制进 URL 的 Cookie 值
- > 由哈希派生的文件名——避免在文件名里出现 /
- > DNS 标签与 TXT 记录——允许连字符,不允许斜线
- > 内容寻址存储的键
- > 由随机字节生成的短链标识符
// 什么时候用 BASE32(§6)
Base32 只用 32 个字符:大写 A–Z 和数字 2–7。体积膨胀约 60%——比 Base64 糟得多——但你换来了三个 Base64 无法提供的非常独特的性质。
大小写不敏感。字母表只有大写。读或敲这个字符串的人可以完全不在意大小写;JBSWY3DP 和 jbswy3dp 解码结果完全相同。
去歧义字符。数字 0、1、8、9 被排除掉,因为它们在常见字体里长得像 O、I、B、g。只用 2–7。这让从纸面或手机屏幕抄录字符串时可靠得多。
二维码 alphanumeric 模式兼容。二维码有一种特殊的"alphanumeric"模式,用 ASCII 的一个子集按 5.5 bits/char 打包。Base32 完全落在这个子集里(外加填充),所以用 Base32 编码的载荷生成的二维码比 Base64 明显要小。
- > TOTP / HOTP 种子密钥——Google Authenticator、1Password、Authy 都用 Base32
- > Tor .onion v3 地址——ed25519 密钥的 56 字符 Base32 编码
- > 以磁力链接形式分享的 BitTorrent info-hash
- > 类 geohash 的可人工共享位置编码
- > 授权码与产品序列号
- > 打印型恢复码(2FA 备份码、钱包助记词)
- > 通过语音通道进行的类 DTMF 播报
- > 需要大小写不敏感存储的系统(DNS 标签)
// 体积开销——一个具体例子
// Input: a 32-byte SHA-256 hash
// Raw: 0x89abcdef… (32 bytes, binary — can't put in text)
// Hex (Base16): 40b2e2… (64 chars, 100% overhead)
// Base32: 5ENM4H2TQWMZ3O4OQBJAFY5Q (56 chars, 75% overhead)
// Base64: ia+N7/eZtRsPj5TqFoqUlD… (44 chars, 37% overhead)
// Base64URL: ia-N7_eZtRsPj5TqFoqUlD… (43 chars, 34% overhead, no padding)
// Input: a 16-byte UUID
// Hex: e7a6c1d0-4b7d-4c6c-8e2f-9f1a3e4b5c6d (36 chars incl. dashes)
// Base32: 5WTMDUCLPVGGZDRPTF… (26 chars)
// Base64: 56bB0Et9TGyOL58aPktcbQ== (24 chars)
// Base64URL: 56bB0Et9TGyOL58aPktcbQ (22 chars, no padding)
// 可读性基准
这就是 Base32 真正大放异彩的地方。试着把下面两行念出来:
• a+b/c1D2e3F/+g=——Base64。"小 a,加号,小 b,斜线,小 c,一,大 D,二,小 e,三,大 F,斜线,加号,小 g,等号。"光是大小写就极容易抄错。
• JBSWY3DPEHPK3PXP——Base32。"J-B-S-W-Y,三,D-P-E-H-P-K,三,P-X-P。"大小写无所谓,没有 0/O 或 1/l 的歧义,你可以在电话里高置信度地读出来。
这恰恰是 TOTP 用 Base32 的原因:有人得在二维码无法扫描时把种子从备用页面敲进验证器 app。Base64 的大小写敏感性会产生没完没了的客服工单。
// 二维码体积对比
二维码有一个特殊的"alphanumeric 模式",用一个 45 字符子集——0–9、A–Z(仅大写)、空格以及 $ % * + - . / :——以 5.5 bits/char 打包。任何超出这个子集的字符都会强制二维码进入"byte 模式",每字符 8 bit,生成的二维码明显变大。
Base32 完全落在 alphanumeric 子集里。Base64 则不然——小写字母以及 +/= 都会强制进入 byte 模式(严格说 +/ 在 alphanumeric 集里,但任何小写字母都会触发 byte 模式)。这意味着同一个载荷编码成 Base32 能装进更小的二维码——经常就是 21×21 的清爽二维码与 33×33 的拥挤二维码的差别。
// 解码时的坑
-
>
大小写敏感性——Base64 解码器会拒绝大小写不对的输入(
SGVsbG8=≠sgvSbg8=)。Base32 解码器通常会先归一为大写再查表,所以各种大小写都能接受。 -
>
填充——标准 Base64 要求
=填充;JWT/base64url 禁止它;Base32 把它作为可选(RFC 4648 §6)。请务必确认你用的解码器期望哪种。 -
>
空白字符——MIME Base64 会在第 76 列用 CR-LF 换行。不少解码器容忍空白,也有一些不容忍。在喂给
atob()或其等价函数之前先剥掉。 -
>
字母表撞车——把 Base64 喂给 Base32 解码器(反之亦然),起初看上去好像能工作——
A、B、C在两者里都合法——直到碰上+或=才静默失败。 - > Crockford Base32 是一种非 RFC 的变体,被 Stripe ID 和一些区块链系统使用。它的字母表不同(排除了 I、L、O、U),并支持校验位。不要把它与 RFC 4648 Base32 混淆。
- > Base32 扩展 Hex(RFC 4648 §7)——一种保留字典序的替代 Base32 字母表。用于 DNSSEC 的 NSEC3 记录。极易与标准 Base32 搞混。
// 决策流程图
Does the output go into a URL, cookie, filename, DNS, JWT, or OAuth flow?
│
├─ Yes → Does a human need to type or speak the string?
│ │
│ ├─ Yes → Base32 (uppercase, no ambiguous chars)
│ │ e.g., TOTP seeds, recovery codes
│ │
│ └─ No → Base64URL (more compact, URL-safe)
│ e.g., JWT, short tokens, hash-named files
│
└─ No → Does the output go into a QR code that must stay tiny?
│
├─ Yes → Base32 (fits in QR alphanumeric mode)
│
└─ No → Standard Base64
e.g., email MIME, PEM, HTTP Basic, data URI
// 那么 BASE16(十六进制)呢?
十六进制(RFC 4648 §8 里叫 Base16)是通用兜底方案。每一个 shell 工具、调试器、协议都认识它。它大小写不敏感、人类显而易见地可读、实现起来也最简单。但它会让你的载荷体积翻倍(100% 开销),所以它只用在体积固定且较小的标识符上:SHA-256 哈希(64 位十六进制 = 32 字节)、UUID(32 位十六进制 = 16 字节)、MAC 地址、调试器里的内存地址。
对于几十字节以上的数据,十六进制的线上体积成本真的让人痛。Base64 比 hex 小 50%,Base32 比 hex 小 25%。这就是为什么 Base64 在邮件和网页内嵌两类场景里胜出,而 hex 留在了调试与哈希展示场景。
// BASE58 / BASE62 / BASE85 又怎么样?
也存在一些非 RFC 的 base-N 编码,各有其利基用途:
• Base58——比特币地址、Flickr 照片 ID。排除四个易混字符(0、O、I、l)以及 +/,便于人工抄录。约 37% 开销。
• Base62——短链、Twitter snowflake ID。只使用字母数字(无特殊字符),在 URL 里无需转义。约 34% 开销。
• Base85 / Ascii85 / Z85——PostScript、PDF、老版本 ZeroMQ 帧。约 25% 开销(比 Base64 更紧凑),但字符选择比较棘手,会在 XML/JSON 转义里引发麻烦。
这些很有意思,但都不是 RFC 4648 标准化的。如果你要发布一份公开协议,Base64 或 Base64URL 几乎永远是更安全的默认——每种语言的标准库都已经支持。一旦选 Base58,你就等于把一个依赖塞给每一位消费方。
// 并排试一下
• Base64 编码器——标准字母表,带 URL-safe 切换开关
• Base64URL 编码器——URL-safe,并剥除填充
• Base32 编码器——RFC 4648 §6 字母表
• Base16(十六进制)编码器——用于对照
• Base58 编码器——比特币风格、对人友好的字母表
延伸阅读:
• Base64 是什么?它是如何工作的?
• URL-safe 与标准 Base64——完整故事
• 用 Base64 处理 UTF-8 与 Unicode