使用场景
我现在工作的某银行手机银行APP项目在进行某些交易时,交易报文是加密的,加密方式为业内常见的AES加RSA双重加密。加解密过程为:使用AES密钥对实际报文进行加密,同时再将AES密钥使用RSA公钥进行加密,这样接收方在收到报文后,先用RSA私钥对AES密钥进行解密,再用解密后的AES密钥解密实际报文。
AES秘钥在经过RSA加密后,生成的是256位的16进制数,也就是1024位的二进制数。这1024位的二进制数每8位组成一个字节,最后得到的就是一个长度为128的byte数组(byteArray)。由于互联网传输使用的HTTP/HTTPS协议是“文本传输协议”,也就是只能传输“文本”内容,所以在实际传输时,需要使用一种方式将byteArray转换成可见字符,以使用HTTP/HTTPS协议进行传输。
但是byteArray不是charArray,并不能直接toString转化为字符串。尽管“一个字节长度”很容易让人联想到ASCII编码,但是byte不能简单粗暴的直接转换成ASCII字符,因为ASCII编码中,有一些字符是无法显示的“控制字符”。如果在网络传输时直接传输“010101”这样的字符串,固然简单方便,但是一个0或1就需要一个字节来表示,这无疑是对带宽的巨大浪费。为了有效率地将byteArray转换成可读文本,Base64编码就出场了。注意,Base64是一种编码方式,“Base64加密”的说法是错误的,Base64根本就没有加密功能。
原理说明
引用百科的说法:Base64是网络上最常见的用于传输8Bit字节码的编码方式之一,Base64就是一种基于64个可打印字符来表示二进制数据的方法。
这个博客:Base64编码原理与应用 对Base64有详细的解释。(图片也是抄自这个博客)
简单描述Base64编码的思路是这样的:
8位二进制数一共可以表示256个不同的字符/数字,反过来说,需要使用256个不同的字符/数字才能完整地表示一个8位的二进制数。但是英文字母只有26个,加上大小写也只有52个,根本凑不齐256个这么多。所以Base64编码换了一种方式——先把3个相邻的字节,也就是3X8=24位二进制数排成一列,再按照每6个相邻的二进制数一组的方式分成4组。每一组6位二进制数可以表示64个不同的字符,52个大小写字母,1~9共10个数字,再加上 + 和 / 两个符号,凑够了64个字符,让它们与64个数一一对应,这样就把3个字节的byte用4个Base64字符表示出来了。
Base64有很多变种,差别在于最后两个符号,比如Base64 RUL 就是用了 - 和 _ 替换了 + 和 / 这两个字符,因为替换后的这两个字符在URL链接中不会被错误的转码
理论上Base64可以编码任意内容,因为不论是ASCII字符还是图片,都是由一个个字节组成的,而一个字节就是8位二进制数,所以每3个字节就可以转化成4个Base64字符。
当源数据的字节个数不能被3整除时,最后会剩下1个或2个字节,这时就可以在后面添加2个或1个全0字节,补全24个二进制位。在Base64中,6个二进制0表示大写的A,为了把补的0和正常数据中的A区别开来,在结尾处从补的0转化成的A用等号=表示。有几个A就替换成几个=。思考一下就能发现,Base64结尾处只可能补1个或2个等号,如果有2个等号,也可以只写一个等号。原因也很简单,因为Base64编码后得到的字符串的字符个数一定是4的倍数,如果发现不能整除,那肯定是因为省略了1个等号的缘故。
由于Base64字符串有可能要拼起来传输,在首尾相接的情况下,必须要在有一个符号来区分两串字符串,所以在很多情况下,会看到一些Base64工具类编码得到的字符串即使源数据字节数能被3整除,在得到的Base64编码字符串后面都有一个等号=,这就是工具类自己加上的用以表示字符串结尾的等号。在apache.commons.codec包下的Base64类和JDK1.8新增的java.uitl包下的Base64类都是这样的输出方式。
顺便一提,在使用Base64工具类的时候,我遇到了一个小坑:
1.Java.util包下的Base64工具类是jdk1.8新加的,我之前eclipse里默认的jdk版本是1.8,在本地开发的时候想当然的优先使用了Java提供的工具类,也没有想过Base64这个工具类会这么新。在代码提交后编译报错,才发现由于项目运行的jdk是1.7版本,在1.7中没有这个类,自然编译报错。最后不得不返工修改代码,弃用了JDK自带的Base64工具类。
2.项目中原来的代码有使用sun.misc.BASE64Encoder工具类,我在发现项目jdk版本问题后,最先想到的修复方式就是改用这个类进行Base64编码,但是在修改代码后发现原本使用Java.util.Base64工具类能正常运行的代码报错了,debug之后发现编码得到的Base64字符串每76个字符会加入一个换行符,又经过了一番百度才知道,原来不同厂家的Base64工具类的实现遵照的可能是不同的标准,sun公司遵照的标准就是过长的Base64字符串进行换行,确保每一行的字符不超过76个。
具体解释在这个博客:升级JDK8的坑----base64,来龙去脉写得非常清楚