开发者工具 · Base64 编码器和解码器
RFC 4648 解释:定义 Base64、base32 和 base16 的标准
· 背景
base64 编码
RFC 4648 是每个 Base64 实现背后的简短易读的文档。这篇文章将详细介绍它所指定的内容、它故意保留的内容以及为什么实现仍然不同。
两个库,同一个字符串有两个答案——一个真正的互操作性难题,只有标准才能解决
两个 JavaScript 库可以为同一字符串返回不同的 Base64,每个库都声明正确性。 RFC 4648 是一份可读的十二页文档,应该可以解决此类分歧,但实现仍然有所不同,因为 RFC 故意将某些决定留给应用程序。本文将介绍 RFC 4648 指定的内容、它有意委托给调用者的内容,以及为什么阅读该标准一次就能解决大多数真正的互操作性难题。 Base64 编码器和解码器工具包括 RFC 4648 测试向量,因此您可以根据权威示例验证实现。
RFC 4648 替换并合并了多个早期文档:来自 MIME (RFC 2045) 的 Base64、来自隐私增强邮件 (RFC 1421) 的 Base64、来自 S/MIME (RFC 2630) 的 Base32 以及来自各种来源的 Base16。合并是必要的,因为 MIME 和 PEM 都有自己的字母表和规则,并且 MIME 换行与 PEM 的 64 列块冲突。 RFC 4648 在一个地方定义了五个编码系列:base64、base64url、base32、base32hex 和 base16,每个都有自己的字母表、填充规则和示例测试向量。 Base64 字母表为 A-Z、a-z、0-9、加号和斜杠(按此顺序)。
实现和测试向量建立的内容 — 标准和 URL 安全字母表、填充和空白处理
每个字符代表 6 位;三个输入字节(24 位)映射到四个输出字符。字母表不是任意的:它避免了 EBCDIC 和 ASCII 之间不同的字符,避免了控制字符、引号和需要在 C 字符串文字中转义的反斜杠。 base64url 变体将加号替换为破折号,将斜杠替换为下划线,以避免 URL 和文件名中的保留字符。两种变体同样有效; RFC 4648 部分 2 指定 base64,部分 5 指定 base64url,应用程序必须声明它使用哪一种。
使用等于字符填充使输出达到四个字符的倍数。如果输入是 1 字节(8 位),则输出是两个字符加两个等号。如果输入为 2 字节(16 位),则输出为三个字符加一个等号。如果输入是 3 字节的倍数,则不需要填充。一些应用程序省略填充或允许在解码时丢失填充; RFC 4648 部分 3.2 将规范编码定义为始终填充,但 3.3 部分指出解码器可能接受缺少填充以实现兼容性。
该工具实现的字母表 - 标准 Base64 和 Base64url;其他基地仍在其范围之外
填充的区别是实现不一致的原因:严格的解码器拒绝缺少等于,而宽松的解码器则接受它。 RFC 4648 明确指出:在 URL 中使用时,填充字符 equals 通常是百分比编码的,因此如果直接在 URL 参数中使用 base64url 输出,则不需要填充,应该省略填充。这句话是 URL 安全模式和填充省略经常配对的原因之一,尽管它们是独立的选择。 5 (base64url) 部分不禁止填充;它只是指出了常见的做法。
选择 base64url 的调用者必须决定接收系统是否需要填充。不同的解码器对输入中的非字母字符进行不同的处理。 RFC 4648 部分 3.1 规定:如果编码包含基本字母表之外的字符,则实现必须拒绝该编码。但是,3.3 节指出,MIME Base64 (RFC 2045) 允许 76 字符换行换行,并且 MIME 解码器必须跳过空格。 RFC 区分严格解码(拒绝所有非字母表)和 MIME 兼容解码(跳过空格,拒绝其他字符)。
填充、非字母字符和规范编码 - 解释大多数解码器分歧的部分
应用程序必须选择要遵循的规则;该标准定义了两者。 Base32 使用 A-Z 和 2-7 (总共 32 个字符),将五个输入字节(40 位)编码为八个输出字符。 Base32hex 用 0-9 和 a-v 替换字母字符,这在首选小写字母的上下文中很有用。
Base16 是十六进制:0-9 和 a-f。 Base32 和 base32hex 在 6 和 7 部分有自己的填充规则,并且 RFC 为每个字母表提供单独的测试向量。大多数开发者只需要base64和base64url;为了完整性以及 TOTP 机密 (RFC 4226) 和 DNS 编码等应用程序,RFC 中包含了 base32、base32hex 和 base16。
此实现中可见的应用程序选择 — 换行、严格的文本解码和错误处理
RFC 4648 中的测试向量是检查实现的基本事实。对字符串 f、fo、foo、foob、fooba 和 foobar 进行编码会产生特定的 base64 输出:Zg==、Zm8=、Zm9v、Zm9vYg==、Zm9vYmE= 和 Zm9vYmFy。为这些字符串产生不同输出的实现是不正确的。 RFC 提供了 base32、base32hex 和 base16 的等效测试向量。 Base64 编码器和解码器工具包含这些向量,因此您可以根据标准验证其输出。换行是 MIME 问题,而不是 base64 问题。
RFC 2045 指定 76 字符行; RFC 4648 部分 3.1 在 MIME 上下文中注意到了这一点,但并未将其作为 base64 本身的要求。某些应用程序以 64 字符换行(原始 PEM 标准);其他的则根本不包裹。严格的 RFC 4648 base64 解码器仅对字母表和填充进行操作。 MIME 兼容的解码器必须跳过换行符(CR、LF、CRLF)。在 MIME 之外使用 base64 的应用程序不应添加换行符,除非接收系统需要它们; RFC 没有将换行定义为 base64 的一部分。
工作示例:RFC 自己的测试向量 — 编码“foobar”前缀并在浏览器中检查它们
空白处理是实现差异的另一个点。 RFC 4648 表示严格的解码器必须拒绝非字母字符。 MIME 包装的 base64 (RFC 2045 base64) 允许使用空格进行格式化。这两个标准在输出字节应该是什么方面达成一致,但在输入有效方面有所不同。大多数 JavaScript 实现都会选择 MIME 兼容性并跳过空格;严格的规则很少在浏览器中使用。 Base64 编码器和解码器接受包含空格 (MIME) 和严格输入,从而明确区分。规范解码与宽容解码是最后的主要差异。
规范解码遵循 RFC 4648 部分 3.2:拒绝格式错误的填充、拒绝缺失的填充、拒绝非字母字符。 Web 标准中使用的宽容解码(HTML 规范将其称为 forgiving-base64)添加了规则:忽略空格、接受缺失的填充、允许破折号和下划线作为加斜杠等效项,即使在标准 base64 模式下也是如此。 JavaScript 的 atob() 是宽容的;严格的 RFC 4648 解码器更严格。两者都没有错;它们服务于不同的环境。从用户或网络读取数据的应用程序应该知道对方期望哪种规则。
这不包括的内容 — MIME 和 PEM 文档本身以及特定于语言的 API
RFC 为应用程序留下了九个选择:五个字母中的哪一个、是否需要或允许填充、是否需要或允许空格、是否将破折号下划线视为加斜杠等效项、如何报告错误、如何处理输入结束、是否接受缺少的填充、要分配多少输出字节以及如何发出大小限制信号。这些选择解释了为什么两个 RFC 4648 实现在同一输入上可能存在分歧。阅读一次 RFC;根据测试向量检查您的实现;说明您的应用程序使用哪些选项;测试与实际对等点的互操作性,而不是假设。
了解 RFC 4648 解决了大多数 Base64 争议,因为分歧通常不是关于 RFC 本身,而是关于双方选择的选项。 RFC 足够简短,一个小时内即可从头到尾读完。该标准定义了字母表,提供了测试向量,并警告实现必须决定的地方。 Base64 编码器和解码器工具可让您试验测试向量并查看实际的标准字母表。大多数日常使用 Base64 不需要深厚的 RFC 知识;但是,当调试编码不匹配或与不熟悉的 API 集成时,阅读一次标准就可以消除猜测。
要点:阅读一次标准 — Base64 编码器和解码器如何为您提供快速检查标准字母表测试向量的方法
RFC 4648 将数十年的临时基本编码实践整合为一个可读的规范。它没有定义何时使用base64(MIME、PEM、JWT、数据URI等,每个都有自己的规范);它定义了 base64 是什么。通过定义五个编码系列并注明哪些选项是规范的,RFC 可以检查实现是否正确。权威测试向量是起点:如果您的实现对 foobar 进行编码并生成 Zm9vYmFy 以外的任何内容,则 RFC 表示该实现是错误的。
使用该权限作为验证检查点:对每个 RFC 测试向量进行编码,比较确切的字符,然后对结果进行解码以确认原始字节返回不变。这种基于浏览器的检查将字母或填充错误与集成中其他地方的问题分开,同时将标准本身作为参考,而不是依赖于库标签。