如果输入长度不能被3整除,为什么 base64编码需要填充?

Base64编码中填充的目的是什么? 以下是维基百科的摘录:

“分配一个附加的垫字符,可用于强制将编码输出成为4个字符的整数倍(或相当于当未编码的二进制文本不是3个字节的倍数时) ; 这些垫字符然后必须在解码时丢弃,但仍然允许计算未编码文本的有效长度,当其输入二进制文本长度不是3个字节的倍数时(最后一个非垫字符通常是编码的,因此它代表的最后6位块将在其最低有效位上为零填充,最多两个垫字符可能出现在编码流的末尾)。”

我编写了一个程序,它可以对任何字符串进行 base64编码,也可以对任何 base64编码的字符串进行解码。填充能解决什么问题?

116029 次浏览

在现代社会,它没有多少好处。因此,让我们把这看作一个问题,原创的的历史目的可能是什么。

Base64编码在1993年的 RFC 1421中首次出现。这个 RFC 实际上主要用于加密电子邮件,并且在 一小部分4.3.2.4中描述了 base64。

此 RFC 不解释填充的用途。我们最接近于提到最初目的的是这句话:

完整的编码量程总是在消息结束时完成。

它不建议连接(这里的顶部答案) ,也不建议将实现的简单性作为填充的明确目的。然而,考虑到整个描述,假设这可能是为了帮助解码器读取32位单元(“量子”)的输入并非不合理。这在今天是没有好处的,然而在1993年不安全的 C 代码实际上很可能利用了这一特性。

在一个相关的注意事项,这里有一个任意的基地转换器我为您创建。享受! Https://convert.zamicol.com/

什么是填充字符?

填充字符有助于满足长度要求,没有其他含义。

填充的十进制示例: 如果任意要求所有字符串的长度都是8个字符,那么数字640可以使用前面的0作为填充字符来满足这一要求,因为它们没有任何意义,“00000640”。

二进制编码

字节范式: 对于编码来说,字节是行业标准的度量单位,任何方案都必须与字节相关。

Base256 完全符合字节范式,一个字节等于 base256中的一个字符。

Base16 ,十六进制或十六进制,每个字符使用4位。一个字节可以表示两个 base16字符。

与 base256和 base16不同,Base64 不能均匀地适应字节范式(base32也不能)。所有 base64字符都可以用6位表示,比一个完整字节少2位。

我们可以将 base64编码与字节范式表示为一个分数: 每个字符6比特/字节8比特/字节。此分数减少为4个字符的3个字节。

这个比率(每4个 base64字符占3个字节)是我们在编码 base64时要遵循的规则。Base64编码只能承诺使用3字节包进行测量,与 base16和 base256不同,后者的每个字节都可以独立存在。

所以 为什么是鼓励填充的,即使编码在没有填充字符的情况下也可以正常工作?

如果流的长度是未知的,或者确切知道数据流何时结束可能有帮助,请使用填充。填充字符明确表示这些额外的点应该是空的,并排除了任何歧义。即使使用填充的长度是未知的,您也将知道数据流的结束位置。

作为一个反例,像 JOSE这样的一些标准不允许填充字符。在这种情况下,如果缺少某些内容,那么加密签名将不起作用,或者其他非 base64字符将丢失(如“ .”).虽然没有对长度做出假设,但是不需要填充,因为如果有什么地方出了问题,它就不会起作用。

这正是 基地64 RFC 所说的,

在某些情况下,在基编码数据中使用填充(“ =”) 在一般情况下,假设 无法制作传输数据的大小,需要填充 产生正确的解码数据。

[...]

填充步骤在基数64[ ... ]如果不正确 实施,导致编码数据的非显着改变。 例如,如果输入对于基64编码只有一个八位元组, 然后使用第一个符号的所有六个位,但只使用第一个 使用下一个符号的两个位。这些垫位必须设置为 零通过符合编码器,这在描述中描述 如果此属性不成立,则没有 基编码数据的规范表示和多个基- 可以将已编码的字符串解码为相同的二进制数据 属性(以及本文档中讨论的其他属性)持有的规范 编码是有保证的。

填充允许我们在不丢失位的前提下解码 base64编码。如果没有填充,则不再显式确认在三个字节包中进行测量。如果没有填充,您可能无法保证在没有附加信息(通常来自堆栈中的其他位置,如 TCP、校验和或其他方法)的情况下精确复制原始编码。

与 base64这样的桶转换方案相比,基数换算法基数换算法没有任意的桶大小,而且对于从左到右的读取器来说,它是左填充的。“基数迭代除法”转换方法通常用于基数转换。

例子

下面是 RFC 4648(https://www.rfc-editor.org/rfc/rfc4648#section-8)的示例

“ BASE64”函数中的每个字符使用一个字节(base256) ,然后我们将其转换为 BASE64。

BASE64("")       = ""           (No bytes used. 0 % 3 = 0)
BASE64("f")      = "Zg=="       (One byte used. 1 % 3 = 1)
BASE64("fo")     = "Zm8="       (Two bytes.     2 % 3 = 2)
BASE64("foo")    = "Zm9v"       (Three bytes.   3 % 3 = 0)
BASE64("foob")   = "Zm9vYg=="   (Four bytes.    4 % 3 = 1)
BASE64("fooba")  = "Zm9vYmE="   (Five bytes.    5 % 3 = 2)
BASE64("foobar") = "Zm9vYmFy"   (Six bytes.     6 % 3 = 0)

这里有一个编码器,你可以使用: http://www.motobit.com/util/base64-decoder-encoder.asp

你认为不需要填充物的结论是正确的。从编码序列的长度确定输入的长度总是可能的。

但是,在 base64编码的字符串串联的方式导致单个序列的长度丢失的情况下,填充非常有用,例如,在一个非常简单的网络协议中可能会发生这种情况。

如果将 没有填充物字符串连接起来,就不可能恢复原始数据,因为关于每个单独序列末尾的奇数字节数的信息会丢失。但是,如果使用填充序列,就不会出现歧义,并且整个序列可以被正确解码。

编辑: 插图

假设我们有一个 base64编码单词、连接它们并通过网络发送它们的程序。它对“ I”、“ AM”和“ TJM”进行编码,将结果聚合在一起,不用填充,然后传输它们。

  • I编码为 SQ(带填充的 SQ==)
  • AM编码为 QU0(带填充的 QU0=)
  • TJM编码为 VEpN(带填充的 VEpN)

所以传输的数据是 SQQU0VEpN。接收器 base64-将其解码为 I\x04\x14\xd1Q)而不是预期的 IAMTJM。结果是无意义的,因为发送方在编码序列中有 关于每个单词结尾的被破坏的信息。如果发送方发送的是 SQ==QU0=VEpN,那么接收方可以将其解码为三个独立的碱基64序列,这些碱基64序列将连接成 IAMTJM

为什么要用填充物呢?

为什么不把协议设计成每个单词的前缀都是整数长度呢?然后接收器可以正确地解码流,不需要填充。

这是一个很好的想法,只要我们 知道的长度,我们正在编码之前,我们开始编码的数据。但是,如果我们不是用文字,而是用实时摄像机来编码视频呢?我们可能事先不知道每个块的长度。

如果协议使用填充,则根本不需要传输长度。数据可以在从摄像头传入时进行编码,每个数据块都以填充结束,接收者将能够正确地解码流。

显然,这是一个非常人为的例子,但也许它说明了为什么填充可能会在某些情况下有所帮助。

填充以定义的方式将输出长度填充到四个字节的倍数。

使用填充,base64字符串的长度总是4的倍数(如果没有,字符串肯定已经损坏) ,因此代码可以轻松地在一个循环中处理该字符串,每次处理4个字符(总是将4个输入字符转换为3个或更少的输出字节)。因此,填充使健全性检查变得容易(使用填充时不可能出现 length % 4 != 0 = = > 错误) ,并且使处理更简单、更有效。

我知道人们会怎么想: 即使没有填充,我也可以在一个循环中处理所有4字节的块,然后只需要为最后1到3个字节添加特殊处理(如果存在的话)。这只是几行额外的代码和速度差异将是太小,甚至无法测量。也许是这样,但是您考虑的是 C (或更高级的语言)和一个拥有大量 RAM 的强大 CPU。如果您需要在硬件上解码 base64,使用一个简单的 DSP,它具有非常有限的处理能力,没有 RAM 存储,并且您必须在非常有限的微汇编中编写代码,该怎么办?如果你根本不能使用代码,所有的事情都只能用堆叠在一起的晶体管来完成(一个硬件实现) ,那该怎么办?有填充物,比没有简单多了。