编码原理理解之「 base64」

2020-09-28  本文已影响0人  Chriszzzz

一、什么是 base64

Base64 是网络上最常见的用于传输 8Bit 字节码的编码方式之一,是一种基于 64 个可打印字符(见下图👇)来表示 二进制数据 的方法。

标准base64编码对应表

也就是说,经过 base64 转换过后的二进制数据,其只由 65 个字符(上述 64个👆 + 末尾可能填充的”=“)组成。👇

Base64 是网络上最常见的用于传输8Bit字节码的编码方式之一,Base64就是一种基于64个可打印字符来表示二进制数据的方法。
.
== base64编码后 ==>
.
QmFzZTY0IOaYr+e9kee7nOS4iuacgOW4uOingeeahOeUqOS6juS8oOi+kzhCaXTlrZfoioLnoIHnmoTnvJbnoIHmlrnlvI/kuYvkuIDvvIxCYXNlNjTlsLHmmK/kuIDnp43ln7rkuo42NOS4quWPr+aJk+WNsOWtl+espuadpeihqOekuuS6jOi/m+WItuaVsOaNrueahOaWueazleOAgg==

对吧,找不到第 66 个字符。

但为什么会有 base64 这么奇怪的编码方式呢?

邮件传输协议(SMTP)是不支持不可见字符的传递的(不可见字符比如换行符、制表符等)。但是图片的数据所表示的字符中,存在大量的不可见字符,那么,邮件就不能传输图片么?显然不可接受。为了解决这个问题,大牛们提出了 base64 编码,传输时通过 base64 将任何二进制数据都用可见字符来表示。

如今,base64 也被应用在 X.509 公钥证书约定中,HTTP请求的参数编码上,XML结构中存储二进制数据的场景中。简而言之,基于数据传输、存储,屏蔽了大量的不可见字符(他们往往有特殊含义),传输,存储的可靠性大幅提升。

二、base64 的编码规则

base64是对二进制数据进行编码的!base64是对二进制数据进行编码的!base64是对二进制数据进行编码的! 此点不同于我们常见的 utf-8 编码(做「字符串」&「二进制」之间的转换),base64 做的是「二进制」 & 「能转化为可见字符的二进制」之间的转换。

简单来讲,base64 编码分为如下几步:
1)将二进制数据按照 3字节(24位)分组;
2)将每组数据分为 4份(每份6位)
3)每份数据 (6位能表示0~63) 查找 base64 编码对应表,找到对应字符;
4)将对应字符基于 ASCII 表示,最后纯空的份用 ’=‘ 替代。

三、base64 的编码举例

比如我们想对 ”今儿个真Happy!“ 的 utf8(你当然可以用其他的编码方式转换,毕竟 base64 只关心二进制输入)的二进制数据进行编码。

Step1:获取待编码的二进制数据

”今儿个真Happy!“,经过 utf-8 编码的二进制数据如下👇

今儿个真Happy!

== utf-8编码 ==>

十六进制表示:e4 bb 8a e5 84 bf e4 b8 aa e7 9c 9f 48 61 70 70 79 ef bc 81 
二进制表示:11100100 10111011 10001010 11100101 10000100 10111111 11100100 10111000 10101010 11100111 10011100 10011111 01001000 01100001 01110000 01110000 01111001 11101111 10111100 10000001

Step2:将二进制数据分组

接下来,我们需要将待编码的二进制数据按照 3字节 一组分组。因为 1字节8位base6464 个可见字符需要用 6位(2的6次方)表示,86 的最小公倍数是 24,即 3 个字节。

11100100 10111011 10001010 11100101 10000100 10111111 11100100 10111000 10101010 11100111 10011100 10011111 01001000 01100001 01110000 01110000 01111001 11101111 10111100 10000001

== 3字节一组 ==>

11100100 10111011 10001010 
11100101 10000100 10111111 
11100100 10111000 10101010 
11100111 10011100 10011111 
01001000 01100001 01110000 
01110000 01111001 11101111 
10111100 10000001 

但我们发现原始二进制的字节数无法被3整除,没关系,我们后面再处理。

Step3:每组做4等分

我们将每组数据进行 4 等分(每份 6 位),基于 6 位得到 0 ~ 63 的值,即为 base64 编码的索引。

11100100 10111011 10001010 
11100101 10000100 10111111 
11100100 10111000 10101010 
11100111 10011100 10011111 
01001000 01100001 01110000 
01110000 01111001 11101111 
10111100 10000001 

== 4等分 ==>

111001 001011 101110 001010 
111001 011000 010010 111111 
111001 001011 100010 101010 
111001 111001 110010 011111 
010010 000110 000101 110000 
011100 000111 100111 101111 
101111 001000 0001 

最后一行非纯空的数据补 0 补满 6 位,纯空数据当做 '=':
101111 001000 0001  ==> 101111 001000 000100 '='

== 每份二进制转为10进制 ==》

05 11 20 10 
05 24 18 63 
05 11 08 16 
05 05 24 05 
18 06 05 22 
02 07 13 21 
21 08 04 ’=‘

Step4:做Base64的字符映射

05 11 20 10 
05 24 18 63 
05 11 08 16 
05 05 24 05 
18 06 05 22 
02 07 13 21 
21 08 04 ’=‘

== base64 字符映射 ==>

5 L u K 
5 Y S /
5 L I q
5 5 y f
S G F w
c H n v
v I E = 

对应 [ASCII](https://baike.baidu.com/item/ASCII/309296?fr=aladdin) 的二进制数据即:

00110101 01001100 01110101 01001011 
00110101 01011001 01010011 00101111 
00110101 01001100 01101001 01110001 
00110101 00110101 01111001 01100110 
01010011 01000111 01000110 01110111 
01100011 01001000 01101110 01110110 
01110110 01001001 01000101 00111101 

这即为base64完成编码后在内存中的值👆

四、讨论

4.1 网上常见的base64算法过程的补两个0问题

网上很多关于 base64 的说明教程(包括百度百科)都描述了 3字节(3x8位)4x6位 后,在6位 数据前 补两个’0‘凑4组8位数据 的问题。而这个步骤存在的意义实质上在于方便 base64 标准代码实现的理解。在此,我们不分析 base64 编码的代码实现,所以小编未将该步骤加入其中。

百度百科的组转换示例

4.2 base64的索引存在与设计层,其存储层使用的是字符编码。

第二个很让人困惑的问题就是 base64 转换后的二进制数据,如果你将 base64 转换的二进制数据打印出来,会发现每个字节的数据并不仅仅在 0 ~ 63 这个范围内。

这是因为,base64 的实际存储形式并非其索引值……

ABCD base64的映射

比如一份转化后的 base64 数据映射到 ABCD,则其二进制表示并不是0x01020304,而是 ABCD 对应ASCII标中的值:0x41424344

ABCD ASCII的映射

base64 的设计是不关心转换的 base64 字符是如何存储的。但实际应用中,ASCII,或讲完全兼容 ASCIIutf-8 实在是太通用了,所以我们直接将 base64 与其结合,其实无可厚非。无论是苹果官方的 base64 编码,还是大家用得比较多的也比较老牌的 GTMBase64,都是这么做的。

iOS自带base64编码接口说明

4.3 base64的变种

即便 base64 只使用了除等号 ”=“ 之外的64个字符,但它还是不出意外地会在各种场景下出问题……关键在于 62号 字符 "+",和 63号 字符 "/",它们在某些场景下是有特殊含义的。所以,我们也会在必要的场景将 ”+“”/“ 两个字符替换,组成变种的 base64 编码,比如:

变种1:URL变种:"+","/","=" --> "-","_",""

URL编码器会把标准 Base64 中的 “/”“+” 字符变为形如 “%XX” 的形式,而这些 “%” 号在存入数据库时还需要再进行转换,因为 ANSI SQL 中已将 “%” 号用作通配符。为解决此问题,可采用一种用于 URL 的改进 Base64 编码,它不仅在末尾去掉填充的 '=' 号,并将标准 Base64 中的 “+”“/” 分别改成了 “-”“_”,这样就免去了在 URL编解码和数据库存储时所要作的转换,避免了编码信息长度在此过程中的增加,并统一了数据库、表单等处对象标识符的格式。

变种2:正则变种:"+","/" --> "!","-"
另有一种用于正则表达式的改进 Base64 变种,它将 “+”“/” 改成了 “!”“-”,因为 “+” , “/”在正则表达式中具有特殊含义。

五、实践

用一行代码来打印 base64 的编码流程吧~

/* 代码示例 */
cytLogStringData_base64_flow(@"今儿个真Happy!");
/* 打印流程 */
1 输入字符串: 
今儿个真Happy!

2 将字符串转为数据(通过UTF-8)编码:
16进制数据表示: e4 bb 8a e5 84 bf e4 b8 aa e7 9c 9f 48 61 70 70 79 ef bc 81 
2进制数据表示: 11100100 10111011 10001010 11100101 10000100 10111111 11100100 10111000 10101010 11100111 10011100 10011111 01001000 01100001 01110000 01110000 01111001 11101111 10111100 10000001 

3 将2进制数据进行3字节分组:
11100100 10111011 10001010 
11100101 10000100 10111111 
11100100 10111000 10101010 
11100111 10011100 10011111 
01001000 01100001 01110000 
01110000 01111001 11101111 
10111100 10000001 

4 将2进制数据每3个字节分为4组:
111001 001011 101110 001010 
111001 011000 010010 111111 
111001 001011 100010 101010 
111001 111001 110010 011111 
010010 000110 000101 110000 
011100 000111 100111 101111 
101111 001000 0001

5 获取base64编码的索引值:
57 11 46 10 57 24 18 63 57 11 34 42 57 57 50 31 18 06 05 48 28 07 39 47 47 08 04 00 

6 将索引映射为base64目标字符:
5LuK5YS/5Liq55yfSGFwcHnvvIE=

7 base64字符串内存中的样子(对应字符的ASCII值,或说utf-8编码值):
00110101 01001100 01110101 01001011 00110101 01011001 01010011 00101111 00110101 01001100 01101001 01110001 00110101 00110101 01111001 01100110 01010011 01000111 01000110 01110111 01100011 01001000 01101110 01110110 01110110 01001001 01000101 00111101 

git地址:https://github.com/chrisYooh/CYEncoding.git
对你有帮助的话记得帮小编点个 「Star」 哦!✨✨✨

上一篇下一篇

猜你喜欢

热点阅读