Base64编码详细介绍 附视频讲解

视频资料

在线视频讲解评论获取

图片[1]-Base64编码详细介绍 附视频讲解-FancyPig's blog

基本概念

 Base64这个术语最初是在“MIME内容传输编码规范”中提出的。Base64不是一种加密算法,虽然编码后的字符串看起来有点加密的赶脚。它实际上是一种“二进制到文本”的编码方法,它能够将给定的任意二进制数据转换(映射)为ASCII字符串的形式,以便在只支持文本的环境中也能够顺利地传输二进制数据。例如支持MIME的电子邮件应用,或需要在XML中存储复杂数据(例如图片)时。 

要实现Base64,首先需要选取适当的64个字符组成字符集。一条通用的原则是从某种常用字符集中选取64个可打印字符,这样就能避免在传输过程中丢失数据(不可打印字符在传输过程中可能会被当做特殊字符处理,从而导致丢失)。例如,MIME的Base64实现选用了大写字母、小写字母和0~9的数字作为前62个字符。其他实现通常会沿用MIME的这种方式,而仅仅在最后2个字符上有所不同,例如UTF-7编码。  

一个例子

  下面这段文本:

 Man is distinguished, not only by his reason, but by this singular passion fromother animals, which is a lust of the mind, that by a perseverance of delightin the continued and indefatigable generation of knowledge, exceeds the shortvehemence of any carnal pleasure.

通过MIME Base64进行转换后就成为:

TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGludWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=

转换方法

 以例子开头的“Man”被转换为“TWFu”为例,我们来看看Base64基本的转换过程: 1. M、a和n的ASCII编码分别为01001101、01100001和01101110,合并后得到一个24位的二进制串0100110101100001011011102. 按每6位一组将其分为4组:010011、010110、000101、1011103. 最后按对应关系从字符集中取出4个字符(即T、W、F、u)作为结果(本文后面列出了由MIME定义的字符集)。 Base64的基本思想就是这么简单:它将每3个字节(24位)转换为4个字符。因为6位二进制数可以表示64个不同的数,因此只要确定了字符集(含64个字符),并为其中的每个字符确定一个唯一的编码,就可以通过正向与反向映射将二进制字节转换为Base64编码或反之。 

补零处理

 通过不断将每3个字节转换为4个Base64字符之后,最后可能会出现以下3种情况之一: 

1. 没有字节剩下

2. 还剩下1个字节

3. 还剩下2个字节 

1没什么好说的。后面的2和3该如何处理呢? 遇到这种情况,就需要在剩下的字节后面补零,直到其位数能够被6整除(因为Base64是对每6位进行编码的)。假如还剩下1个字节,即8位,那么需要再补4个0使其成为12位,这样就可以分为2组了;如果剩下2个字节,即16位,那么只需要再补2个0(18位)就可以分成3组了。最后再用普通方法做映射即可。 

图片[2]-Base64编码详细介绍 附视频讲解-FancyPig's blog

 还原时,依次将每4个字符还原成3个字节,最后会出现3种情况之一: 

1. 没有字符剩下

2. 还剩下2个字符

3. 还剩下3个字符 

这3种情况与上面的3种情况一一对应,只要对补零的过程反过来处理,就可以原样还原了。 

填充

 我们经常会在Base64编码字符串中看到最后有“=”字符,这就是通过填充生成的。填充就是当出现编码时的情况2和3时,在后面补上“=”字符,使编码后的字符数为4的倍数。 所以我们可以很容易地想到,情况2,即还剩下1个字节时,需要补2个“=”,因为此时最后一个字节编码为2个字符,补上2个“=”正好凑够4个。情况3同理,需要补1个“=”。 

图片[3]-Base64编码详细介绍 附视频讲解-FancyPig's blog

 填充不是必须的,因为无需填充也可以通过编码后的内容计算出缺失的字节。所以在一些实现中填充是必须的,有些却不是。一种必须使用填充的场合是当需要将多个Base64编码文件合并为一个文件的时候。 

实现(示例)

 下面是一个Base64字符集,它包含大写字母、小写字母和数字,以及“+”和“/”符号。 

编码字符 编码字符 编码字符 编码字符
0A16Q32g48w
1B17R33h49x
2C18S34i50y
3D19T35j51z
4E20U36k520
5F21V37l531
6G22W38m542
7H23X39n553
8I24Y40o564
9J25Z41p575
10K26a42q586
11L27b43r597
12M28c44s608
13N29d45t619
14O30e46u62+
15P31f47v63/

 利用这个字符集我们可以写一个简单的Base64实现(本文最后附有完整源代码): 下面这个encode()方法用来将Java字符串转换为字节数组(Base64操作的是字节),然后调用真正的encode()方法完成编码: 

public String encode(String inputStr, String charset, boolean padding)
        throws UnsupportedEncodingException {
    String encodeStr = null;

    byte[] bytes = inputStr.getBytes(charset);
    encodeStr = encode(bytes, padding);

    return encodeStr;
}

encode()方法的核心代码是:

for (int i = 0; i < groups; i++) {
    byte_1 = bytes[3*i]   & 0xFF;
    byte_2 = bytes[3*i+1] & 0xFF;
    byte_3 = bytes[3*i+2] & 0xFF;

    group_6bit_1 =  byte_1 >>> 2;
    group_6bit_2 = (byte_1 &  0x03) << 4 | byte_2 >>> 4;
    group_6bit_3 = (byte_2 &  0x0F) << 2 | byte_3 >>> 6;
    group_6bit_4 =  byte_3 &  0x3F;

    sb.append(CHARSET[group_6bit_1])
      .append(CHARSET[group_6bit_2])
      .append(CHARSET[group_6bit_3])
      .append(CHARSET[group_6bit_4]);
}

即将每3个字节转换为4个字符。 当然还需要判断最后是否还有剩余的字节,如果有要单独处理: 

if (tail == 1) {
    byte_1 = bytes[bytes.length-1] & 0xFF;

    group_6bit_1 =  byte_1 >>> 2;
    group_6bit_2 = (byte_1 &   0x03) << 4;

    sb.append(CHARSET[group_6bit_1])
      .append(CHARSET[group_6bit_2]);

    if (padding) {
        sb.append('=').append('=');
    }
} else if (tail == 2) {
    byte_1 = bytes[bytes.length-2] & 0xFF;
    byte_2 = bytes[bytes.length-1] & 0xFF;

    group_6bit_1 =  byte_1 >>> 2;
    group_6bit_2 = (byte_1 &   0x03) << 4 | byte_2 >>> 4;
    group_6bit_3 = (byte_2 &   0x0F) << 2;

    sb.append(CHARSET[group_6bit_1])
      .append(CHARSET[group_6bit_2])
      .append(CHARSET[group_6bit_3]);

    if (padding) {
        sb.append('=');
    }
}

decode过程是类似的,具体请自行查阅完整代码。

引申话题:利用Base64加密解密

 虽然本文的开头就已经提到过,Base64不是一种加密算法,但实际上我们确实可以利用Base64来加密数据。 我们都知道,加密就是将明文变为密文的过程。在这个过程中起关键作用的一是算法,二则是密钥。算法相当于制造工艺或加工过程,而密钥则是配方。制造工艺可以公开,但配方必须保密,否则人人都能生产云南白药了。 容易想到,Base64的配方就是字符集。选用的字符集不同,甚至只是改变一下字符集中字符的顺序(编号),相同的加工过程就会生成不同的Base64编码。

 例如,如果不告诉你编码时使用的字符集,你能知道下面的编码对应的原文是什么吗?

Eqm1DInN5r2tFrmlCInhxsHN5rGYwp3J/rSh/tmx1syhxr219oWBDLihHqm1zqih5sChxImB
FsHQwrGowtmx1ImF5r2Q8InR4oXQwo30woShApXJDpXp1s2l+oGUwrGowpmV8qWt4t
ih5ryhEqmUwoGd+tm1+tWV0Iml+pih5r2R1p2lEqWtxo2B1Imt1r2VCoXR5rGYwrGowqGZ
/tGB1pmt1Lih1umN1pWRDInR4pShDqmdCtihGpWx1rWV+oGUwrGowoWZZImNxs2Zxrih
ArmVxsHVCpSY=

既然利用Base64来加密和解密是完全可行的,为什么又说它不是一种加密算法呢?

 这是因为: 

1. 开发Base64的目的就不是为了加密,而是为了方便在文本环境中传输二进制数据 

2. 所以,与开发一个加密算法不同,安全性并不是Base64的目标,只是它的一个副产物。 实际上,Base64的安全性是非常差的,这就是在实际应用中不用它加密的原因。如果你对常用加密方法有所了解的话,你应该知道有一种古老的加密方法,称为“字符替换法”。即指定一个规则,将每个字符用其他字符替换,例如将a变为c、b变为d等,这样替换后生成的结果就是密文。解密时只需要反过来操作,将c变为a、将d变为b就可以了。用不同的替换规则加密,生成的密文也不同。 用Base64来加密实际上就相当于字符替换,只不过它先对字节做了一些变换,然后再进行替换,对加密过程来说,本质上是一样的。 字符替换法虽然简单,但却是一个伟大的发明,它被使用了超过1千年,一直都没有有效的方法来破解它。后来人们终于发现了它的弱点:基于词频和字母频率的统计规律,就能够轻松得到它的密钥。从那以后,加密者与解密者之间的战争从来就没有停歇过,加密者不断发明更复杂更安全的加密算法,解密者则绞尽脑汁去破解它们。 我们现在使用的RSA等非对称加密算法通常基于这样一个前提:大数的质因数分解是极其困难的,目前唯一的方法就是暴力破解。所以现在来看,RSA算法还是很安全的。但难保在将来某一天不会有人发现一种快速分解质因数的方法,那时候RSA等非对称加密算法也会像字符替换法一样变得不再安全,人们就不得不另外寻找新的加密方法喽。

附源程序

© 版权声明
THE END
喜欢就支持一下吧
点赞32
分享
评论 共9条

请登录后发表评论