详细了解base64编码和解码

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位了)出来就行了。

遇到的问题:

  1. 有的base64编码完了,通过url传递,后台接收到后,反编译会失败。是因为url中的+和/符号会被浏览器在传输的过程中处理成别的符号,造成解码失败。这个不是base64的问题,base64又不能预料到使用他的环境。解决方案:编码后,将+/转换成-*等其他不会被浏览器处理的符号,后台接受后,先将处理过的特殊符号转回来再解码。
  2. 不同编码的字符,反编码之后内容相同:如已编码字符:'ZEVWSGRGWldlbEIwVm5WRn========'和'ZEVWSGRGWldlbEIwVm5WRn'通过上边的学习,我们知道你后边加多少=,都是无用的,补齐的数据,反编译的时候会去掉。还有如:'ZEVWSGRGWldlbEIwVm5WRn'和'ZEVWSGRGWldlbEIwVm5WRg'的解码结果相同都是:dEVHdFZWelB0VnVF ,这是因为Rg, Rh, Ri, ...通通表示字母F。虽然编码后的结果不同,但不影响解码后的结果。

以上就是我总结的base64编码问题。虽说不是很难,也是整理了一上午。

借鉴的博客:https://learnku.com/articles/36655

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章