数据结构与算法之深入解析Base64编码的实现原理

Posted Forever_wj

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构与算法之深入解析Base64编码的实现原理相关的知识,希望对你有一定的参考价值。

一、Base64 编码简介

① Base64 编码的由来

  • 为什么会有 Base64 编码呢?因为有些网络传送渠道并不支持所有的字节,例如传统的邮件只支持可见字符的传送,像 ASCII 码的控制字符就不能通过邮件传送。这样用途就受到了很大的限制,比如图片二进制流的每个字节不可能全部是可见字符,所以就无法传送。
  • 最好的方法就是在不改变传统协议的情况下,做一种扩展方案来支持二进制文件的传送,把不可打印的字符也能用可打印字符来表示,问题就可以解决,Base64 编码便因此应运而生。

② 什么是 Base64 编码?

  • Base64 是网络上最常见的用于传输 8Bit 字节码的编码方式之一,Base64 就是一种基于 64 个可打印字符来表示二进制数据的方法。
  • Base64 编码是从二进制到字符的过程,可用于在 HTTP 环境下传递较长的标识信息,采用 Base64 编码具有不可读性,需要解码后才能阅读。
  • Base64 由于以上优点被广泛应用于计算机的各个领域,然而由于输出内容中包括两个以上“符号类”字符(+, /, =),不同的应用场景又分别研制了 Base64 的各种“变种”。为统一和规范化 Base64 的输出,Base62x 被视为无符号化的改进版本。
  • 标准的 Base64 并不适合直接放在 URL 里传输,因为 URL 编码器会把标准 Base64 中的“/”和“+”字符变为形如“%XX”的形式,而这些“%”号在存入数据库时还需要再进行转换,因为 ANSI SQL 中已将“%”号用作通配符。
  • 为解决此问题,可采用一种用于 URL 的改进 Base64 编码,它在末尾填充“=”号,并将标准 Base64 中的“+”和“/”分别改成了“-”和“_”,这样就免去了在 URL 编解码和数据库存储时所要作的转换,避免了编码信息长度在此过程中的增加,并统一了数据库、表单等处对象标识符的格式。
  • 另有一种用于正则表达式的改进 Base64 变种,它将“+”和“/”改成了“!”和“-”,因为“+”,“*”以及前面在 IRCu 中用到的“[”和“]”在正则表达式中都可能具有特殊含义。
  • 此外还有一些变种,它们将“+/”改为“-”或“.”(用作编程语言中的标识符名称)或“.-”(用于 XML 中的 Nmtoken)甚至“_:”(用于 XML 中的 Name)。
  • Base64 要求把每三个 8Bit 的字节转换为四个 6Bit 的字节(38 = 46 = 24),然后把 6Bit 再添两位高位 0,组成四个 8Bit 的字节,也就是说,转换后的字符串理论上将要比原来的长 1/3。
  • 关于编码的规则:
    • 把 3 个字节变成 4 个字节;
    • 每 76 个字符加一个换行符;
    • 最后的结束符也要处理。

二、Base64 编码的原理

  • 看一下 Base64 的索引表,字符选用了 A-Z、a-z、0-9、+、/ 64 个可打印字符,数值代表字符的索引,这个是标准 Base64 协议规定的,不能更改。

  • 64 个字符用 6 个 bit 位就可以全部表示,一个字节有 8 个 bit 位,剩下两个 bit 就浪费掉了,这样就不得不牺牲一部分空间了。这里需要弄明白的就是一个 Base64 字符是 8 个 bit,但是有效部分只有右边的 6 个 bit,左边两个永远是 0。
  • 那么怎么用 6 个有效 bit 来表示传统字符的 8 个 bit 呢?8 和 6 的最小公倍数是 24,也就是说 3 个传统字节可以由 4 个 Base64 字符来表示,保证有效位数是一样的,这样就多了 1/3 的字节数来弥补 Base64 只有 6 个有效 bit 的不足。也可以说用两个 Base64 字符也能表示一个传统字符,但是采用最小公倍数的方案其实是最能减少浪费的。
  • 如下图所示:Man 是三个字符,一共 24 个有效 bit,只好用 4 个 Base64 字符来凑齐 24 个有效位,红框表示的是对应的 Base64,6 个有效位转化成相应的索引值再对应 Base64 字符表,查出 “Man” 对应的 Base64 字符是 “TWFU”。

  • 说到这里有个原则不知道你发现了没有,要转换成 Base64 的最小单位就是三个字节,对一个字符串来说每次都是三个字节三个字节的转换,对应的是 Base64 的四个字节。
  • 如下所示,转换到最后会发现不够三个字节了,这怎么办呢?

  • 我们可以用两个 Base64 来表示一个字符或用三个 Base64 表示两个字符,像下图的 A 对应的第二个 Base64 的二进制位只有两个,把后边的四个补 0 即可,所以 A 对应的 Base64 字符就是 QQ,上文已经说过了,原则是 Base64 字符的最小单位是四个字符一组,那这才两个字符,后边补两个“=”吧。其实不用“=”也不耽误解码,之所以用“=”,可能是考虑到多段编码后的 Base64 字符串拼起来也不会引起混淆。
  • 由此可见 Base64 字符串只可能最后出现一个或两个“=”,中间是不可能出现“=”的,下图中字符“BC”的编码过程也是一样的。

  • 编码原理:将 3 个字节转换成 4 个字节 ((3 * 8) = 24 = (4 * 6)),先读入 3 个字节, 每读一个字节,左移8位,再右移四次,每次 6 位,这样就有 4 个字节。
  • 解码原理:将 4 个字节转换成 3 个字节,先读入 4 个 6 位(或运算),每次左移 6 位,再右移 3 次,每次 8 位,这样就可以还原。

三、Base64 编码的应用

  • Base64 编码可用于在 HTTP 环境下传递较长的标识信息。例如,在Java Persistence 系统 Hibernate中,就采用了 Base64 来将一个较长的一个标识符(一般为 128-bit 的 UUID)编码为一个字符串,用作 HTTP 表单和 HTTP GET URL 中的参数。在其他应用程序中,也常常需要把二进制数据编码为适合放在U RL(包括隐藏表单域)中的形式。此时,采用 Base64 编码不仅比较简短,同时也具有不可读性,即所编码的数据不会被人用肉眼所直接看到。
  • 然而,标准的 Base64 并不适合直接放在 URL 里传输,因为 URL 编码器会把标准 Base64 中的“/”和“+”字符变为形如“%XX”的形式,而这些“%”号在存入数据库时还需要再进行转换,因为 ANSI SQL 中已将“%”号用作通配符。
  • 为解决此问题,可采用一种用于 URL 的改进 Base64 编码,它不仅在末尾去掉填充的’='号,并将标准 Base64 中的“+”和“/”分别改成了“-”和“_”,这样就免去了在 URL 编解码和数据库存储时所要作的转换,避免了编码信息长度在此过程中的增加,并统一了数据库、表单等处对象标识符的格式。
  • 另有一种用于正则表达式的改进 Base64 变种,它将“+”和“/”改成了“!”和“-”,因为“+”,“/”以及前面在 IRCu 中用到的“[”和“]”在正则表达式中都可能具有特殊含义。
  • 此外还有一些变种,它们将“+/”改为“-”或“.”(用作编程语言中的标识符名称)或“.-”(用于 XML 中的 Nmtoken)甚至“_:”(用于 XML 中的 Name)。
  • 其它应用:
    • Mozilla Thunderbird 和 Evolution 用 Base64 来保密电子邮件密码;
    • Base64 也会经常用作一个简单的“加密”来保护某些数据,而真正的加密通常都比较繁琐;
    • 垃圾讯息传播者用 Base64 来避过反垃圾邮件工具,因为那些工具通常都不会翻译 Base64 的讯息;
    • 在 LDIF 档案,Base64 用作编码字串。
  • ios 中的 Base64:
	// 字符串 base64编码
	// UTF8字符串——data——base64编码字符串
	NSString *target = @"Hello!";
	NSData *data = [target dataUsingEncoding:NSUTF8StringEncoding];
	NSString *base64Str = [data base64EncodedStringWithOptions:0];
	
	// UTF8字符串——data——base64、UTF8编码格式的data——base64编码字符串
	NSData *base64Data = [NSData base64EncodedDataWithOptions:0];
	NSString *ret = [[NSString alloc] initWithData:base64Data encoding:NSUTF8StringEncoding];
	
	#pragma mark -- 关于base64EncodedDataWithOptions方法生成的Base-64, UTF-8 encoded NSData
	/* Create a Base-64, UTF-8 encoded NSData from the receiver's contents using the given options.
	*/
	- (NSData *)base64EncodedDataWithOptions:(NSDataBase64EncodingOptions)options API_AVAILABLE(macos(10.9), ios(7.0), watchos(2.0), tvos(9.0));
	/*从方法说明中可以看到,生成的是base64、UTF8编码的字节数组。
	比如:@“hello!” 转成NSData对象
	  对应的字节数组是 <48656c6c 6f21>(十六进制)
	进行base64编码
	  按照上面的过程,结果字节数组是<18 06 21 44 27 06 60 33>(十进制表示,方便查Base64编码表),对应的字符串是SGVsbG8h
	  而使用上面的iOS api,得到的字节数组是 <83 71 86 115 98 71 56 104>(十进制表示,方便查UTF8编码表),对应的字符串是SGVsbG8h
	 */
  • Java :
	import java.util.Base64;
	对于标准的Base64:
	加密为字符串使用Base64.getEncoder().encodeToString();
	加密为字节数组使用Base64.getEncoder().encode();
	解密使用Base64.getDecoder().decode();
	对于URL安全或MIME的Base64,只需将上述getEncoder()getDecoder()更换为getUrlEncoder()getUrlDecoder()getMimeEncoder()getMimeDecoder()即可

四、总结

  • 说起 Base64 编码可能有些奇怪,因为大多数的编码都是由字符转化成二进制的过程,而从二进制转成字符的过程称为解码,而 Base64 的概念就恰好反,由二进制转到字符称为编码,由字符到二进制称为解码。
  • Base64 编码主要用在传输、存储、表示二进制等领域,还可以用来加密,但是这种加密比较简单,只是一眼看上去不知道什么内容罢了,当然也可以对 Base64 的字符序列进行定制来进行加密。
  • Base64 编码是从二进制到字符的过程,像一些中文字符用不同的编码转为二进制时,产生的二进制是不一样的,所以最终产生的 Base64 字符也不一样。例如“上网”对应 utf-8 格式的 Base64 编码是 “5LiK572R”,对应 GB2312 格式的 Base64 编码是 “yc/N+A==”。

以上是关于数据结构与算法之深入解析Base64编码的实现原理的主要内容,如果未能解决你的问题,请参考以下文章

Base64算法原理及实现

数据结构与算法之深入解析“贪心算法“的原理解析和算法实现

密码学Base64 编码 ( Base64 简介 | Base64 编码原理 | 最后编码组字节不足时补位 ‘=‘ 符号 | Base64 编码实现参考 )

数据结构与算法之深入解析RSA加密算法的实现原理

数据结构与算法之深入解析KMP算法的核心原理和实战演练

数据结构与算法之深入解析“UTF-8编码验证”的求解思路与算法示例