base64编码
公司技术一起开会,做任何项目都经常会讨论编码的问题。在这种时候,你就算装不了逼,也不能一脸懵逼呀。编码技术太多,但是常用的基础的必须了解,今天就详细讲解一下base64编码,包括base64的坑:url中使用base64编码的注意事项,不同编码的base64解码之后的内容却是一样的等问题,这些都了解了,base64就能真正成为你自己的技术了。
备注:首先base64就是编码,这不是压缩,编码是会增加字节数的,同时base64算法可逆,不能用于加密,虽然他也叫加密算法,但是我觉得叫base64编码更贴切。
优势:
- 算法简单,几乎不影响效率。
- 算法可逆,解码方便。
- 加密后的字符串只有[0-9a-zA-Z+/=], 不可打印字符(包括转移字符)也可传输。
- 跨语言(java,php,python...),跨平台(windows,mac,linux,unix....),跨编码(utf8,gbk...),这也是其最重要的优势
为什么说算法简单,base64总共只有[0-9a-zA-Z+/=],0-63共64个编码字符,就是说所有被编码的字符都会通过下边这张遍进行编码,选择二进制进行转换,我觉着主要是因为计算机底层就是二进制,用其他进制还得转换成二进制。为什么有的base64编码有=号呢,这么说有些人还是有些懵逼,没事下边就解释怎么编码:
下边顺便把不可打印字符、转移字符和打印字符截张图:
base64编码:
base64编码一个字节用6位二进制标示,一个字节是8位二进制,三个字节正好是3*8=24个位,可以生成4个base64字符。比如字符串为 "123",1 的 ASCII 为 49,转换为二进制就是 00110001,2 的 ASCII 为 50,转换为二进制就是 00110010,3 的 ASCII 为 51,转换为二进制就是 00110011,这三个二进制组合在一起就是这样:001100010011001000110011
上面的二进制位总共 24 位,每6位生成一个base64字符:
- 第一个001100,通过编码表查出是第12个:M
- 第二个010011,通过编码表查处是第19个:T
- 第三个001000,通过编码表查处是第8个:I
- 第四个110011,通过编码表查处是第51个:z
那么123编码之后就是:MTIz
此编码正好是6的倍数,如果不是这么巧呢,多出一个字符变成32位二进制了呢,或者少一个字符,变成16位二进制了呢,都无法整除6,那就需要补齐。
补齐:
如果该字符多一个字节,变成了"1234",成了4*8=32位,base64编码,6个一组,最多整好分成5组,还多2位,我们就给他补上4个0000,那字符"1234"原来的二进制为:001100010011001000110011,现在多了4个0:001100010011001000110011001101000000,现在变成36位了,然后在进行分割,查表得MTIzNA,但是为了后面的解码,我们需要在加密后的字符串末尾加上 2 个 “=”,就是 “MTIzNA==”。
如果剩下 2 个字节的话变成,字符变成了"12",2 个字节刚好 16 位,6 位一组的话,需要3*6=18位,也就是说,少了 2 位,这样就可以组合成 18 位了(3 个 Base64 字符),这里我们以字符串 “12” 为例,1 的 ASCII 转换为二进制是 00110001,2 的 ASCII 转换为二进制是 00110010,我们将它组合在一起然后补齐之后(加上 2 个 0),就是 001100010011001000,按照 6 位一组进行分割,然后查表求得,结果是 MTI,但是为了后面的解码,我们需要在加密后的字符串末尾加上 1 个 “=”,就是 “MTI=”。
从这里也就知道一个=表示两个00,解码的时候根据=相应的减掉多余的0就可以了。
跨编码:如汉字编码,不管是utf8下一个汉字3个字节还是gb2312下一个字符2个,都是按照上边的规则来进行编码的,所以可以跨编码。它不受其他编码的影响,仍然保持不变,这点很有意义,如下验证:
String a = "123412312sfwefwefwefw";
String b = new String(CodecManager.getCodecClient(CodecConstants.BASE64).encode(a.getBytes()));
System.out.println(b);
//对base64编码后的字符串,进行其他编码
String c1 = new String(b.getBytes(),"gbk");
System.out.println(c1);
String c2 = new String(b.getBytes(),"utf-8");
System.out.println(c2);
String c3 = new String(b.getBytes(),"ISO8859-1");
System.out.println(c3);
输出:
MTIzNDEyMzEyc2Z3ZWZ3ZWZ3ZWZ3
MTIzNDEyMzEyc2Z3ZWZ3ZWZ3ZWZ3
MTIzNDEyMzEyc2Z3ZWZ3ZWZ3ZWZ3
MTIzNDEyMzEyc2Z3ZWZ3ZWZ3ZWZ3
跨语言:
<?php
echo base64_encode('123412312sfwefwefwefw');
输出:MTIzNDEyMzEyc2Z3ZWZ3ZWZ3ZWZ3
下边再来个汉字的例子:将字符"我是A"这字符的ASCII 转换为二进制:utf8下,一个汉字为3个字节就是24位,两个就是48位,加上一个字节A是8位,总共是56位:11100110100010001001000111100110100110001010111101000001。6*10=60,补4个0000,可以自己在线搜索汉字转ASCII码:
最终6位分割对照表得到:5oiR5pivQQ==
base64解码:
解码就简单多了,比如刚才5oiR5pivQQ==这个,将这个字符根据对照表解码成二进制,然后去掉补齐4个0000,然后组合起来再去ASCII里边反编译(这里边就是一个字符8位了)出来就行了。
遇到的问题:
- 有的base64编码完了,通过url传递,后台接收到后,反编译会失败。是因为url中的+和/符号会被浏览器在传输的过程中处理成别的符号,造成解码失败。这个不是base64的问题,base64又不能预料到使用他的环境。解决方案:编码后,将+/转换成-*等其他不会被浏览器处理的符号,后台接受后,先将处理过的特殊符号转回来再解码。
- 不同编码的字符,反编码之后内容相同:如已编码字符:'ZEVWSGRGWldlbEIwVm5WRn========'和'ZEVWSGRGWldlbEIwVm5WRn'通过上边的学习,我们知道你后边加多少=,都是无用的,补齐的数据,反编译的时候会去掉。还有如:'ZEVWSGRGWldlbEIwVm5WRn'和'ZEVWSGRGWldlbEIwVm5WRg'的解码结果相同都是:dEVHdFZWelB0VnVF ,这是因为Rg, Rh, Ri, ...通通表示字母F。虽然编码后的结果不同,但不影响解码后的结果。
以上就是我总结的base64编码问题。虽说不是很难,也是整理了一上午。