编码原理理解之「 base64」
一、什么是 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位,base64 含 64 个可见字符需要用 6位(2的6次方)表示,8 和 6 的最小公倍数是 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,或讲完全兼容 ASCII 的 utf-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」 哦!✨✨✨