为什么我们要使用Base64?

439

维基百科 表示:

在需要对二进制数据进行编码以存储和传输到设计用于处理文本数据的介质时,通常会使用Base64编码方案。这是为了确保在传输过程中数据不会被修改,仍旧能够保持完整性。

但是,数据不是一直以二进制形式存储/传输吗?因为我们机器的内存存储的就是二进制形式,只是取决于你如何解释它。所以,无论你将位模式010011010110000101101110编码为ASCII中的Man还是Base64中的TWFu,最终都将存储相同的位模式。

如果最终转换为0和1来编码,并且每个机器和媒体都可以处理它们,那么数据用ASCII或Base64表示有何区别?

“设计用于处理文本数据的介质”是什么意思?它们可以处理二进制=>他们可以处理任何东西。


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

当我们发送数据时,不能确定数据将以我们预期的格式进行解释。因此,我们发送以某种格式(例如Base64)编码的数据,以便双方都能理解。这样即使发送方和接收方以不同方式解释相同的内容,但因为他们同意使用编码格式,所以数据将不会被错误地解释。

来自Mark Byers的示例

如果我想要发送

Hello
world!

有一种方法是以ASCII方式发送它,如下所示:

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

但是字节10在另一端可能无法被正确解释为换行符,因此我们使用ASCII的一个子集来进行编码,如下所示:

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

这样做会增加传输的数据量,但能确保接收方能按预期方式解码数据,即使接收方对其余字符集有不同的解释。


9
历史背景:电子邮件服务器过去使用7位ASCII编码。其中许多服务器会将高位设置为0,因此您只能发送7位值。请参见http://en.wikipedia.org/wiki/Email#Content_encoding。 - Harold L
4
@Martin,你在开玩笑吧。Perl很难读懂,但是base64根本就无法阅读。 - Peter Long
值得一提的是,以下最正确的答案是Aiden Bell的。虽然7/8位问题、编码等是有效的,但并不是核心原因:任意数据都不能被误解为协议。转换为base64可以防止这种情况发生。例如,包含附件的电子邮件附件可能会出现问题。 - Nick Westgate
1
@Lazer,你的图片已经丢失。 - Mick
10
@Lazer说:“但是在另一端,第10个字节可能无法被正确解释为换行符。” 为什么?两个参与方已经同意使用ASCII码,他们必须正确地解释它啊! - ProgramCpp
显示剩余3条评论
13个回答

468
你的第一个错误是认为ASCII编码和Base64编码可以互换。它们不能互换。它们用于不同的目的。
当你使用ASCII编码文本时,你从一个文本字符串开始,将其转换为一系列字节。
当你使用Base64编码数据时,你从一系列字节开始,将其转换为一个文本字符串。
为了理解为什么首先需要Base64,我们需要了解一些计算机历史。
计算机使用二进制(0和1)进行通信,但人们通常希望以更丰富的数据形式进行通信,例如文本或图像。为了在计算机之间传输这些数据,首先必须将其编码为0和1,然后再解码。以文本为例-有许多不同的方法可以执行此编码。如果我们都能同意一个单一的编码方式,那么就会简单得多,但可悲的是这并非如此。
最初创建了许多不同的编码方式(例如Baudot code),每个字符使用不同数量的位,直到ASCII成为标准,每个字符使用7位。但是,大多数计算机将二进制数据存储在每个字节中,每个字节由8位组成,因此ASCII不适用于传输此类型的数据。有些系统甚至会清除最高有效位。此外,不同系统中行结束编码的差异意味着ASCII字符10和13有时也会被修改。
为了解决这些问题,引入了Base64编码。这允许您将任意字节编码为已知安全发送而不会损坏的字节(ASCII字母数字字符和几个符号)。缺点是使用Base64对消息进行编码会增加其长度-每3个字节的数据编码为4个ASCII字符。
为了可靠地发送文本,您可以首先使用所选的文本编码(例如UTF-8)将其编码为字节,然后将生成的二进制数据进行Base64编码,以ASCII编码的文本字符串形式安全地发送。接收者将不得不反转此过程以恢复原始消息。当然,这要求接收者知道使用了哪些编码,并且这些信息通常需要单独发送。
在电子邮件消息中编码二进制数据的历史悠久,其中电子邮件服务器可能会修改行尾。一个更现代的例子是使用Base64编码直接嵌入图像数据到HTML源代码中。在这种情况下,必须对数据进行编码,以避免类似'<'和'>'的字符被解释为标签。

这里有一个可行的例子:

我希望发送一条包含两行文本的短信:

Hello
world!

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

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

在一些系统中,字节10可能会损坏,因此我们可以将这些字节作为Base64字符串进行编码:

SGVsbG8Kd29ybGQh

使用ASCII编码后,它看起来像这样:

83 71 86 115 98 71 56 75 100 50 57 121 98 71 81 104

这里的所有字节都是已知的安全字节,因此几乎没有任何系统会破坏这条消息。我可以发送这个代替我的原始消息,让接收者反转过程来恢复原始消息。


10
大多数现代通信协议不会破坏数据,例如电子邮件可能会由于投递代理在将消息保存到邮箱时替换字符字符串“\nFrom”为“\n> From”。或者HTTP头是以换行符终止的,没有可逆转义换行符的方法可以将任意ASCII字符直接转储到其中(行继续合并空格),因此您也不能仅仅向其中倾倒任意ASCII字符。Base64比仅7位安全更好,它是字母数字和 =+/ 安全的。 - Steve Jessop
4
使用 Base64 对消息进行编码的缺点是会增加其长度 - 每 3 个字节的数据编码为 4 个字节。这是因为在 Base64 编码中,每个字符由 6 个比特位组成,所以在将 3 个字节编码时,会产生 4 个字符,即 4 个字节。因此,使用 Base64 编码后的消息长度会增加约三分之一。 - Lazer
10
@Lazer: 不行。看看你自己的例子 - "Man" 被 Base-64 编码为 "TWFu"。3个字节变成了4个字节。这是因为输入可以是256个可能的字节之一,而输出仅使用其中的64个(包括等号以帮助表示数据长度)。每个输出四联字符会浪费8位,以防止输出包含任何“激动人心”的字符,即使输入中有这些字符也是如此。 - Steve Jessop
6
将"When you encode data in Base64, you start with a sequence of bytes and convert it to a text string"这句话重新表述为:"当你在Base64中编码数据时,你从一系列字节开始,将其转换为只包含ASCII值的一系列字节。" 只包含ASCII字符的字节序列是SMTP所需的,这就是为什么Base64(和引用可打印)被用作内容传输编码的原因。非常好的概述! - ALEXintlsos
4
我找到了一篇提到这个问题的回帖,“如果我们不这样做,就有可能会导致某些字符被错误地解释。例如: 换行符(如0x0A和0x0D), 控制字符,如^C、^D和^Z,在某些平台上被解释为文件结束符, 空字节作为文本字符串的结尾, 大于0x7F的字节(非ASCII字符), 我们在HTML/XML文档中使用Base64编码来避免像'<'和'>'这样的字符被解释为标签。” - Joshua
显示剩余6条评论

82

在XML中编码二进制数据

假设您想要在XML文档中嵌入一些图像。这些图像是二进制数据,而XML文档是文本。但是XML无法处理嵌入的二进制数据。那么该怎么办呢?

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

与其:

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

你需要做的事情:

<images>
  <image name="Sally" encoding="base64">j23894uaiAJSD3234kljasjkSD...</image>
  <image name="Bobby" encoding="base64">Ja3k23JKasil3452AsdfjlksKsasKD...</image>
</images>

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


这可能是微软旧的.mht格式的工作方式(将HTML文件和图像存储在单个文件中)。 - Sridhar Sarnobat

46

为什么不看一下当前定义Base64的RFC文档呢?

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

过去,不同的应用程序有不同的要求,因此有时会以稍微不同的方式实现基础编码。今天,协议规范有时会通用地使用基本编码,特别是“base64”,而没有精确的描述或参考。多用途互联网邮件扩展(MIME)[4]经常被用作对base64的参考,而不考虑换行或非字母表字符的后果。本规范的目的是建立共同的字母表和编码考虑事项。这将希望减少其他文档中的歧义,从而实现更好的互操作性。

最初,Base64是作为一种方式设计的,以允许二进制数据作为多用途互联网邮件扩展的一部分附加到电子邮件上。


1
这是公平的,但也引出了一个问题,为什么我们今天不再局限于US-ASCII码,却仍然在使用它呢? - Mike B

40

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

Base64编码将二进制数据编码为只能在文本媒体中解释为文本的值,并且不包含任何特殊字符和/或控制字符,以便该数据在文本媒体间得到保留。


1
所以这就像 Base64 一样,通常源和目标都会以相同的方式解释数据,因为它们很可能会以相同的方式解释这 64 个字符,即使它们以不同的方式解释控制字符。 对吗? - Lazer
6
数据甚至可能在传输过程中被破坏。例如,许多 FTP 程序会在传输标记为文本模式的情况下,如果服务器和客户端的操作系统不匹配,则将行尾从13,10重写为10或相反。FTP 只是我想到的第一个例子,它并不好,因为 FTP 支持二进制模式。 - Hendrik Brummermann
@nhnb:我认为FTP是一个很好的例子,因为它表明文本模式不适用于需要二进制数据的事物。 - jamesdlin
1
什么是文本媒体? - Koray Tugay
但这引出了一个问题,如果不是base64,那么其他协议使用什么呢?难道每个协议都会有需要保留某些字节作为控制字符的问题吗?然而我只看到电子邮件和表单数据使用base64。 - Mike B

29

媒体验证字符串编码的正确性,因此我们希望确保数据可被处理应用程序接受(并且不包含代表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图像被编码为电子邮件的一部分。邮件客户端读取标题并对其进行解码。由于编码方式,我们可以确保GIF不包含任何可能被解释为协议的内容,并且避免插入SMTP或POP可能找到的数据。


2
太棒了——这个解释让我恍然大悟。它不是为了混淆或压缩数据,而只是为了避免使用可以被解释为协议的特殊序列。 - Patrick Michaelsen

27

阅读其他人发布的内容后,我对Base64编码有了以下理解:

重要!

Base64编码不是为了提供安全性。

Base64编码不是为了压缩数据。

我们为什么使用Base64

Base64是一种文本表示法,由只包含64个字符的字符集组成,这些字符是大小写字母、数字以及+、/和=。

这64个字符被认为是“安全”的,也就是说,它们不像<、> \n等字符那样容易被旧的计算机和程序误解。

Base64何时有用

在将文件作为文本传输时,我发现Base64非常有用。您可以获取文件的字节并将其编码为Base64,然后传输Base64字符串,接收方则进行相反操作。

这与通过电子邮件发送附件时使用的过程相同。

如何执行Base64编码/解码

从Base64文本转换为字节称为解码。

从字节转换为Base64文本称为编码。这与其他编码/解码的命名方式略有不同。

Dotnet和Powershell

Microsoft的Dotnet框架支持将字节编码和解码为Base64。请在mscorlib库中查找Convert命名空间。

以下是您可以使用的Powershell命令:

// Base64 encode PowerShell 
// See: https://adsecurity.org/?p=478
$Text='This is my nice cool text'
$Bytes = [System.Text.Encoding]::Unicode.GetBytes($Text)
$EncodedText = [Convert]::ToBase64String($Bytes)
$EncodedText


// Convert from base64 to plain text 
[System.Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('VABoAGkAcwAgAGkAcwAgAG0AeQAgAG4AaQBjAGUAIABjAG8AbwBsACAAdABlAHgAdAA='))
Output>This is my nice cool text 

Bash内置了一个用于base64编码/解码的命令。您可以像这样使用它:

要进行base64编码:

bash命令行中输入:
```bash echo 'your message' | base64 ```
将 "your message" 替换为您要编码的消息。
echo 'hello' | base64

将base64编码的文本解码为普通文本:

echo 'aGVsbG8K' | base64 -d

Node.js也支持base64编码。下面是一个可以使用的类:


/**
 * Attachment class.
 * Converts base64 string to file and file to base64 string
 * Converting a Buffer to a string is known as decoding.
 * Converting a string to a Buffer is known as encoding.
 * See: https://nodejs.org/api/buffer.html
 * 
 * For binary to text, the naming convention is reversed.
 * Converting Buffer to string is encoding.
 * Converting string to Buffer is decoding.
 *  
 */
class Attachment {
    constructor(){

    }

    /**
     * 
     * @param {string} base64Str 
     * @returns {Buffer} file buffer
     */
    static base64ToBuffer(base64Str) {
        const fileBuffer = Buffer.from(base64Str, 'base64');
        // console.log(fileBuffer)
        return fileBuffer;
    }

    /**
     * 
     * @param {Buffer} fileBuffer 
     * @returns { string } base64 encoded content
     */
    static bufferToBase64(fileBuffer) {
        const base64Encoded = fileBuffer.toString('base64')
        // console.log(base64Encoded)
        return base64Encoded
    }
}

你可以这样获得文件缓冲区:

  const fileBuffer = fs.readFileSync(path);

或者像这样:

const buf = Buffer.from('hey there');

你还可以使用 API 来进行编码和解码,以下是其中之一:

要进行编码,将原始文本作为正文传递。

POST https://mk34rgwhnf.execute-api.ap-south-1.amazonaws.com/base64-encode

要进行解码,请将 base64 字符串作为正文传递。

POST https://mk34rgwhnf.execute-api.ap-south-1.amazonaws.com/base64-decode

使用 base64 的虚构示例

这里是一个很牵强的场景,描述了何时可能需要使用 base64。

假设你是一名间谍,正在执行任务,需要复制并带回一张非常有价值的图片以供你国家的情报机关使用。

这张图片存储在一台没有网络访问权限和打印机的计算机上。你手头只有一支笔和一张纸。没有闪存,没有光盘等。那么你该怎么办呢?

你的第一个选择是将图片转换成二进制的 0 和 1,然后逐个复制到纸上,然后就可以离开了。

然而,这可能会很困难,因为仅使用 0 和 1 作为字母表来表示图片将导致非常多的 0 和 1。你的纸张很小,而且时间也不充足。此外,0 和 1 越多,出错的机会越大。

你的第二个选择是使用十六进制代替二进制。十六进制允许有 16 种可能的字符,因此你拥有更广泛的字母表,需要的纸张和时间更少。

更好的选择是将图片转换为 base64,并利用另一个更大的字符集来表示数据。用的纸张更少,完成所需时间更短。就是这样!


为什么我们不使用基于256的编码来减小消息的大小呢?我特别好奇为什么人们在图像上使用base64,因为它会增加文件大小。 - Stan
我们不使用base64是因为我们想节省字节。 - Gilbert
什么是“节省字节”的意思? - Stan
1
@StanPeng 我的意思是,我们不需要关心文本变得多大。Base64会引入一些开销,但它可以解决问题。例如,想象一下使用像soap这样的协议发送图像。你只能发送文本,不允许发送二进制大对象或文件。那么你该怎么办呢?你可以将图像转换为Base64文本,然后发送它。 - Gilbert
如果我们发送一个缓冲区整数数组,它可能会成功。我已经在 rest 中完成了这个操作,尽管该数组可能被序列化为文本。 - Stan
@StanPeng 是的,我也考虑过这个问题,但我从未尝试过。 - Gilbert

20

使用Base64替代转义特殊字符

我将给你一个非常不同但却真实的例子:我编写javascript代码以在浏览器中运行。HTML标签有ID值,但是对于ID中哪些字符是有效的存在一些限制。

但我希望我的ID能够无损地指向文件系统中的文件。现实中的文件可以拥有各种奇怪和精彩的字符,包括感叹号,重音字符,波浪线,甚至是表情符号!我不能这样做:

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

假设我想运行类似这样的一些代码:

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

我认为当执行时这段代码会失败。

使用Base64编码,我可以引用一些复杂的内容而不必担心哪种语言允许哪些特殊字符并需要转义:

document.getElementById("18GerPD8fY4iTbNpC9hHNXNHyrDMampPLA");

与使用MD5或其他哈希函数不同,您可以反编码以找出实际有用的数据究竟是什么。

要是我早知道Base64就好了。这样我就可以避免使用‘encodeURIComponent’和str.replace(‘\n’,’\\n’) 以至于抓狂了。

文本的SSH传输:

如果您正在尝试通过ssh传递复杂数据(例如点文件以便获得自定义的shell),没有Base64将难以实现。以下是使用base64的方法(我知道您可以使用SCP,但那需要多个命令-这会使ssh键绑定变得更加复杂):


15

我发现它很方便的一个例子是在尝试将二进制数据嵌入XML时。 SAX解析器会将其中一些二进制数据误解为XML特殊字符,因为这些数据可能是任何内容。在发送端对数据进行Base64编码,并在接收端对其进行解码,可以解决该问题。


1
+1 -- 但这并不是SAX特有的。任何XML解析器都会发生,例如DOM或XLINQ。 - Billy ONeal
1
@Billy:是的,当然。我只是碰巧在那个应用程序中使用了SAX解析器。 - Bill the Lizard
不同的引擎,例如SAX解析器可能以不同的方式解释一些ASCII值(不同的控制字符)。因此,这里的想法是使用具有普遍共同含义的ASCII子集。对吗? - Lazer
1
@Lazer:没错。如果你试图将未编码的二进制数据解释为ASCII,那么它里面就可能会偶然包含控制字符(但在这种情况下并没有)。 - Bill the Lizard

11

大多数计算机以8位二进制格式存储数据,但这并不是必需的。有些设备和传输介质一次只能处理7位(甚至更少)。这样的���质会按照7位的倍数解释流,因此如果您发送8位数据,则在另一端将无法收到预期的结果。Base-64只是解决此问题的一种方法:将输入编码为6位格式,通过介质发送,然后在接收端将其解码回8位格式。


4
如果流在传输7位后中断,这为什么是一个问题呢?最终,另一台机器将接收到流中的所有数据,然后可以选择8位格式来显示它。我的想法有什么问题吗! - mallaudin
@mallaudin,你说得对,Base64的真正原因是为了避免在解析应用程序的文本层中使用特殊字符而导致误解(它是在应用层而不是物理层中使用的)。 - ZekeC

8

除了其他(有点冗长)的答案之外:即使忽略仅支持7位ASCII的旧系统,以文本模式提供二进制数据的基本问题包括:

  • 换行符通常在文本模式下进行转换。
  • 必须小心不要将NUL字节视为文本字符串的结尾,在任何具有C血统的程序中都很容易这样做。

还有一些控制字符,如^C、^D和^Z,在某些平台上会被解释为文件结束符。 - dan04

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接