❝不要乱说话。话说出去之前我们还是话的主人,话说出去之后我们就成了话的奴隶。 ❞
大家好,我是「柒八九」。
前言
在我们项目开发中,Base64
想必大家都不会很陌生,Base64
是将「二进制数据」转换为文本
的一种优雅方式,使存储和传输变得容易。但是,作为一个合格的程序员,我们应该有一种打破砂锅问到底的求助欲望。
所以,今天我们来讲讲在各种语言中出镜率都高的离谱的Base64
算法。今天,我们就用我们在初高中语文老师教我们的描述一个事物的三大步骤:1. 是什么,2. 如何工作,3. 为什么它很重要。来讲讲Base64
算法。
好了,天不早了,干点正事哇。
1. 前置知识点
❝「前置知识点」,只是做一个概念的介绍,不会做深度解释。因为,这些概念在下面文章中会有出现,为了让行文更加的顺畅,所以将本该在文内的概念解释放到前面来。「如果大家对这些概念熟悉,可以直接忽略」 同时,由于阅读我文章的群体有很多,所以有些知识点可能「我视之若珍宝,尔视只如草芥,弃之如敝履」。以下知识点,请「酌情使用」。 ❞
RFC
❝
RFC
,全称为Request for Comments
,是一种用于定义「互联网标准和协议」的文件系列。 ❞
RFC
最早由互联网工程任务组(IETF
)创建,用于记录和传播互联网协议、方法和最佳实践的提案、规范和讨论。
「每个 RFC 都有一个唯一的编号」,通常以RFC
开头,后面跟着一个数字,例如RFC 791
、RFC 2616
等。RFC
文档通常包含了协议规范、技术说明、最佳实践、标准化提案等,以促进互联网技术的发展和互操作性。
我们可以在IETF-datatracker[1]中输入指定的编号或者查找的关键字进行搜寻。
以下是一些常见的RFC
文档,大家可以翻阅自己想了解的技术点:
RFC 791
-Internet Protocol
(IP): 定义了IPv4
,是互联网上最基本的协议之一。RFC 793
-Transmission Control Protocol
(TCP
): 定义了TCP
,一种重要的传输协议,用于可靠的数据传输。RFC 2616
-Hypertext Transfer Protocol
--HTTP/1.1
: 定义了HTTP
协议,用于在Web
上传输超文本的基础通信协议。RFC 2326
-Real Time Streaming Protocol
(RTSP
):RTSP
用于流媒体传输,如音频和视频流的控制。RFC 5246
-The Transport Layer Security
(TLS) Protocol Version 1.2: 定义了TLS 1.2
,用于安全地传输数据,如HTTPS
协议中使用的加密通信。- RFC 4648[2] - 这是咱们今天的主角,
Base64
的相关内容
Latin-1 字符集
Latin-1
,也称为ISO-8859-1
,是一种由国际标准化组织(ISO
)认可的「8 位字符集」,代表了「西欧语言的字母表」。正如其名称所示,「它是ISO-8859
的一个子集」,该标准还包括用于写作系统如西里尔文、希伯来文和阿拉伯文的其他相关字符集。它被大多数Unix
系统以及Windows
系统使用。
❝
Latin-1
有时被不太准确地称为「扩展 ASCII」。 ❞
这是因为其字符集的前 128 个字符
与美国 ASCII 标准相同。其余字符集包含了带重音的字符和符号。
关于更详细的Latin-1
的表格,可以参考Latin-1-table[3]
btoa
btoa
是 JavaScript
中的一个内置函数,用于将二进制数据
(通常是 8 位字节)编码为 Base64
字符串。它的名称是 binary to ASCII
的缩写,用于将二进制数据转换为文本字符串,以便在文本协议中传输或存储。
用法:
btoa
函数接受一个字符串参数
,该字符串包含二进制数据。它将该二进制数据转换为 Base64
编码的字符串。
const binaryData = "front789";
const base64String = btoa(binaryData);
console.log(base64String);
这段代码将 front789
这个字符串转换为 Base64
编码的字符串并将结果打印到控制台。
限制:
尽管 btoa
是一个有用的函数,但它有一些限制:
- 「只能编码字符串:」
btoa
函数只接受字符串作为参数,而不接受其他类型的数据(如二进制数组)。如果需要编码二进制数据,需要先将其转换为字符串。 - 「字符集限制:」
btoa
函数仅支持Latin-1
字符集,这意味着它只能编码包含在Latin-1
字符集内的字符。如果字符串包含超出Latin-1
字符集的字符,那么会导致编码失败。 - 「不适合加密:」
Base64
编码不是加密,它只是一种编码方式,不提供安全性。如果需要加密数据,应该使用专门的加密算法而不是仅仅进行Base64
编码。 - 「数据大小增加:」
Base64
编码会增加数据大小。通常情况下,Base64
编码后的数据会比原始二进制数据更大,这可能会对数据传输和存储造成额外开销。
Data URL
Data URL
是一种统一资源标识符(URI)方案,用于将数据嵌入到文档中,而不是从外部文件加载数据。Data URL
允许我们将数据(如文本、图像、音频等)直接包含在网页或文档中,而不需要额外的 HTTP 请求。这种方式对于小型资源或需要避免外部请求的情况非常有用。
Data URL 的基本结构如下:
代码语言:javascript复制data:[<mediatype>][;base64],<data>
其中:
<mediatype>
是可选的媒体类型(例如,text/plain
或image/png
),用于描述数据的类型。如果被省略,则默认值为text/plain;charset=US-ASCII
。;base64
是可选的,表示数据以Base64
编码方式包含。如果省略了;base64
,则数据将以纯文本方式包含。<data>
包含实际的数据,可以是文本或二进制数据。
以下是 Data URL
的一些常见用途和示例:
「嵌入图像:」 Data URL 可用于将图像直接嵌入 HTML
或 CSS
中,而不需要外部图像文件。例如,将一张 PNG 图像嵌入 HTML 中:
<img
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/wAABgAB/OGirwAAAABJRU5ErkJggg=="
alt="Embedded Image"
/>
「内联 CSS:」 Data URL
可用于内联 CSS
样式表,以减少外部 CSS
文件的请求。例如,将 CSS
样式表嵌入 HTML 中:
<style>
body {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/wAABgAB/OGirwAAAABJRU5ErkJggg==);
}
</style>
「嵌入字体:」 Data URL
可用于嵌入自定义字体,以确保字体在不同设备上显示一致。例如,嵌入一个字体文件:
@font-face {
font-family: "CustomFont";
src: url(data:application/font-woff;base64,d09GRgABAAAA...) format("woff");
}
「内联脚本:」 Data URL
可用于内联小型 JavaScript
脚本,以减少外部脚本文件的请求。例如,内联一个简单的 JavaScript
函数:
<script>
let greeting = "前端柒八九";
alert(greeting);
</script>
2. 为什么会出现 Base64 编码
要理解为什么需要 Base64
编码,我们需要了解一些计算机历史。
计算机以二进制(0
和 1
)进行通信,但人们通常希望使用更丰富的数据形式进行通信,如文本
或图像
。「为了在计算机之间传输数据,首先必须将其编码为 0 和 1,然后再解码」。以文本为例,有许多不同的编码方式。如果我们都能就一个单一的编码方式达成一致,那将会简单得多,但很遗憾,这并不是事实。针对这块的内容,可以参考了不起的 Unicode
最初创建了许多不同的编码方式(例如 Baudot
编码),每种方式「使用不同数量的比特来表示一个字符」,直到最终 ASCII
成为一个标准,「每个字符使用 7 位」。然而,大多数「计算机将二进制数据存储为每个字节由 8 位组成的数据」,因此 ASCII
不适合传输这种类型的数据。一些系统甚至会删除最高位。
为解决这些问题,引入了 Base64
编码。这允许我们「将任意字节编码为已知不会损坏的字节」(ASCII 字母数字字符和一些符号)。缺点是使用 Base64
对消息进行编码会增加其长度 - 「每 3 个字节的数据编码为 4 个 ASCII 字符」。
要可靠地发送文本,我们可以首先使用自己选择的文本编码(例如 UTF-8
)将其编码为字节,然后将结果的二进制数据使用 Base64
编码为可安全传输的 ASCII
文本字符串。接收者反转此过程以恢复原始消息。当然,这需要接收者知道使用了哪种编码,通常需要单独发送这些信息。
我们来看一个示例:
我希望发送一个带有两行的文本消息:
代码语言:javascript复制Hello
world!
如果我将其发送为 ASCII
(或 UTF-8),它将如下所示:
72 101 108 108 111 10 119 111 114 108 100 33
某些系统会破坏字节 10
,所以我们可以将这些字节作为 Base64
字符串进行 Base64
编码:
SGVsbG8Kd29ybGQh
这里的所有字节都是已知的安全字节,所以很少有机会使任何系统损坏此消息。我可以发送这个消息而不是我的原始消息,然后让接收者反转此过程以恢复原始消息。
2. 什么是 Base64 编码?
Base64编码
将二进制数据转换为文本,具体来说是ASCII文本
。生成的文本仅包含A-Z
、a-z
、0-9
以及符号
和/
这些字符。
而在之前我们在了不起的 Unicode中介绍过ASCII
的。
由于字母表中有 26 个字母,我们有26 26 10 2
(64
)个字符。因此,这种编码被命名为Base64
。这 64 个字符被认为是「安全」的,也就是说,与字符<
、>
、n
等不同,「它们不会被旧计算机和程序误解」。
下面是经过 Base64
编码的文本front789
的样子:ZnJvbnQ3ODk=
。
还有一点需要注意,如果在使用JS
对某一个文本进行准换时,如果该文本包含非Latin1
字符的字符串,会报错,所以我们需要对其进行准换处理。
// 原始文本字符串,包含非Latin1字符
const text = "前端柒八九";
// 创建一个 TextEncoder 对象,用于将文本编码为字节数组
const encoder = new TextEncoder();
// 使用 TextEncoder 对象将文本编码为字节数组
const data = encoder.encode(text);
// 使用 String.fromCharCode 和展开运算符 (...) 将字节数组转换为字符串
// 然后使用 btoa 函数将字符串转换为 Base64 编码
const base64 = btoa(String.fromCharCode(...data));
// 打印 Base64 编码后的结果
console.log(base64); //5YmN56uv5p S5YWr5Lmd
我们在这里并没有加密文本。给定Base64
编码的数据,非常容易将其转换回(解码)原始文本。我们「只是改变了数据的表示」,即编码
。
❝在本质上,
Base64编码
使用一组特定的、减少的字符来「编码二进制数据」,以防止数据损坏。 ❞
Base64字母表
由于只有64个字符
可用于编码,我们可以仅使用6位
来表示它们,因为2^6 = 64
。每个Base64
数字表示6位数据
。一个字节中有8位
,而 8
和 6
的「最小公倍数」是 24
。因此,「24 位,或 3 个字节,可以用四个 6 位的 Base64 数字表示」。
4. Base64 使用案例
我们可能在HTML
文档中使用了<img src="789.jpeg">
标签来包含图像。其实,我们可以直接将「图像数据」嵌入到 HTML 中,而不必使用外链!数据URL
可以做到这一点,它们使用Base64编码
的文本来内联嵌入文件。
<img src="data:image/gif;base64,xxxxbase64encodedtextxxxx" />
data:[<mime type
>][;charset=<charset>][;base64],<encoded data></encoded></charset
></mime>
另一个常见的用例是当我们需要在网络上传输或存储一些二进制数据,而网络只能处理文本或ASCII
数据时。这确保了数据在传输过程中保持不变。还有就是在 URL 中传递数据时,当数据包含不适合 URL 的字符时,此时Base64
就有了用武之地。
Base编码
还在许多应用程序中使用,因为它使得可以使用文本编辑器来操作对象。
我们还可以使用 Base64 编码「将文件作为文本传输」。
- 首先,获取文件的字节并将它们「编码为 Base64」。
- 然后传输 Base64 编码的字符串,然后在接收端「解码为原始文件内容」。
5. Base64 编码算法
以下是将一些文本转换为 Base64
的简单算法。
- 将文本转换为其
二进制表示
。 - 将
比特位
分组为每组6位
。 - 将每个组转换为
0到63
的十进制数。它不能大于 64,因为每组只有 6 位。- 如果转换为十进制数的数字大于 64,我们可以将其
取模64
例如:151 % 64 = 23
- 如果转换为十进制数的数字大于 64,我们可以将其
- 使用
Base64字母表
将此十进制数转换为等效的Base64字符
。
通过上述操作我们会得到一个Base64编码
的字符串。如果最后一组中的比特位不足,可以使用=
或==
作为填充。
让我们以front7
作为范例,来模拟上述操作。
通过首先将每个字符转换为其对应的 ASCII
数字,然后将该十进制数转换为二进制,(使用ASCII 转二进制工具[4])将文本front7
转换为二进制:
01100110 01110010 01101111 01101110 01110100 00110111
f r o n t 7
将比特位分组为每组6位
:
011001 100111 001001 101111 011011 100111 010000 110111
将每个组转换为 0 到 63 之间的十进制数:
代码语言:javascript复制011001 100111 001001 101111 011011 100111 010000 110111
25 23 9 47 27 23 16 27
- 这步中如果数据超过 64,需要对其 64 取模
现在使用 Base64
字母表将每个十进制数转换为其 Base64
表示:
25 23 9 47 27 23 16 27
Z n J v b n Q 3
然后我们完成了。名字front7
在 Base64 中表示为ZnJvbnQ3
。
乍一看,Base64 编码的好处并不是很明显。
想象一下,如果我们有一张图片或一个「敏感文件」(PDF、文本、视频等),而不是简单的字符串,我们想将它存储为文本。我们可以首先将其转换为二进制,然后进行 Base64
编码,以获得相应的 ASCII
文本。
现在我们可以将该文本发送或存储在任何地方,以任何我们喜欢的方式,而不必担心一些旧设备、协议或软件会错误解释原始二进制数据以损坏我们的文件。
6. 如何进行 Base64 编码和解码
所有编程语言都支持将数据编码为 Base64
格式以及从 Base64
格式解码数据。
JS 中处理
代码语言:javascript复制// 简单字符串
const text1 = "front789";
bota(text1); // ZnJvbnQ3ODk=
// 超出`Latin-1`字符的字符串
const text2 = "前端柒八九";
const encoder = new TextEncoder();
const data = encoder.encode(text);
const base64 = btoa(String.fromCharCode(...data));
console.log(base64); //5YmN56uv5p S5YWr5Lmd
Rust 中处理
用Rust
的话,我们可以直接用 base64
crate。
在 Cargo.toml
文件中添加以下内容:
[dependencies]
base64 = "0.21.5"
代码语言:javascript复制use base64::{Engine as _, engine::general_purpose};
let orig = b"data";
let encoded: String = general_purpose::STANDARD_NO_PAD.encode(orig);
assert_eq!("ZGF0YQ", encoded);
assert_eq!(orig.as_slice(), &general_purpose::STANDARD_NO_PAD.decode(encoded).unwrap());
// or, URL-safe
let encoded_url = general_purpose::URL_SAFE_NO_PAD.encode(orig);
想了解更多关于Rust
如何处理Base64
,可以查看Rust base64[5]
此外,终端也内置支持 Base64
编码。在终端中尝试以下命令:
echo "前端柒八九" | base64
5YmN56uv5p S5YWr5LmdCg==
$ echo "5YmN56uv5p S5YWr5LmdCg==" | base64 -d
前端柒八九
后记
「分享是一种态度」。
「全文完,既然看到这里了,如果觉得不错,随手点个赞和“在看”吧。」
Reference
[1]
IETF-datatracker: https://datatracker.ietf.org/
[2]
RFC 4648: https://datatracker.ietf.org/doc/html/rfc4648?ref=writesoftwarewell.com
[3]
Latin-1-table: https://kb.iu.edu/d/aepu
[4]
ASCII 转二进制工具: https://www.rapidtables.com/convert/number/ascii-to-binary.html
[5]
Rust base64: https://docs.rs/base64/latest/base64/