关于算法:为什么我们使用Base64?

Why do we use Base64?

维基百科说

Base64 encoding schemes are commonly used when there is a need to encode binary data that needs be stored and transferred over media that are designed to deal with textual data. This is to ensure that the data remains intact without modification during transport.

但是数据不是总是以二进制的形式存储/传输的吗?因为我们的机器有存储二进制的内存,而这取决于您如何解释它?因此,无论您将位模式010011010110000101101110编码为ASCII中的Man还是base64中的TWFu,最终都将存储相同的位模式。

如果最终的编码是以0和1为单位的,并且每台机器和媒体都能处理它们,那么如果数据以ASCII或base64表示,这又有什么关系呢?

它是什么意思"媒体设计用来处理文本数据"?他们可以处理二进制=>他们可以处理任何事情。

谢谢大家,我想我现在明白了。

当我们发送数据时,我们不能确定数据的解释格式是否与我们预期的格式相同。所以,我们发送以双方都理解的某种格式(比如base64)编码的数据。这样,即使发送方和接收方对相同的事物的解释不同,但由于它们在编码格式上达成一致,数据也不会被错误地解释。

来自Mark Byers示例

如果我想发送

1
2
Hello
world!

一种方法是用ASCII格式发送

1
72 101 108 108 111 10 119 111 114 108 100 33

但字节10可能无法正确地解释为另一端的换行符。所以,我们使用ASCII的子集对它进行这样的编码

1
83 71 86 115 98 71 56 115 67 110 100 118 99 109 120 107 73 61 61

以相同信息量传输更多数据为代价,确保接收器能够以预期的方式解码数据,即使接收器恰好对字符集的其余部分有不同的解释。


您的第一个错误是认为ASCII编码和base64编码是可互换的。它们不是。它们的用途不同。

  • 当您用ASCII编码文本时,您从一个文本字符串开始,并将其转换为一个字节序列。
  • 当您以base64编码数据时,您从一个字节序列开始,并将其转换为文本字符串。

为了理解为什么首先需要base64,我们需要一些计算历史。

计算机以二进制0和1进行通信,但人们通常希望与更丰富的表单数据(如文本或图像)进行通信。为了在计算机之间传输这些数据,首先必须将其编码为0和1,然后发送,然后再次解码。以文本为例-有许多不同的方法来执行这种编码。如果我们都能在单一编码上达成一致,那就简单多了,但遗憾的是事实并非如此。

最初创建了许多不同的编码(例如波特代码),每个字符使用不同的位数,直到最终ASCII成为一个标准,每个字符7位。然而,大多数计算机存储二进制数据的字节由8位组成,因此ASCII不适合传输这种类型的数据。有些系统甚至会擦除最重要的位。此外,跨系统的行尾编码的差异意味着ASCII字符10和13有时也会被修改。

为了解决这些问题,引入了base64编码。这允许您将aribtrary字节编码为已知可以安全发送而不会损坏的字节(ASCII字母数字字符和一些符号)。缺点是,使用base64对消息进行编码会增加其长度——每3个字节的数据编码为4个ASCII字符。

为了可靠地发送文本,您可以首先使用您选择的文本编码(例如utf-8)将文本编码为字节,然后base64将生成的二进制数据编码为安全发送编码为ASCII的文本字符串。接收器必须反转此过程才能恢复原始消息。当然,这要求接收者知道使用了哪些编码,并且这些信息通常需要单独发送。

历史上,它被用来在电子邮件中编码二进制数据,电子邮件服务器可能会修改行尾。更现代的例子是使用base64编码将图像数据直接嵌入到HTML源代码中。这里有必要对数据进行编码,以避免像"<"和">"这样的字符被解释为标记。

下面是一个很好的例子:

我想发一条有两行文字的短信

1
2
Hello
world!

如果我将它作为ASCII(或UTF-8)发送,它将如下所示:

1
72 101 108 108 111 10 119 111 114 108 100 33

字节10在某些系统中已损坏,因此我们可以将这些字节编码为base64字符串:

1
SGVsbG8sCndvcmxkIQ==

当使用ASCII编码时,如下所示:

1
83 71 86 115 98 71 56 115 67 110 100 118 99 109 120 107 73 61 61

这里的所有字节都是已知的安全字节,因此任何系统都不会损坏此消息。我可以发送这个消息而不是我的原始消息,并让接收者反向处理以恢复原始消息。


用XML编码二进制数据

假设您想在一个XML文档中嵌入几个图像。图像是二进制数据,而XML文档是文本。但是XML不能处理嵌入的二进制数据。你是怎么做到的?

一个选项是在base64中对图像进行编码,将二进制数据转换为XML可以处理的文本。

而不是:

1
2
3
4
<images>
  <image name="Sally">{binary gibberish that breaks XML parsers}</image>
  <image name="Bobby">{binary gibberish that breaks XML parsers}</image>
</images>

你这样做:

1
2
3
4
<images>
  <image name="Sally" encoding="base64">j23894uaiAJSD3234kljasjkSD...</image>
  <image name="Bobby" encoding="base64">Ja3k23JKasil3452AsdfjlksKsasKD...</image>
</images>

XML解析器能够正确解析XML文档并提取图像数据。


为什么不查看当前定义base64的RFC?

Base encoding of data is used in
many situations to store or transfer
data in environments that, perhaps for
legacy reasons, are restricted to
US-ASCII [1] data.Base encoding can
also be used in new applications
that do not have legacy restrictions,
simply because it makes it possible
to manipulate objects with text
editors.

In the past, different applications
have had different requirements and
thus sometimes implemented base
encodings in slightly different
ways. Today, protocol specifications
sometimes use base encodings in
general, and"base64" in particular,
without a precise description or
reference. Multipurpose Internet Mail
Extensions (MIME) [4] is often used
as a reference for base64 without
considering the consequences for
line-wrapping or non-alphabet
characters. The purpose of this
specification is to establish common
alphabet and encoding
considerations. This will hopefully
reduce ambiguity in other
documents, leading to better
interoperability.

base64最初是作为一种允许二进制数据作为多用途Internet邮件扩展的一部分附加到电子邮件的方法而设计的。


当然,为文本数据设计的媒体最终也是二进制的,但文本媒体通常使用某些二进制值作为控制字符。此外,文本媒体可能拒绝将某些二进制值作为非文本。

base64编码将二进制数据编码为只能在文本媒体中解释为文本的值,并且不包含任何特殊字符和/或控制字符,因此数据也将在文本媒体中保留。


更重要的是,媒体验证字符串编码,因此我们希望确保处理应用程序可以接受数据(例如,不包含表示EOL的二进制序列)。

假设您希望在编码为utf-8的电子邮件中发送二进制数据——如果1和0的流创建了一个序列,而该序列在utf-8编码中不是有效的Unicode,则电子邮件可能无法正确显示。

当我们要对URL本身中的URL无效字符进行编码时,在URL中也会发生同样的情况:

http://www.foo.com/hello my friend -> http://www.foo.com/hello%20my%20friend

这是因为我们想通过一个系统发送一个空间,这个系统会认为这个空间很臭。

我们所要做的就是确保在已知良好的、可接受的和非有害的位序列与另一个位的文字序列之间有一个1对1的映射,并且处理应用程序不区分编码。

在您的示例中,man可能是第一种形式的有效ASCII;但通常您可能希望传输随机二进制的值(即在电子邮件中发送图像):

MIME-Version: 1.0
Content-Description:"Base64 encode of a.gif"
Content-Type: image/gif; name="a.gif"
Content-Transfer-Encoding: Base64
Content-Disposition: attachment; filename="a.gif"

在这里,我们看到一个GIF图像以base64编码为一个电子邮件块。电子邮件客户端读取邮件头并对其进行解码。由于编码的原因,我们可以确保GIF不包含任何可能被解释为协议的内容,并且避免插入SMTP或POP可能认为重要的数据。


当我发现它很方便的时候,有一个例子就是在XML中嵌入二进制数据。SAX解析器错误地解释了一些二进制数据,因为这些数据实际上可以是任何东西,包括XML特殊字符。base64对发送端的数据进行编码,并在接收端对其进行解码,解决了这个问题。


base64而不是转义特殊字符

我将给您一个非常不同但真实的例子:我编写要在浏览器中运行的javascript代码。HTML标记有ID值,但对ID中的有效字符有限制。

但我想让我的ID无损地引用我的文件系统中的文件。文件在现实中可以有各种各样的奇怪和奇妙的字符,从感叹号,重音字符,tilde,甚至emoji!我不能这样做:

1
2
    <img src="http://myserver.com/path/to/my_strangely_named_file!@().jpg">
    Here's a pic I took in Moscow.

假设我想运行这样的代码:

1
2
# ERROR
document.getElementById("/path/to/my_strangely_named_file!@().jpg");

我认为这个代码在执行时会失败。

使用base64,我可以引用一些复杂的东西,而不用担心哪种语言允许哪些特殊字符,哪些需要转义:

1
document.getElementById("18GerPD8fY4iTbNpC9hHNXNHyrDMampPLA");

与使用MD5或其他散列函数不同,您可以反向编码,以查明数据到底是什么真正有用。

我希望我早在64年前就知道Base64了。我本可以避免用"EDOCX1"(0)和"EDOCX1"(1)来扯头发的。

ssh文本传输:

如果您试图通过ssh传递复杂的数据(例如一个dotfile,这样您就可以对shell进行个性化设置),那么在不使用base 64的情况下,祝您好运。这就是使用base 64的方法(我知道您可以使用scp,但这需要多个命令—这会使键绑定复杂化):

  • https://superuser.com/a/1376076/114723

大多数计算机以8位二进制格式存储数据,但这不是必需的。有些机器和传输媒体一次只能处理7位(甚至更小)。这样的媒体将以7位的倍数来解释流,因此如果您要发送8位数据,那么您将无法在另一端接收到您期望的数据。Base-64只是解决这个问题的一种方法:将输入编码为6位格式,通过媒体发送,然后在接收端将其解码为8位格式。


为什么/如何使用base64编码?

base64是一种效率为75%的二进制到文本编码方案。它的使用使得典型的二进制数据(如图像)可以通过传统的"非8位干净"通道安全地发送。在早期的电子邮件网络中(直到20世纪90年代初),大多数电子邮件都是7位US-ASCII字符集的纯文本。许多早期的通信协议标准都是针对"7位"通信链路而设计的,而不是8位的干净链路。方案效率是输入中的位数与编码输出中的位数之间的比率。十六进制(base16)也是一种效率为50%的二进制到文本编码方案。

base64编码步骤(简化):

  • 二进制数据按24位(3字节)的连续块排列。
  • 每个24位数据块按6位的四个部分分组。
  • 每个6位组被转换成相应的base64字符值,即base64编码将三个八位字节转换成四个编码字符。输出字节与输入字节的比率为4:3(33%的开销)。
  • 有趣的是,相同的字符将根据它们在三个八位字节组中的位置进行不同的编码,这三个八位字节组被编码以产生四个字符。
  • 接收器必须反转此过程才能恢复原始消息。

  • What does it mean"media that are
    designed to deal with textual data"?

    这些协议被设计用来处理文本(通常只有英文文本)而不是二进制数据(如.png和.jpg图像)。

    They can deal with binary => they can
    deal with anything.

    但事实并非如此。设计用于表示文本的协议可能不正确地处理恰好包含以下内容的二进制数据:

    • 字节0x0a和0x0d,用于行尾,因平台而异。
    • 其他控制字符,如0x00(空=C字符串结束符)、0x03(文本结束符)、0x04(传输结束符)或0x1A(DOS文件结束符),可能会过早地发出数据结束的信号。
    • 超过0x7F的字节(如果是为ASCII设计的协议)。
    • 无效的UTF-8字节序列。

    所以你不能仅仅通过基于文本的协议发送二进制数据。您仅限于表示非空格非控制ASCII字符的字节,其中94个字符。选择base 64的原因是使用2的幂运算更快,而64是最大的一个。

    One question though. How is that
    systems still don't agree on a common
    encoding technique like the so common
    UTF-8?

    至少在网络上,他们大多数都有。大多数站点使用UTF-8。

    西方的问题是,有很多旧的软件,1字节=1个字符,不能与UTF-8一起工作。

    东方的问题是他们对编码的依恋,如GB2312和shift-jis。

    事实上,微软似乎还没有忘记选择了错误的UTF编码。如果要使用Windows API或Microsoft C运行时库,则只能使用UTF-16或区域设置的"ansi"编码。这使得使用UTF-8很痛苦,因为您必须一直转换。


    除了其他(有些冗长的)答案:即使忽略只支持7位ASCII的旧系统,在文本模式下提供二进制数据的基本问题是:

    • 换行符通常在文本模式下转换。
    • 必须注意不要将nul字节视为文本字符串的结尾,这在任何具有c沿袭的程序中都是很容易做到的。


    What does it mean"media that are designed to deal with textual data"?

    早在ASCII统治世界的那一天,处理非ASCII值就让人头疼。人们跳进各种各样的铁环,通过电线传输这些信息,而不会丢失信息。