Base64常常在我们开发中出现,我们可能只知道它是一串乱码(看不懂的符号),知道有时候知道我们需要把一些数据进行base64编码,有时候需要进行Base64解码。但是却不知道它的作用是什么?它存在的意义是什么?还有它是如何生成的?如何编码和解码?
上述的疑问相信在你读完本篇文章之后烟消云散。
下面开始正文。
Base64是什么?
它和加密解密操作有什么关系吗?
让我们先来看看以下这段看似诡异的乱码:
54K55Liq5YWz5rOo77yM5piv5a+55oiR5oyB57ut5Yib5L2c55qE5pyA5aSn55qE6byT5Yqx77yB
没错,这就是经过Base64编码后的字符串。对它解码后,我们获得以下内容
点个关注,是对我持续创作的最大的鼓励!
这段文字在经过Base64编码后面目全非,而经过Base64解码后又能恢复本来面目,这很有加密解密的意味。不过Base64算法并不是加密算法,仅仅是加密算法的近亲。Base64算法的转换方式很像古典加密算法中的单表置换算法。下面来介绍这种常用于电子邮件的算法。
Base64算法最早应用于解决电子邮件传输的问题。在早期,由于 “历史问题” 电子邮件只允许ASCII码字符。如果要传输一封带有非ASCII码字符的电子邮件,当它通过有 “历史问题” 的网关时就可能出现问题。这个网关很可能会对这个非ASCII码字符的二进制位做调整,即将这个非ASCII码的8位二进制码的最高位置为0。此时用户收到的邮件就会是一封纯粹的乱码邮件了。基于这个原因产生了Base64算法。
对ASCII码概念感觉模糊的同学可以去看看这篇文章:一文看懂所有字符编码标准
Base64是一种基于64个 字符的编码算法,根据RFC2045的定义:“Base64内容传送编码是一种以任意8位字节序列组合的描述形式,这种形式不易被人直接识别”。
经过Base64编码后的数据会比原始数据略长,为原来的4/3倍。
经Base64编码后的字符串的字符数是以4为单位的整数倍。
RFC2045还规定,在电子邮件中,每行为76个字符,每行末需添加一个回车换行符(“\r\n”),不论每行是否够76个字符,都要添加一个回车换行符。但在实际应用中,往往根据实际需要忽略了这一要求。
在RFC2045文件中给出了如下字符映射表:
这张字符映射表中,Value指的是十进制编码,Encoding指的是字符,共映射了64个字符,这也是Base64算法命名的由来。映射表的最后一个字符是等号,它是用来补位的。有经验的朋友一看到字符串末尾有个等号,就会联想到Base64算法了。
其实,Base64算法还有几个同胞兄弟,如Base32和Base16算法。为了能在http请求中以Get方式传递二进制数据,由Base64算法衍生出了Url Base64算法。
Url Base64算法主要是替换了Base64 字符映射表中的第62和63个字符,也就是将“+”和“/”符号替换成了“-”和“_”符号。但对于补位符“=”,一种建议是使用"~ "符号,另一种建议是使用“.”符号。其中,由于“~”符号与文件系统冲突,不建议使用。
而对于“.”符号,如果出现连续两次,则认为是错误。对于补位符的问题,Bouncy Castle和Commons Codec有差别,Bouncy Castle使用"."作为补位符,而Commons Codec则完全杜绝使用补位符。
有关Base16、Base32和Url Base64算法的详细内容,可以参考RFC4648 。
Base64算法有编码和解码操作可充当加密和解密操作,还有一张字符映射表充当了密钥。
单表置换算法,Base64算法正是运用了这一思想,将原文经二进制转换后与字符映射表相对应,得到“密文”。Base64算法经常用做一个简单的“加密”来保护某些数据。
尽管如此,Base64算法仍不能叫做加密算法。Base64算法公开,这一点与柯克霍夫原则并无违背,但充当密钥的字符映射表公开,直接违反了柯克霍夫原则,而且Base64算法的加密强度并不够高。因此,不能将Base64算法看做我们所认可的现代加密算法。
Base64算法虽不能称为加密算法,但其变换法则遵从了单表置换算法。也正因如此,Base64算法成为加密算法学习最好的范例。尤其是在自定义加密算法研制方面,Base64算法是一个很不错的参考。如果我们将Base64算法做少许改造,并将字符映射表调整、保密,改造后的Base64算法就具备了加密算法的意义!除此之外,Base64算法常作为密钥、密文和证书的一种通用存储编码格式,与加密算法形影不离。
Base64算法主要是将给定的字符以与字符编码(如ASCII码,UTF-8码)对应的十进制数为基准,做编码操作:
1)将给定的字符串以字符为单位,转换为对应的字符编码 (如ASCII码)
2)将获得的字符编码转换为二进制码
3)对获得的二进制码做分组转换操作,每3个8位二进制码为一组,转换为每4个6位二进制码为一组(不足6位时低位补0) 。这是一个分组变化的过程,3个8位二进制码和4个6位二进制码的长度都是24位(3x8=4x6=24)
4)对获得的4-6二进制码补位,向6位二进制码添加2位高位0,组成4个8位二进制码。
5)将获得的4-8二进制码转换为十进制码
6)将获得的十进制码转换为Base64字符表中对应的字符
我们对字符串“A”进行Base64编码:
字符 A
ASCII码 65
二进制码 01000001
4-6二进制码 010000 010000
4-8二进制码 00010000 00010000
十进制码 16 16
字符表映射码 Q Q = =
由此,字符串“A”经过Base64编码后就得到了“QQ==”这样一个字符串。
Base64解码操作就是编码操作的逆运算,反推上述流程很容易获得原文信息。
"A"经过Base64编码后的字符串末尾带着2个等号。很显然,当原文的二进制码长度不足24位,最终转换为十进制码时也不足4项,这时就需要用等号补位。如果原文只有一个字符,那么经过Base64编码后的字符串末尾会有2个等号。
经Base64编码后的字符串最多只会有2个等号,这是因为:
余数 = 原文字节数 MOD 3
这是一个简单的算术问题,余数的值只可能是0、1或2。
余数为0时,则原文字节数恰好是3的倍数,没有等号。
这个尾巴;余数为1时,则为了让Base64编码后的字符数是4的倍数,要补2个等号。
同理,余数为2时,则要补1个等号。
所以,通常判别一个字符串是不是Base64编码的第一步操作就是判断这个字符串末尾是不是有等号。
这同时也说明,Base64编码后的字符串是以4个字符为单位,其长度只能是4个字符的整数倍。
本文开篇对“点个关注,是对我持续创作的最大的鼓励!”这个字符串做了Base64编码后获 得了一个长度为 76个字符的字符串“54K55Liq5YWz5rOo77yM5piv5a+55oiR5oyB57ut5Yib5L2c55qE5pyA5aSn55qE6byT5Yqx77yB”,正好说明了这一点。
Base64算法很好地解决了非ASCII码字符的传输问题,譬如中文字符的传输问题。
ASCII码可以表示十进制范围为0~ 127的字符,对应二进制范围是00000000~01111111。ASCII码包括阿拉伯数字、大小写英文字母和一些控制符,但却没有包含双字节编码的字符,如中文字符。因此有了GB2312、GBK和UTF-8等编码。GB2312、GBK用2个字节表示一个汉字,UTF-8编码则用3个字节表示一个汉字。Base64算法实现时,对6位二进制码添加高2位0,恰恰保护了非ASCII码字符在通过有问题的网关时不发生问题。
我们以字符串“密”为例,字符串“密“对应的UTF-8编 码就是-27,-81,-122。我们对其做如下Base64编码:
字符 密
UTF-8码 -27 -81 -122
二进制码 11100101 10101111 10000110
4-6二进制码 111001 011010 111110 000110
4-8二进制码 00111001 00011010 00111110 00000110
十进制码 57 26 62 6
字符表映射码 5 a + G
字符串“密”经过Base64编码后得到字符串“5a+G”。
我们通过时序图来描述一次基于Base64算法的消息传递。这里,我们仍以甲方与乙方交互数据举例。甲方作为数据的发送方,乙方作为数据的接收方。其操作流程如下:
1)甲方对数据做Base64编码处理
2)甲方将编码后的数据发送给乙方
3)乙方获得数据后对数据做Base64解码处理
要操作Base64算法并不复杂。甲乙双方可以通过上述流程完成一次数据通信,或是单方面地进行电子邮件发送,亦或是仅仅为了传输非ASCII码的字符信息等。
这些操作在实际应用中常常是通过Base64算法来完成的。
Base64算法广泛应用于电子邮件传输,以及密钥和证书文件的文本方式保存。在数据保密要求强度不高的情况下,可以使用Base64算法做简单的数据“加密”。
电子邮件一般都会使用Base64算法,我们截取一段电子邮件信息:
Content-Type: text/plain;
charset="utf8"
Content-Transfer-Encoding: base64
6L+Z5piv5LiA5bCB5rWL6K+V6YKu5Lu277yB
在这封邮件里,我们看到了我们需要的信息
通过上述信息,我们确定无疑这是一封使用了Base64编码的邮件,并且邮件的内容使用的是UTF-8编码的字符集。由此,我们只需要通过上述Base64算法的实现类一Base64Coder完成解码操作。毋庸置疑,这份邮件的内容是:“这是一封测试邮件!“
不论是通过HTTP的Get方式以URL参数传输数据,还是通过Post方式以数据体传输数据,都能发现Base64编码藏匿其中
只要通信双方在通信前约定好字符编码和字符映射表,改良后的Base64算法就可以在HTTP环境中以Get/Post方式传递二进制数据。这时的Base64算法,无疑成为了一种简单的加密算法。
在计算机的世界里,密钥就是一段二进制的数据。
密钥的二进制表现形式使得它的安全性大为提升,但却大大降低了它的可读性。通常我们希望密钥可以有一个容易理解的表现形式(如经过Base64编码后的字符串),以增强它的可读性并且方便密钥的发放。如甲方向乙方发送密钥,可以将密钥以二进制形式转化为Base64编码后的字符串形式,通过安全途径以文档形式发放给乙方,密钥很可能被要求写在合同中。
Base64算法的优势恰恰是在基于二进制编码格式的转换上,这一点使得二进制的密钥通常以Base64编码的形式展现。
另一种类似的效果是将二进制串转为十六讲制串,如消息摘要结果。以下是一个经过Base64编码处理后DES的密钥:
I6ttWOmtSo8=
这样处理后的密钥看起来就比较容易接受,可以将其放到合同书中,发放给其他人。
数字证书可以使用多种格式存储,常见的包括:
.pem
、.crt
、.cer
、.key
等扩展名结尾。PEM 格式证书通常如下:
-----BEGIN CERTIFICATE----- MIIFqzCCBJOgAwIBAgIQAf2jJAY75k6nqYDL5OwZpzANBgkqhkiG9w0BAQwFADB5 [...] u+Dvi9IJOZd/21Bbsb/6bER5epqCyl/wI -----END CERTIFICATE-----
DER 格式:
DER(Distinguished Encoding Rules)格式是二进制编码的 X.509 证书格式,通常以.der
、.cer
、.crt
等扩展名结尾。
PKCS#12 格式(PFX 或 P12):
PKCS#12 是一种可包含公钥、私钥和证书链等信息的存储格式。通常以.p12
、.pfx
等扩展名结尾。
JKS 格式:
JKS(Java KeyStore)是Java中常见的密钥存储格式,可以用于存储证书和密钥。通常以.jks
扩展名结尾。
PKCS#7 / P7B 格式:
PKCS#7 或 P7B 格式用于存储证书链,通常以.p7b
、.p7c
等扩展名结尾。
CAdES 格式:
CAdES(CMS Advanced Electronic Signatures)是用于电子签名的一种格式,扩展了 CMS(Cryptographic Message Syntax)。通常以.p7m
、.p7s
等扩展名结尾。
《Java加密与解密艺术》