一篇文章彻底弄懂 base64 及原理!

按:base64如今仍然是常见的编码方式,尤其是在“原始数据是二进制,而传输协议只支持文本”的场合。可惜的是,许多开发人员并不清楚其中的原理,只知道“看起来毫无意义,但又有一大堆等号的”就是base64。恰好, 我的朋友胡永浩写了这篇文章,深入浅出讲解了base64,值得认真阅读。

一篇文章彻底弄懂 base64 及其原理

Python资源共享群:484031800

Base32, Base64

Base32 是一个  binary-to-text encoding [1] schemes,顾名思义,就是将二进制数据转换为编码只有基础 32 个字符的数据编码方式, Base64 则是 64 个。注意编码不等同于加密,网上有误解 Base 编码方式为加密方式,实际上标准 Base64 编码解码无需额外信息即完全可逆。

Base 编码常见用途如下:

如定义所言,binary to text

一些协议如 HTTP , FTP (File Transfer Protocol) [当指定发送文本时], SMTP (Simple Mail Transfer Protocol) 是 text-based protocol ,也就是只支持文本传输,不支持二进制传输。是的,http 上传文件,图片时使用的 multipart/form-data 也是需要转成文本的。

所以附件如图片,文件等(binary)就可以用 Base64 编码为 text再传输。

将资源编码为字符串

如  data URI scheme [2] 定义了如下语法来识别网页中的资源:

data:[<media type>][;base64],<data>

HTML 中可以在标签中指定识别 Base64编码 来展示资源,

<div>

<p>Taken from wikpedia</p>

<img src="data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAUA

AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO

9TXL0Y4OHwAAAABJRU5ErkJggg==" alt="Red dot" />

</div>

但因为 Base64 是每 3 个原始字符编码成 4 个字符,不够时补 = (下文会详述),因此编码后的大小是有可能会比原文件大的,所以 html 用 Base64 来展示图片而不是用具体的图片好处大概就只有少建立一条 http 连接以及少一个 http 请求(在 HTTP 1.1 以下),这种办法只有大量的小图片才有优越性了。

统一转成『合法』字符

为了避免出现不符合规则的字符,方便把含有不可见字符串的信息用可见字符串表示出来。比如 http 协议当中的 headers 头部,必须进行 URLEncode 不然出现的等号可能使解析失败,空格也会使 http 请求解析出现问题,比如请求行也就是 request 就是以空格来划分的 POST /hi/you HTTP/1 ,值得注意的是 Base32 的字符列表里有不合法字符 / 。

还有避免原始信息经过百花齐开的路由,网关多次转发,因有部分系统不支持此不可识别字符或将此作为控制符,将其转义、丢弃等,造成信息丢失,所以如电子邮件里的附件也是用 base64 编码的。

base64url

有 base64 编码的变种 base64url ,将base64 编码中的 + 换成 - 以及将 / 换成 _ ,甚至不需要往后面补 = 了。这样子在 url 中传递东西时,不再需要 URL encode ,好处就是长度短了,以及好看了一点,毕竟 % 有点视觉污染(实际上,还可以直接将编码后的东西存数据库了,因为 base64 比 URLEncode 更通用了 )

Base64 的由来——参考 RFC

RFC 向来都不会说明设计的历史由来,自然 base64 编码也是一样,我参考的  rfc4648 [3] 也只是说明了因为当时开发者们自己发明使用base 64并不规范,没有统一的标准,因此定义了一份通用标准。

然后呢, Base64 就是自己选了 ASCII 子集(64 个字符)为标准字符集,当然这也是因为 64 是 2的 x 次方 (如 64 就是 2 的6次方),而1个 bit 分别有 0和 1 两种状态,6 个 bit也就是 2 的 6 次方=64 个状态,刚好可以表示 64 个字符,因此 6 个 bit 就可以表达出 64 个字符了。就是下面定义的 64 个:

Table 1: The Base 64 Alphabet


Value Encoding Value Encoding Value Encoding Value Encoding

0 A 17 R 34 i 51 z

1 B 18 S 35 j 52 0

2 C 19 T 36 k 53 1

3 D 20 U 37 l 54 2

4 E 21 V 38 m 55 3

5 F 22 W 39 n 56 4

6 G 23 X 40 o 57 5

7 H 24 Y 41 p 58 6

8 I 25 Z 42 q 59 7

9 J 26 a 43 r 60 8

10 K 27 b 44 s 61 9

11 L 28 c 45 t 62 +

12 M 29 d 46 u 63 /

13 N 30 e 47 v

14 O 31 f 48 w (pad) =

15 P 32 g 49 x

16 Q 33 h 50 y

编码定义

The encoding process represents 24-bit groups of input bits as output

strings of 4 encoded characters.

• 输入:二进制(图片,文件,字符串本质就是二进制) • 输出:编码后的字符串 • 处理过程:处理输入的二进制时,每 24 个 bit (3 个字节)作为一组,编码输出为 base64 处理 后的 4 个标准字符集中的字符。

值得注意的是,网上的示例或说明中,都或多或少有以下偏颇之处:

• 输入的例子可以是16 进制数字、二进制、一串数字等,很多文章举的例子都是字符串;让人忽略 binary to text 的 binary • 是每 24 位(同样需要注意不一定是 3 个 8 位的字符,3 个字节bytes才准确)为一组来处理,输出 4 个编码后的字符。强调这点是因为,24 位为一组,不够的都需要补 = ,如按其他人的文章说的 8 位 8 位的转,根本不清楚要补多少 = • 24 位转成 4 个编码后的字符(也就是 4*8=32位),所以编码后的长度肯定会变大 • 综上所述,RFC 原文才是最对的定义,有时细微的区别意味着理解有问题。下面会一一说明。

特殊处理

When fewer than 24 input

bits are available in an input group, bits with value zero are added

(on the right) to form an integral number of 6-bit groups.

Padding at the end of the data is performed using the '=' character.

• 每 24 位为一组来编码输入的 binary 时,如果最后的一组不足24 位,往后补 0直到 补足到 24 • 对于最后对于全为 0 的一组,补充 =

举一些例子来说明一下:

Input data: 0x14fb9c03d97e

16进制: 1 4 f b 9 c | 0 3 d 9 7 e

2进制: 00010100 11111011 10011100 | 00000011 11011001 01111110

6位一组: 000101 001111 101110 011100 | 000000 111101 100101 111110

Decimal: 5 15 46 28 0 61 37 62

Output: F P u c A 9 l +

16 进制的 0x14fb9c03d97e 作为输入,先转成二进制,然后 2 进制的每 24 位 选出来编码,上面例子就是: 00010100 11111011 10011100 ,然后 6 位一组的分开,得到 000101 001111 101110 011100 。

然后分别转 10 进制,也就是 000101 变成 5, 001111 变成 15等,再去 base64 定义的字符列表中找出此 10 进制对应的字符,以此类推,就是 base64 后的结果了。

上面例子是输入刚好是有48 位, 2个 24 位,刚刚够,不需要补 =

下面看看需要补 = 的例子:

Input data: 0x14fb9c03

Hex: 1 4 f b 9 c | 0 3

8-bit: 00010100 11111011 10011100 | 00000011 开始补 0 =》00000000 00000000

pad

6-bit: 000101 001111 101110 011100 | 000000 110000 000000 000000

Decimal: 5 15 46 28 0 48

pad with = =

Output: F P u c A w = =

注意上述输入只有 32 位,第一个 24 位处理完后,还剩下 8 位,因此需要补16 个 0.

补完后,就是 48 位的输入了,照样每 24 位输出 4 个编码后的字符。

观察后半部分, 000000 110000 000000 000000 ,第一个 000000 因为后面还有内容,所以10 进制为 0,因此编码字符为 A,这个很正常;而 1100000 之后的两个 6 位 0 ,都是纯粹的填充(pading)了,因此并不用 A 而都用 = 代替掉 ,注意不用 A

Base64 decode

说完 encode,decode 就容易啦,无非就是逆过程,RFC 都不屑于讲了。。。

一串 base64 后的字符串,根据每个字符在 base64 字符表里找到对应的 10 进制,然后转成 2 进制,最后多余补足的 000000 去掉,完了:cold_sweat:

URL_Encode 以及 Unicode 相关留待下次说。

References

[1] binary-to-text encoding: https://www.wikiwand.com/en/Binary-to-text_encoding

[2] data URI scheme: https://www.wikiwand.com/en/Data_URI_scheme

[3] rfc4648: https://tools.ietf.org/html/rfc4648#page-3

你可能感兴趣的:(Python)