时间线
| 年份 | 文档 | 贡献 |
|---|---|---|
| 1987 | RFC 989(PEM 第一部分) | 64 字符的“可打印编码”,用 = 填充,另用 * 标记未加密部分 |
| 1988、1989 | RFC 1040、RFC 1113 | PEM 修订草案 |
| 1992 | RFC 1341(MIME) | Base64 成为电子邮件的 Content-Transfer-Encoding |
| 1993 | RFC 1421(PEM)、RFC 1521(MIME) | PEM 每行恰好 64 个字符,MIME 修订版 |
| 1996 | RFC 2045(MIME) | 沿用至今的 MIME 定义:每行最多 76 个字符 |
| 1997 | RFC 2152(UTF-7) | 在 Unicode 文本编码中使用 Base64 |
| 2003 | RFC 3501(IMAP4rev1) | 用 , 表示邮箱名称的修改版 Base64 |
| 2003 | RFC 3548 | 第一份独立规范:base64、base64url、base32、base16 |
| 2006 | RFC 4648 | 取代 RFC 3548,新增 base32hex 和测试向量 |
| 2007 | RFC 4880(OpenPGP) | 带 CRC-24 校验和的 Radix-64 封装 |
| 2015 | RFC 7468 | PEM 风格证书和密钥文件的严格规则 |
| 2015 | RFC 7515(JWS) | 令牌使用不带填充的 Base64URL |
| 2024 | RFC 9580(OpenPGP) | 取代 RFC 4880,弃用封装校验和 |
PEM:Base64 的起点
我们今天使用的字母表最早出现在 1987 年 2 月发布的 RFC 989《Privacy Enhancement for Internet Electronic Mail》中。加密邮件要经过只能处理可打印文本的网关,所以 PEM 需要一种办法,用在任何邮件系统中都不会被改动的字符来书写二进制密文。它每次取 6 个比特,映射到 A-Z、a-z、0-9、+ 和 /,用 = 做填充。RFC 989 中的表格与本站的字母表完全相同。它还保留了 * 来标记未加密的部分,这个特性后来没有保留下来。
PEM 经历了 RFC 1040 和 RFC 1113,最终定型于 RFC 1421(1993 年 2 月)。这份文档固定了行长:除最后一行外,每行恰好包含 64 个可打印字符,最后一行不超过 64 个。给出的理由是方便打印,以及 SMTP 每行 1000 字符的限制。PEM 作为安全邮件系统从未流行起来,但它的文件格式活了下来。每个 -----BEGIN CERTIFICATE----- 块都是它的后代,RFC 7468(2015)如今把规则写得很明确:生成方必须恰好在 64 个字符处换行,解析方可以接受其他宽度。可以用证书解码器查看这类文件。
MIME:每个邮件附件背后的 Base64
MIME(1992 年的 RFC 1341、1993 年的 RFC 1521、1996 年的 RFC 2045)需要把图片和文档放进电子邮件,于是借用了 PEM 的编码。RFC 2045 至今仍是电子邮件的参考标准,它与 PEM 有两处不同:
- 编码后的每行不得超过 76 个字符,行与行之间用 CRLF 分隔。
- “base64 编码数据中,任何不属于 base64 字母表的字符都应被忽略。”解码器会跳过换行、空格以及其他不属于字母表的字符。
正是第二条规则,导致很多解码器会悄悄丢弃无效字符。可以用 MIME Base64 编码器生成 MIME 风格的输出,附件的结构见 电子邮件中的 Base64。
RFC 3548 与 RFC 4648:统一的参考
在 2003 年之前,每个用到 Base64 的协议都指向 MIME 或 PEM,不管愿不愿意,都继承了它们的行长规则和宽松的解码规则。RFC 3548(2003 年 7 月)把 base64、base64url、base32 和 base16 定义为独立的编码。RFC 4648(2006 年 10 月)以少量改动取代了它:新增了能保持编码数据排序顺序的 base32hex,以及一组测试向量:
BASE64("") = ""
BASE64("f") = "Zg=="
BASE64("fo") = "Zm8="
BASE64("foo") = "Zm9v"
BASE64("foobar") = "Zm9vYmFy"
除非引用它的规范另有说明,RFC 4648 的规则比 MIME 更严格:
- 换行:实现不得添加换行(第 3.1 节)。
- 填充:实现必须包含
=填充(第 3.2 节)。 - 字母表以外的字符:解码器必须拒绝它们(第 3.3 节)。
- 规范编码:编码器必须把未使用的比特置为零,解码器可以拒绝未置零的输入(第 3.5 节)。否则
SGk/Pg==和SGk/Ph==都会解码为Hi?>。
“除非引用本文档的规范明确另有说明”这句话起了很大作用。正因为有它,JWS 可以去掉填充,MIME 可以保留 76 字符的行长,而两者用的仍然都是“RFC 4648 Base64”。Base32 和 Base16 工具实现了同一份 RFC 中的另外两种编码。
其他标准中定义的变体
JWS 与 JWT:不带填充的 Base64URL
RFC 7515(2015 年 5 月)把 BASE64URL(x) 定义为第 5 节的字母表,并“省略所有末尾的 ‘=’ 字符”,不含换行和空白。JWT、JWE、JWK 以及 JOSE 家族的大多数成员都使用这种形式。详见 JWT 中的 Base64。
IMAP:用逗号的修改版 UTF-7
IMAP 邮箱名称是 7 位字符串,所以 RFC 3501(第 5.1.3 节)用“修改版 UTF-7”来编码非 ASCII 的文件夹名。连续的非 ASCII 字符会先转换为 UTF-16,再以不带填充的 Base64 编码,用 , 代替 /,并用 & 和 - 包起来。RFC 自带的示例 &U,BTFw- 就是中文名称“台北”,德语文件夹 Entwürfe 则变成 Entw&APw-rfe。普通的 UTF-7(RFC 2152)把同一个名称写作 Entw+APw-rfe。
OpenPGP:带 CRC-24 的 Radix-64
OpenPGP 的 ASCII 封装(armor)就是 MIME Base64 加上一行校验和。RFC 4880 定义了针对二进制数据的 CRC-24(生成多项式 0x864CFB,初始值 0xB704CE),结果用 Base64 编码并加上 = 前缀。GnuPG 对 12 个字节的 Hello world! 生成如下内容:
-----BEGIN PGP ARMORED FILE-----
Comment: Use "gpg --dearmor" for unpacking
SGVsbG8gd29ybGQh
=s4Gu
-----END PGP ARMORED FILE-----
RFC 4880 允许每行最多 76 个字符,GnuPG 写的是 64 个。RFC 9580(2024 年 7 月)保留了这种格式,但规定 CRC-24 这一行“不应生成”,并且实现不得因为它缺失或错误而拒绝一个块。
Base64 变体一览表
| 变体 | 索引 62 和 63 | 填充 | 行长 | 定义于 |
|---|---|---|---|---|
| 标准 Base64 | + / | 必须 | 无 | RFC 4648 第 4 节 |
| Base64URL | - _ | 可选,由引用规范决定 | 无 | RFC 4648 第 5 节 |
| JWS / JWT | - _ | 从不 | 无 | RFC 7515 |
| MIME | + / | 必须 | 最多 76,CRLF | RFC 2045 |
| PEM | + / | 必须 | 恰好 64 | RFC 1421、RFC 7468 |
| OpenPGP 封装 | + / | 必须 | 最多 76(GnuPG:64) | RFC 4880、RFC 9580 |
| UTF-7 | + / | 无 | 不适用 | RFC 2152 |
| IMAP 修改版 UTF-7 | + , | 无 | 不适用 | RFC 3501 |
| bcrypt | 字母表 ./A-Za-z0-9 | 无 | 不适用 | 事实标准(OpenBSD) |
| crypt(3) 哈希 | 字母表 ./0-9A-Za-z | 无 | 不适用 | 事实标准(Unix) |
最后两行的整个字母表都重新排列过,字母表页面对此有介绍。还有很多格式直接引用上表中的某一行,不做任何改动:HTTP Basic 认证(RFC 7617)、data URI(RFC 2397)、XML Schema 的 base64Binary 以及子资源完整性(SRI)哈希,用的都是带填充的标准 Base64。
常见问题
Base64 是哪个 RFC 定义的?
通用参考是 RFC 4648(2006 年 10 月),它定义了 base64、base64url、base32、base32hex 和 base16。MIME 版本见 RFC 2045,PEM 的原始版本见 RFC 1421。
为什么 MIME 每行 76 个字符,而 PEM 每行 64 个?
两个限制都是为了让每行远低于 SMTP 的 1000 字符上限。PEM 在 RFC 1421 中固定为 64;MIME 在自己的规范里选择了“不超过 76”。RFC 4648 则规定,除非引用它的标准另有要求,编码器根本不应添加换行。
Base64URL 是正式标准吗?
是的。它是 RFC 4648 的第 5 节“Base 64 Encoding with URL and Filename Safe Alphabet”,最早出现在 RFC 3548 中。JWS(RFC 7515)使用不带填充的 Base64URL。
PGP 块末尾那行 =XXXX 是什么?
它是解码后数据的 CRC-24 校验和,本身也用 Base64 写成。RFC 9580(2024)规定新的实现不应再生成这一行,也不得因为它而拒绝一个块。