Base16, Base32, 和 Base64 数据编码

本备忘录的状态

这个文档规定了互联网协会的一个互联网标准跟踪协议,并且要求讨论并提出修改意见。有关这个协议的标准化情况和状态,请参考最新版本的“互联网官方协议标准”(STD1)。本备忘录的分发是不受限制的。

版本公告

Copyright © The Internet Society (2006).

摘要

本文档描述了常用的 base64,base32,和 base16 编码方案。也讨论了编码数据中换行的使用,编码数据中填充的使用,编码数据中非字母字符的使用,不同的编码字符表的使用,和规范编码。

目录

1. 介绍
2. 本文档中使用的约定
3. 实现差异
3.1. 编码数据中的换行
3.2. 编码数据中的填充
3.3. 编码数据中非字母表字符的解释
3.4. 选择字母表
3.5. 规范编码
4. Base64 编码
5. 使用 URL 和文件名安全字母表的 Base64编码
6. Base32 编码
7. 使用拓展十六进制字母的 Base32 编码
8. Base16 编码
9. 插图和示例
10. 测试向量
11. Base64 的ISO C99实现
12. 安全考虑
13. RFC3548 后的变化
14. 致谢
15. 拷贝条件
16. 参考文献
16.1. 规范类参考文献
16.2. 资料类参考文献

1. 介绍

许多情况下,数据的 Base 编码被用于在一些环境中进行存储或传输数据,这些环境可能因为遗留的原因,被限制为 US-ASCII [1] 数据。Base 编码也可以用于没有遗留限制的新的应用程序中,因为它使使用文本编辑器来操作对象成为了可能。

在过去,不同的应用程序有不同的需求,因此有时在 base 编码的实现上会有略微的不同。今天,协议规范通常会使用 base 编码,特别是 “base64”,但是却没有一个精确的描述或参考。多用途互联网邮件拓展(MIME)[4] 通常被用作 base64 编码的参考,但是它并没有考虑到换行和非字母字符的影响。本文档的目的就是去建立常用的字母和编码考虑。这有望降低其它文档中的歧义,从而实现更好的互操作性。

2. 本文档中的约定

本文档中的关键字 “MUST”, “MUST OUT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, 和 “OPTIONAL” 按照 [2] 中的描述来理解。

3. 实现差异

在这里,我们讨论了过去 base 编码实现间的差异,并在适当的情况下,为未来强制要求规范建议的行为。

3.1 编码数据中的换行

MIMIE [4] 通常被用作 base64 编码的参考。然而,MIME 本身并没有定义 “base64”,而是定义了一种在 MIME 内部使用的“base64 内容传输编码”。同样的,MIME 强制将 base64 编码数据的行长度限制为 76 个字符。MIME 继承自隐私增强邮件(PEM) [3],并声明它们“几乎相同”;然而,PEM 使用的行长度是 64 个字符。MIME 和 PEM 中的限制都是由于 SMTP 中的限制造成的。

实现一定不能在 base 编码数据中添加换行符,除非引用本文档的规范明确指示了编码器在特定数量字符后添加换行符。

3.2 编码数据中的填充

在一些情况下,base 编码数据不需要也不用填充(”=”)。在一般情况下,当无法对传输数据的大小做出假设时,需要填充以产生的解码数据。

实现必须在编码数据的末尾包含合适的填充字符,除非引用本文档的规范明确规定了其它内容。

base64 和 base32 字母表使用填充,如下文第 4 节和第 6 节所述,但是 base16 字母表不使用填充;参见第 8 节。

3.3 编码数据中非字母表字符的解释

Base 编码使用特定的简化字母表对二进制数据进行编码。由于数据破坏或设计原因,非字母表字符可能存在于 base 编码中。非字母表字符可能被利用为“隐藏通道”,其中用于恶意目的的非协议数据可能被发送。还可能利用实现的错误来发送非字母表字符导致缓冲区溢出攻击。

在解释 base 编码数据时,如果数据包含 base 字母表以外的字符,则实现必须拒绝该编码数据,除非引用本文档的规范明确规定了其它内容。这些规范可能会像 MIME 那样声明,在解释数据时,base 字母表外的字符应该被忽略(“在你接受的内容上要自由”)。请注意,这意味着任何相邻的回车/换行 (CRLF) 字符构成“非字母表字符”,会被忽略。此外,如果填充字符 “=” 出现在编码数据的末尾之前,则此类规范可以忽略该填充字符,将其视为非字母表字符。如果在字符串末尾出现多于允许数量的填充字符(例如,以 ”===” 结尾的 base64 字符串),则也可以忽略多余的填充字符。

3.4 选择字母表

不同的应用程序对字母表中的字符有不同的要求。以下是确定应该使用哪个字母表的一些要求:

o 人为处理。字符“0”和“O”很容易混淆,“1”,“I”,和“l”也是。在下面的 base32 字母表中,0 和 1 不存在,解码器可以根据情况将 0 解释为 O,将 1 解释为 I 或 l。(但是,默认情况下不应如此,参见上一节。)

o 编码到要求其它需求的结构中。对于 base16 和 base32,这决定了使用大写字母或小写字母。对于 base64,文件名和 URL 中的非字母数字字符(尤其是“/”)可能会有问题。

o 用作标识符。有些字符,尤其是 base64 字母表中的“+”和“/”被传统的字符搜索/索引工具视作分词符。

没有一个通用的可被接受的字母表可以满足所有需求。举个高度定制化变体的例子,参见 IMAP [8]。在本文档中,我们记录并命名了一些当前使用的字母表。

3.5 规范编码

base64 和 base32 编码中的填充步骤如果执行不当的话可能会导致编码数据的非重大更改。例如,如果只有一个八位字节的输出来进行 bse64 编码,则使用第一个符号的所有六位,但是只使用下一个符号的前两位。这些填充字必须通过一致的解码器来设置为零,这在下面的填充说明中进行了描述。如果此属性不成立的话,那么就没有了 base64 数据的规范表示,多个 base 编码字符串可能会解码成相同的二进制数据。如果此属性(以及本文档中讨论的其它属性)成立的话,就保证了使用规范编码。

在某些环境中,更改是至关重要的,因此,如果填充位未设置为 0,解码器可能会选择拒绝编码。引用本文档的规范可能要求特定的行为。

4. Base64 编码

下面的关于 base 64 的描述源自 [3], [4], [5], [6]。这种编码也被称为 “base64”。

Base64 编码被设计用来以一种可以使用大小写字母但是不需要人们可读的形式来表示任意八位字节序列。

使用了一个包含 65 个字符的 US-ASCII 子集,使每个可打印字符可以用 6 位来表示。(额外的第 65 个字符,”=”,被用来表示特殊处理功能。)

编码过程会将输入字节的每 24 位组表示为带有 4 个编码后的字符的输出字符串。从左到右进行,3 个连续的 8 位输入组组成一个 24 位输入组。这 24 位会被当做 4 个串行的 6 位组来处理,每个 6 位组会被翻译成 base64 字符表中的一个单独的字符。

每个 6 位组会被当做一个含 64 个可打印字符的数组的索引。索引所指示的字符会被放在输出字符串中。

表 1: Base64 字符表
编码值 编码值 编码值 编码值
0 A 17 R 34 i 51 z
1 B 18 S 35 j 52 0
2 C 19 T 36 k 53 1
3 D 20 U 37 l 54 2
4 E 21 V 38 m 55 3
5 F 22 W 39 n 56 4
6 G 23 X 40 o 57 5
7 H 24 Y 41 p 58 6
8 I 25 Z 42 q 59 7
9 J 26 a 43 r 60 8
10 K 27 b 44 s 61 9
11 L 28 c 45 t 62 +
12 M 29 d 46 u 63 /
13 N 30 e 47 v  
14 O 31 f 48 w (填充符)=
15 P 32 g 49 x  
16 Q 33 h 50 y  

当要编码的数据末尾少于 24 位可用时,则执行特殊处理。一个完整的编码量子总是在一个量子的末尾完成。当一个输入组中少于 24 个输入位可用时,值是 0 的比特位会被添加(在右边)以形成一个完整的 6 位组。使用 ‘=’ 字符来执行数据末尾的填充。由于所有 base64 的输入都是整数个的 8 位字节,因此只会出现以下情况:

(1) 最后一个编码输入量子是 24 位的整倍数;此时,最后一个编码输出单元会是 4 个字符的整倍数,不带 “=” 填充。

(2) 最后一个编码输入量子正好 8 位;此时,最后一个编码输出单元会是两个字符带两个 “=” 填充字符。

(3) 最后一个编码输入量子正好 16 位;此时,最后一个编码输出单元会是三个字符带一个 “=” 填充字符。

5. 使用 URL 和文件名安全字母表的 Base64编码

使用 URL 和文件名安全字母表的 Base64 编码已经在 [12] 中被使用了。

一些建议在变化的字母表中使用 “~” 作为第 63 个字符。由于 “~” 字符在一些文件系统环境中有特殊的意义,因此建议使用本节中提到的编码来替代。剩余的未保留的 URI 字符是 “.”,但是一些文件系统环境不允许在文件名使用多个 “.”,因此使用 “.” 字符也不理想。

填充字符 “=” 通常在 URI 中使用百分比编码,但是如果数据长度隐式已知的话,可以通过跳过填充字符来避免这种情况;请参见第 3.2 节。

这个编码被称作 “base64url”。这个编码不应该视作与 “base64” 编码相同,也不应该被称为 “base64” 编码。除非特别指名,否则 “base64” 指上一节中的 base64。

除了在表 2 中显示的第 62 和第 63 个字符不同外,这个编码在技术上与上一节的完全相同。

表 2: URL 和文件名安全的 base 64 字母表
编码值 编码值 编码值 编码值
0 A 17 R 34 i 51 z
1 B 18 S 35 j 52 0
2 C 19 T 36 k 53 1
3 D 20 U 37 l 54 2
4 E 21 V 38 m 55 3
5 F 22 W 39 n 56 4
6 G 23 X 40 o 57 5
7 H 24 Y 41 p 58 6
8 I 25 Z 42 q 59 7
9 J 26 a 43 r 60 8
10 K 27 b 44 s 61 9
11 L 28 c 45 t 62 - (减号)
12 M 29 d 46 u 63 _ (下划线)
13 N 30 e 47 v  
14 O 31 f 48 w (填充符)=
15 P 32 g 49 x  
16 Q 33 h 50 y  

6. Base32 编码

下面关于 base32 的描述源自于 [11] (包括更正)。这个编码被称作 “base32”。

Base32 被设计用来以一种不区分大小写但不需要人类可读形式来表示任意八位字节序列。

使用了一个包含 33 个字符的 US-ASCII 子集,使每个可打印字符可以用 5 位来表示。(额外的第 33 个字符,”=”,被用来表示特殊处理功能。)

编码过程会将输入字节的每 40 位组表示为带有 8 个编码后的字符的输出字符串。从左到右进行,5 个连续的 8 位输入组组成一个 40 位输入组。这 40 位会被当做 8 个串行的 5 位组来处理,每个 5 位组会被翻译成 base32 字符表中的一个单独的字符。当一个比特流通过 base32 进行编码时,必须假定该比特流以最高位优先排序。也就是说,流的第一个比特位将是第一个 8 位字节的高位,第八个比特位会是第一个 8 位字节的低位,依此类推。

每个 5 位组会被当做一个含 32 个可打印字符的数组的索引。索引所指示的字符会被放在输出字符串中。这些字符,如下表 3 所示,选自 US-ASII 中的数字和大写字母。

表 3: Base32 字符表
编码值 编码值 编码值 编码值
0 A 9 J 18 S 27 3
1 B 10 K 19 T 28 4
2 C 11 L 20 U 29 5
3 D 12 M 21 V 30 6
4 E 13 N 22 W 31 7
5 F 14 O 23 X  
6 G 15 P 24 Y (填充)=
7 H 16 Q 25 Z  
8 I 17 R 26 2  

当要编码的数据末尾少于 40 位可用时,则执行特殊处理。一个完整的编码量子总是在一个量子的末尾完成。当一个输入组中少于 40 个输入位可用时,值是 0 的比特位会被添加(在右边)以形成一个完整的 5 1 位组。使用 ‘=’ 字符来执行数据末尾的填充。由于所有 base32 的输入都是整数个的 8 位字节,因此只会出现以下情况:

(1) 最后一个编码输入量子是 40 位的整倍数;此时,最后一个编码输出单元会是 8 个字符的整倍数,不带 “=” 填充。

(2) 最后一个编码输入量子正好 8 位;此时,最后一个编码输出单元会是两个字符带六个 “=” 填充字符。

(3) 最后一个编码输入量子正好 16 位;此时,最后一个编码输出单元会是四个字符带四个 “=” 填充字符。

(4) 最后一个编码输入量子正好 24 位;此时,最后一个编码输出单元会是五个字符带三个 “=” 填充字符。

(5) 最后一个编码输入量子正好 32 位;此时,最后一个编码输出单元会是七个字符带一个 “=” 填充字符。


1: 应该是 8,RFC Errata中有说明


7. 使用拓展十六进制字母的 Base32 编码

下面 base32 的描述源自 [7]。这个编码可以称作 “base32hex”。 这个编码不应该视作与 “base32” 编码相同,也不应该被称为 “base32” 编码。例如,NextSECure3 (NSEC3) [10] 使用这种编码。

这种字母表有一个属性是 base64 和 base32 字母表所没有的,就是在按位比较编码数据时,编码数据会保持其排序顺序。

除字母表外,此编码与前一编码相同。新字母表见表 4。

表 4: 拓展十六进制 base32 字母表
编码值 编码值 编码值 编码值
0 0 9 9 18 I 27 R
1 1 10 A 19 J 28 S
2 2 11 B 20 K 29 T
3 3 12 C 21 L 30 U
4 4 13 D 22 M 31 V
5 5 14 E 23 N  
6 6 15 F 24 O (填充)=
7 7 16 G 25 P  
8 8 17 H 26 Q  

8. Base 16 编码

以下描述为原始描述,但与之前的描述类似。基本上,Base 16编码是标准的不区分大小写的十六进制编码,可以称为“Base 16”或“hex” 。

使用US-ASCII的16个字符子集,使每个可打印字符能够表示4位。

编码过程将输入位的8位组(八位字节)表示为带有 2 个编码后的字符的输出字符串。从左到右,从输入数据中获取一个8位输入。然后,这8位被视为2个串联的4位组,每个组被翻译成 base16 字母表中的单个字符。

每个 4 位组会被当做一个含 16 个可打印字符的数组的索引。索引所指示的字符会被放在输出字符串中。

表 5: Base16 字母表
编码值 编码值 编码值 编码值
0 0 4 4 8 8 12 C
1 1 5 5 9 9 13 D
2 2 6 6 10 A 14 E
3 3 7 7 11 B 15 F

与 base32 和 base64 编码不同,由于一个完整的编码字永远是可用的,所以不需要特殊的填充字符。

9. 插图和示例

为了在二进制编码和基编码之间进行转换,输入存储在一个结构中,然后提取输出。下图显示了base 64的案例,借用自[5]。

            +--first octet--+-second octet--+--third octet--+
            |7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|
            +-----------+---+-------+-------+---+-----------+
            |5 4 3 2 1 0|5 4 3 2 1 0|5 4 3 2 1 0|5 4 3 2 1 0|
            +--1.index--+--2.index--+--3.index--+--4.index--+  

下图显示了base 32的情况,借用自[7]。base-32值中的每个连续字符表示基础八位字节序列的5个连续位。因此,每组8个字符代表5个八位字节(40位)的序列。

                        1          2          3
             01234567 89012345 67890123 45678901 23456789
            +--------+--------+--------+--------+--------+
            |< 1 >< 2| >< 3 ><|.4 >< 5.|>< 6 ><.|7 >< 8 >|
            +--------+--------+--------+--------+--------+
                                                    <===> 8th character
                                              <====> 7th character
                                         <===> 6th character
                                   <====> 5th character
                             <====> 4th character
                        <===> 3rd character
                  <====> 2nd character
             <===> 1st character  

下面的Base64数据示例来自[5],并进行了更正。

Input data: 0x14fb9c03d97e
Hex: 1 4 f b 9 c | 0 3 d 9 7 e
8-bit: 00010100 11111011 10011100 | 00000011 11011001 01111110
6-bit: 000101 001111 101110 011100 | 000000 111101 100101 111110
Decimal: 5 15 46 28 0 61 37 62
Output: F P u c A 9 l +
Input data: 0x14fb9c03d9
Hex: 1 4 f b 9 c | 0 3 d 9
8-bit: 00010100 11111011 10011100 | 00000011 11011001
pad with 00
6-bit: 000101 001111 101110 011100 | 000000 111101 100100
Decimal: 5 15 46 28 0 61 36
pad with =
Output: F P u c A 9 k =
Input data: 0x14fb9c03
Hex: 1 4 f b 9 c | 0 3
8-bit: 00010100 11111011 10011100 | 00000011
pad with 0000
6-bit: 000101 001111 101110 011100 | 000000 110000
Decimal: 5 15 46 28 0 48
pad with = =
Output: F P u c A w = =        

10. 测试向量

BASE64("") = ""  
BASE64("f") = "Zg=="  
BASE64("fo") = "Zm8="  
BASE64("foo") = "Zm9v"  
BASE64("foob") = "Zm9vYg=="  
BASE64("fooba") = "Zm9vYmE="  
BASE64("foobar") = "Zm9vYmFy"  

BASE32("") = ""  
BASE32("f") = "MY======"  
BASE32("fo") = "MZXQ===="  
BASE32("foo") = "MZXW6==="  
BASE32("foob") = "MZXW6YQ="  
BASE32("fooba") = "MZXW6YTB"  
BASE32("foobar") = "MZXW6YTBOI======"  
BASE32-HEX("") = ""  
BASE32-HEX("f") = "CO======"  
BASE32-HEX("fo") = "CPNG===="  
BASE32-HEX("foo") = "CPNMU==="  
BASE32-HEX("foob") = "CPNMUOG="  
BASE32-HEX("fooba") = "CPNMUOJ1"  
BASE32-HEX("foobar") = "CPNMUOJ1E8======"  

BASE16("") = ""  
BASE16("f") = "66"  
BASE16("fo") = "666F"  
BASE16("foo") = "666F6F"  
BASE16("foob") = "666F6F62"  
BASE16("fooba") = "666F6F6261"  
BASE16("foobar") = "666F6F626172"  

11. Base64的ISO C99实现

Base64编码和解码的ISO C99实现被认为遵循本RFC中的所有建议,可从以下网站获得 :

     http://josefsson.org/base-encoding/

该编码不规范。

由于程序原因,该代码无法包含在本RFC中(RFC 3978第5.4节)。

12. 安全考虑

在实现 base 编码和解码时,应注意不要给实现带来缓冲区溢出攻击或其他攻击的漏洞。解码器不应在无效输入时中断,例如,包括嵌入的NUL字符(ASCII 0)。

如果忽略非字母字符,而不是导致整个编码被拒绝(如建议的那样),则可以使用可用于“泄漏”信息的隐蔽通道。被忽略的字符还可用于其他恶意目的,如避免字符串相等比较或触发实现错误。在不遵循推荐做法的应用程序中,应理解忽略非字母字符的含义。类似地,当以不区分大小写的方式处理 base64 和 base32 字母时,大小写的更改可用于泄漏信息或使字符串相等性比较失败。

当使用填充时,有一些非重要位需要考虑安全问题,因为它们可能被滥用以泄漏信息或用于绕过字符串相等性比较或触发实现问题。

Base 编码直观地隐藏了其他容易识别的信息,如密码,但不提供任何计算机密性。众所周知,当用户报告网络协议交换的详细信息(可能是为了说明某些其他问题)并意外泄露密码时,这会导致安全事件,因为她不知道 base 编码不保护密码。

Base 编码不会给明文增加熵,但它确实增加了可用明文的数量,并以特征概率分布的形式为密码分析提供签名。

13. RFC 3548后的变化

添加了“base32扩展十六进制字母表”,用于需要保留编码数据的排序顺序。

引用了这里使用的特殊Base64编码的IMAP。

修复了从RFC2440复制的示例。

增加了关于为密码分析提供签名的安全考虑。

添加了测试向量。

修正了打字错误。

14.致谢

一些人提出了意见和/或建议,包括约John E. Hadstate, Tony Hansen, Gordon Mohr, John Myers, Chris Newman, and Andrew Sieber。本文档中使用的文本基于早期RFC,描述了各种 base 编码的具体用途。作者感谢RSA实验室支持本文档的工作。

本修订版本部分基于 Roy Arends, Eric Blake, Brian E Carpenter, Elwyn Davies, Bill Fenner, Sam Hartman, Ted Hardie, Per Hygum, Jelte Jansen, Clement Kent, Tero Kivinen, Paul Kwiatkowski, and Ben Laurie 提出的意见和/或建议。

15. 拷贝条件

Copyright (c) 2000-2006 Simon Josefsson

关于Simon Josefsson(“作者”)撰写的摘要以及本文件第1、3、8、10、12、13和14节(本节剩余部分),作者不作任何保证,也不对因使用本摘要而造成的任何损害负责。作者向任何人授予不可撤销的许可,允许其以任何方式使用、修改和分发本文件,但不得削弱任何其他人使用、修改和分发本文件的权利,前提是重新分发的衍生作品不包含误导性作者或版本信息,也不得虚假地声称为IETF RFC文件。衍生作品无需根据类似条款获得许可。

16. 参考文献

16.1. 规范类参考文献

[1] Cerf,V.,“网络交换的ASCII格式”,RFC 20,1969年10月。

[2] Bradner,S.,“RFC中用于表示需求水平的关键词”,BCP 14,RFC 2119,1997年3月。

16.2. 资料类参考文献

[3] Linn,J.,“因特网电子邮件的隐私增强:第一部分:信息加密和认证程序”,RFC 14211993年2月。

[4] Freed,N.和N.Borenstein,“多用途互联网邮件扩展(MIME)第一部分:互联网邮件正文格式”,RFC 20451996年11月。

[5] Callas,J.,Donnerhacke,L.,Finney,H.,和R.Thayer,“OpenPGP消息格式”,RFC2440,1998年11月。

[6] Arends,R.,Austein,R.,Larson,M.,Massey,D.,和S.Rose,“DNS安全介绍和要求”,RFC 4033,2005年3月。

[7] Klyne,G.和L.Masinter,“识别复合媒体特征”,RFC 2938,2000年9月。

[8] Crispin,M.,“互联网消息访问协议-版本4rev1”,RFC 35012003年3月。

[9] Berners Lee,T.,Fielding,R.,和L.Masinter,“统一资源标识符(URI):通用语法”,STD 66,RFC 3986,2005年1月。

[10] Laurie,B.,Sisson,G.,Arends,R.,和D.Blacka,“DNSSEC哈希认证拒绝存在”,正在进行的工作,2006年6月。

[11] Myers,J.,“SASL GSSAPI机制”,正在进行的工作,2000年5月。

[12] Wilcox-O’Hearn,B.,“发布到P2P黑客邮件列表”,http://zgp.org/pipermail/p2p-hackers/2001-September/000315.html,2001年9月。

作者地址
Simon Josefsson
SJD
EMail: simon@josefsson.org


参考
翻译工具:

RFC资料:
https://www.rfc-editor.org/rfc/
RFC2CN
RFC Errata