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


100

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

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

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

Answers:


208

您认为不需要填充的结论是正确的。始终可以从编码序列的长度中明确确定输入的长度。

但是,填充在base64编码的字符串以这样的方式连接在一起的情况下很有用,例如,在非常简单的网络协议中可能会丢失各个序列的长度。

如果将未填充的字符串连接在一起,则将无法恢复原始数据,因为有关每个单独序列末尾的奇数字节数的信息会丢失。但是,如果使用填充序列,则不会有歧义,并且整个序列可以正确解码。

编辑:插图

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

  • I编码为SQSQ==带填充)
  • AM编码为QU0QU0=带填充)
  • TJM编码为VEpNVEpN带填充)

因此,传输的数据为SQQU0VEpN。接收器base64会将其解码为,I\x04\x14\xd1Q)而不是原来的IAMTJM。结果是无稽之谈,因为发送方已销毁了有关每个单词在编码序列中的结尾位置的信息。如果发送方SQ==QU0=VEpN改为发送方,则接收方可能已将其解码为三个单独的base64序列,这些序列将串联在一起给出IAMTJM

为什么要花一点时间?

为什么不只是设计协议以给每个单词加上整数长度呢?然后,接收器可以正确解码流,并且无需填充。

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

如果协议使用填充,则完全不需要传输长度。数据可以按照从相机传入的方式进行编码,每个块均以填充结尾,接收器将能够正确解码流。

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


22
+1除了“因为我们出于某些莫名其妙的原因喜欢冗长和冗长”之外,唯一实际提供合理答案的答案。
无效

1
这对于以不同方式编码但在解码后有望被不可分割地连接的块来说是可行的。如果发送U0FNSQ == QU0 =,则可以重建句子,但是会丢失组成句子的单词。我想总比没有好。值得注意的是,GNU base64程序会自动处理级联编码。
马塞洛·坎托斯

2
如果单词的长度是3的倍数怎么办?这种愚蠢的串联方式会破坏信息(单词的结尾),而不是删除填充。
GreenScape

2
Base64串联使编码器可以并行处理大块,而无需将块大小对齐为三的倍数。类似地,作为一种实现细节,可能存在一个编码器,该编码器需要刷新内部数据缓冲区,该大小不是三的倍数。
安德烈D

1
这个答案可能使您认为您可以将“ SQ == QU0 = VEpN”解码给解码器,从而对其进行解码。实际上,您似乎不能,例如javascript和php中的实现不支持此功能。以串联的字符串开头,您必须一次解码4个字节,或者在填充字符后将字符串拆分。看起来这些实现只是忽略了填充字符,即使它们位于字符串中间。
罗马

38

与此相关的是,这是我为您创建的用于任意基本转换的基本转换器。请享用! https://convert.zamicol.com/

什么是填充字符?

填充字符有助于满足长度要求,并且没有任何意义。

填充的十进制示例: 给定任意要求,所有字符串的长度均为8个字符,数字640可以使用前面的0作为填充字符来满足此要求,因为它们不带任何含义“ 00000640”。

二进制编码

字节范例:字节是事实上的标准度量单位,任何编码方案都必须与字节相关。

Base256完全适合此范例。1个字节等于base256中的一个字符。

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

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

我们可以将base64编码相对于字节范式表示为分数:每个字符6位超过每个字节8位。减少的这一部分是3个字节超过4个字符。

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

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

如果流的长度未知,或者准确了解数据流何时结束会有所帮助,请使用填充。填充字符明确表示这些多余的点应为空,并排除任何歧义。即使填充的长度未知,您也将知道数据流的结束位置。

作为反例,某些标准(例如JOSE)不允许填充字符。在这种情况下,如果缺少某些内容,则加密签名将不起作用,否则将丢失其他非base64字符(例如“。”)。尽管没有关于长度的假设,但是也不需要填充,因为如果出现错误,它将根本无法工作。

这正是base64 RFC所说的,

在某些情况下,不需要或不使用在基本编码数据中使用填充(“ =”)。在一般情况下,当无法做出有关传输数据大小的假设时,需要填充以产生正确的解码数据。

[...]

daccess-ods.un.org daccess-ods.un.org如果未正确实现base 64中的填充步骤,则会导致编码数据的非重大更改。例如,如果对于基数64编码,输入仅为一个八位位组,则使用第一个符号的所有六位,但是仅使用下一个符号的前两位。这些填充位必须由合格的编码器设置为零,这将在下面的填充说明中进行描述。如果不保留此属性,则不会对基本编码的数据进行规范表示,并且可以将多个基本编码的字符串解码为相同的二进制数据。如果此属性(以及本文档中讨论的其他属性)成立,则可以保证规范编码。

填充允许我们解码base64编码,并且不会丢失任何比特。如果不进行填充,则不再明确承认要在三个字节的束中进行测量。如果没有填充,通常可能无法从堆栈中的其他位置(例如TCP,校验和或其他方法)获得没有附加信息的原始编码的精确再现。

例子

这是RFC 4648的示例格式(http://tools.ietf.org/html/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


16
-1这是一篇很好的关于数字系统工作原理的详尽文章,但是没有解释为什么当编码无法完美工作时为什么要使用填充。
Matti Virkkunen 2014年

2
你甚至读过这个问题吗?您不需要填充即可正确解码。
纳文2015年

3
我认为此答案实际上确实解释了这里所述的原因:“我们将无法再保证没有附加信息的原始编码的精确再现”。确实很简单,填充让我们知道我们已经收到了完整的编码。每次有3个字节时,您都可以放心地认为可以继续进行解码了,您不必担心,哼哼……也许还会再出现一个字节,可能会更改编码。
Didier A.

@DidierA。您如何知道base64子字符串中再没有3个字节?要解码char*,您需要字符串的大小或空终止符。填充是多余的。因此,OP的问题。
纳文

4
@Navin如果您正在对base64字节进行流解码,则不知道其长度(填充3个字节),便知道每次获得3个字节时,您都可以处理4个字符,直到到达流的末尾。没有它,您可能需要回溯,因为下一个字节可能导致前一个字符发生变化,因此,只有在到达流的末尾时,才能确保正确解码了它。因此,它不是很有用,但是在某些情况下您可能需要启用它。
Didier A.

1

在现代没有太大的好处。因此,让我们将其视为最初的历史目的可能是什么的问题。

Base64编码在1993 年发布的RFC 1421中首次出现。该RFC实际上专注于加密电子邮件,而在第4.3.2.4小节中介绍了base64 。

该RFC没有解释填充的目的。我们最要提到的原始目的是这句话:

完整的编码量总是在消息末尾完成。

它不建议串联(此处为最佳答案),也不建议将其易于实现作为填充的显式目的。但是,考虑到整个描述,可以合理地假设这可能旨在帮助解码器以32位为单位读取输入(“量子”)。今天这没有什么好处,但是在1993年,不安全的C代码实际上很有可能会利用此属性。


1
在没有填充的情况下,当第一个字符串的长度不是三的倍数时尝试连接两个字符串通常会产生看似有效的字符串,但是第二个字符串的内容将无法正确解码。添加填充确保不会发生这种情况。
超级猫

1
@supercat如果这是目标,那么用单个“ =”结束每个base64字符串会更容易吗?平均长度将更短,并且仍将防止错误的串联。
罗曼·斯塔科夫

2
的平均长度与b'Zm9vYmFyZm9vYg==' b'Zm9vYmFyZm9vYmE=' b'Zm9vYmFyZm9vYmFy' b'Zm9vYmFyZm9vYmFyZg==' b'Zm9vYmFyZm9vYmFyZm8=' b'Zm9vYmFyZm9vYmFyZm9v'b'Zm9vYmFyZm9vYg=' b'Zm9vYmFyZm9vYmE=' b'Zm9vYmFyZm9vYmFy=' b'Zm9vYmFyZm9vYmFyZg=' b'Zm9vYmFyZm9vYmFyZm8=' b'Zm9vYmFyZm9vYmFyZm9v='
Scott Scott
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.