Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1163170
  • 博文数量: 146
  • 博客积分: 6619
  • 博客等级: 准将
  • 技术积分: 1621
  • 用 户 组: 普通用户
  • 注册时间: 2008-02-29 14:06
文章分类

全部博文(146)

文章存档

2020年(1)

2019年(4)

2018年(3)

2017年(5)

2015年(5)

2014年(7)

2013年(5)

2012年(11)

2011年(15)

2010年(13)

2009年(14)

2008年(63)

分类: 网络与安全

2019-03-25 23:09:40

基本概念
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 from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure.
通过MIME Base64进行转换后就成为:
TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=
转换方法
 
以例子开头的“Man”被转换为“TWFu”为例,我们来看看Base64基本的转换过程:
 
1. M、a和n的ASCII编码分别为01001101、01100001和01101110,合并后得到一个24位的二进制串010011010110000101101110
2. 按每6位一组将其分为4组:010011、010110、000101、101110
3. 最后按对应关系从字符集中取出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组了。最后再用普通方法做映射即可。
 

 
还原时,依次将每4个字符还原成3个字节,最后会出现3种情况之一:
 
1. 没有字符剩下
2. 还剩下2个字符
3. 还剩下3个字符
 
这3种情况与上面的3种情况一一对应,只要对补零的过程反过来处理,就可以原样还原了。
 
填充
 
我们经常会在Base64编码字符串中看到最后有“=”字符,这就是通过填充生成的。填充就是当出现编码时的情况2和3时,在后面补上“=”字符,使编码后的字符数为4的倍数。
 
所以我们可以很容易地想到,情况2,即还剩下1个字节时,需要补2个“=”,因为此时最后一个字节编码为2个字符,补上2个“=”正好凑够4个。情况3同理,需要补1个“=”。
 
 
填充不是必须的,因为无需填充也可以通过编码后的内容计算出缺失的字节。所以在一些实现中填充是必须的,有些却不是。一种必须使用填充的场合是当需要将多个Base64编码文件合并为一个文件的时候。
 
实现(示例)
 
下面是一个Base64字符集,它包含大写字母、小写字母和数字,以及“+”和“/”符号。
 
编码 字符   编码 字符   编码 字符   编码 字符
0 A 16 Q 32 g 48 w
1 B 17 R 33 h 49 x
2 C 18 S 34 i 50 y
3 D 19 T 35 j 51 z
4 E 20 U 36 k 52 0
5 F 21 V 37 l 53 1
6 G 22 W 38 m 54 2
7 H 23 X 39 n 55 3
8 I 24 Y 40 o 56 4
9 J 25 Z 41 p 57 5
10 K 26 a 42 q 58 6
11 L 27 b 43 r 59 7
12 M 28 c 44 s 60 8
13 N 29 d 45 t 61 9
14 O 30 e 46 u 62 +
15 P 31 f 47 v 63 /
 
利用这个字符集我们可以写一个简单的Base64实现(本文最后附有完整源代码):
 
下面这个encode()方法用来将Java字符串转换为字节数组(Base64操作的是字节),然后调用真正的encode()方法完成编码:

点击(此处)折叠或打开

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

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

  6.     return encodeStr;
  7. }
encode()方法的核心代码是:

点击(此处)折叠或打开

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

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

  9.     sb.append(CHARSET[group_6bit_1])
  10.       .append(CHARSET[group_6bit_2])
  11.       .append(CHARSET[group_6bit_3])
  12.       .append(CHARSET[group_6bit_4]);
  13. }
即将每3个字节转换为4个字符。
 
当然还需要判断最后是否还有剩余的字节,如果有要单独处理:

点击(此处)折叠或打开

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

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

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

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

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

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

  19.     if (padding) {
  20.         sb.append('=');
  21.     }
  22. }

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

 
引申话题:利用Base64加密解密
 
虽然本文的开头就已经提到过,Base64不是一种加密算法,但实际上我们确实可以利用Base64来加密数据。
 
我们都知道,加密就是将明文变为密文的过程。在这个过程中起关键作用的一是算法,二则是密钥。算法相当于制造工艺或加工过程,而密钥则是配方。制造工艺可以公开,但配方必须保密,否则人人都能生产云南白药了。
 
容易想到,Base64的配方就是字符集。选用的字符集不同,甚至只是改变一下字符集中字符的顺序(编号),相同的加工过程就会生成不同的Base64编码。
 
例如,如果不告诉你编码时使用的字符集,你能知道下面的编码对应的原文是什么吗?
TWl+Im1DImR5sHR5r2tFqXN4pWQ8ImZ/tih/r2BZImJZImx5sChCpWlDrGY8ImJFtihyuSh
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等非对称加密算法也会像字符替换法一样变得不再安全,人们就不得不另外寻找新的加密方法喽。
 
附:源程序

点击(此处)折叠或打开

  1. package base64;

  2. import java.io.UnsupportedEncodingException;

  3. /**
  4.  * This class provides a simple implementation of Base64 encoding and decoding.
  5.  *
  6.  * @author QiaoMingkui
  7.  *
  8.  */
  9. public class Base64 {
  10.     /*
  11.      * charset
  12.      */
  13.     private static final char[] CHARSET = {
  14.         'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
  15.         'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
  16.         'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
  17.         'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
  18.         'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
  19.         'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
  20.         'w', 'x', 'y', 'z', '0', '1', '2', '3',
  21.         '4', '5', '6', '7', '8', '9', '+', '/'
  22.     };

  23.     /*
  24.      * charset used to decode.
  25.      */
  26.     private static final int[] DECODE_CHARSET = new int[128];
  27.     static {
  28.         for (int i=0; i<64; i++) {
  29.             DECODE_CHARSET[CHARSET[i]] = i;
  30.         }
  31.     }

  32.     /**
  33.      * A convenient method for encoding Java String,
  34.      * it uses encode(byte[], boolean) to encode byte array.
  35.      *
  36.      * @param inputStr a string to be encoded.
  37.      * @param charset charset name ("GBK" for example) that is used to convert inputStr into byte array.
  38.      * @param padding whether using padding characters "="
  39.      * @return encoded string
  40.      * @throws UnsupportedEncodingException if charset is unsupported
  41.      */
  42.     public String encode(String inputStr, String charset, boolean padding)
  43.             throws UnsupportedEncodingException {
  44.         String encodeStr = null;

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

  47.         return encodeStr;
  48.     }

  49.     /**
  50.      * Using Base64 to encode bytes.
  51.      *
  52.      * @param bytes byte array to be encoded
  53.      * @param padding whether using padding characters "="
  54.      * @return encoded string
  55.      */
  56.     public String encode(byte[] bytes, boolean padding) {
  57.         // 4 6-bit groups
  58.         int group_6bit_1,
  59.             group_6bit_2,
  60.             group_6bit_3,
  61.             group_6bit_4;

  62.         // bytes of a group
  63.         int byte_1,
  64.             byte_2,
  65.             byte_3;

  66.         // number of 3-byte groups
  67.         int groups = bytes.length / 3;
  68.         // at last, there might be 0, 1, or 2 byte(s) remained,
  69.         // which needs to be encoded individually.
  70.         int tail = bytes.length % 3;

  71.         StringBuilder sb = new StringBuilder(groups * 4 + 4);

  72.         // handle each 3-byte group
  73.         for (int i = 0; i < groups; i++) {
  74.             byte_1 = bytes[3*i] & 0xFF;
  75.             byte_2 = bytes[3*i+1] & 0xFF;
  76.             byte_3 = bytes[3*i+2] & 0xFF;

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

  81.             sb.append(CHARSET[group_6bit_1])
  82.               .append(CHARSET[group_6bit_2])
  83.               .append(CHARSET[group_6bit_3])
  84.               .append(CHARSET[group_6bit_4]);
  85.         }

  86.         // handle last 1 or 2 byte(s)
  87.         if (tail == 1) {
  88.             byte_1 = bytes[bytes.length-1] & 0xFF;

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

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

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

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

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

  105.             if (padding) {
  106.                 sb.append('=');
  107.             }
  108.         }

  109.         return sb.toString();
  110.     }

  111.     /**
  112.      * Decode a Base64 string to bytes (byte array).
  113.      *
  114.      * @param code Base64 string to be decoded
  115.      * @return byte array
  116.      */
  117.     public byte[] decode(String code) {
  118.         char[] chars = code.toCharArray();

  119.         int group_6bit_1,
  120.             group_6bit_2,
  121.             group_6bit_3,
  122.             group_6bit_4;

  123.         int byte_1,
  124.             byte_2,
  125.             byte_3;

  126.         int len = chars.length;
  127.         // ignore last '='s
  128.         if (chars[chars.length - 1] == '=') {
  129.             len--;
  130.         }
  131.         if (chars[chars.length - 2] == '=') {
  132.             len--;
  133.         }

  134.         int groups = len / 4;
  135.         int tail = len % 4;

  136.         // each group of characters (4 characters) will be converted into 3 bytes,
  137.         // and last 2 or 3 characters will be converted into 1 or 2 byte(s).
  138.         byte[] bytes = new byte[groups * 3 + (tail > 0 ? tail - 1 : 0)];

  139.         int byteIdx = 0;

  140.         // decode each group
  141.         for (int i=0; i<groups; i++) {
  142.             group_6bit_1 = DECODE_CHARSET[chars[4*i]];
  143.             group_6bit_2 = DECODE_CHARSET[chars[4*i + 1]];
  144.             group_6bit_3 = DECODE_CHARSET[chars[4*i + 2]];
  145.             group_6bit_4 = DECODE_CHARSET[chars[4*i + 3]];

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

  149.             bytes[byteIdx++] = (byte) byte_1;
  150.             bytes[byteIdx++] = (byte) byte_2;
  151.             bytes[byteIdx++] = (byte) byte_3;
  152.         }

  153.         // decode last 2 or 3 characters
  154.         if (tail == 2) {
  155.             group_6bit_1 = DECODE_CHARSET[chars[len - 2]];
  156.             group_6bit_2 = DECODE_CHARSET[chars[len - 1]];

  157.             byte_1 = group_6bit_1 << 2 | group_6bit_2 >>> 4;
  158.             bytes[byteIdx] = (byte) byte_1;
  159.         } else if (tail == 3) {
  160.             group_6bit_1 = DECODE_CHARSET[chars[len - 3]];
  161.             group_6bit_2 = DECODE_CHARSET[chars[len - 2]];
  162.             group_6bit_3 = DECODE_CHARSET[chars[len - 1]];

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

  165.             bytes[byteIdx++] = (byte) byte_1;
  166.             bytes[byteIdx] = (byte) byte_2;
  167.         }

  168.         return bytes;
  169.     }

  170.     /**
  171.      * Test.
  172.      * @param args
  173.      */
  174.     public static void main(String[] args) {
  175.         Base64 base64 = new Base64();
  176.         String str = "Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure.";
  177.         System.out.println(str);
  178.         try {
  179.             String encodeStr = base64.encode(str, "GBK", false);
  180.             System.out.println(encodeStr);
  181.             byte[] decodeBytes = base64.decode(encodeStr);
  182.             String decodeStr = new String(decodeBytes, "GBK");
  183.             System.out.println(decodeStr);
  184.         } catch (UnsupportedEncodingException e) {
  185.             e.printStackTrace();
  186.         }
  187.     }
  188. }




阅读(5356) | 评论(0) | 转发(0) |
0

上一篇:UNICODE,GBK,UTF-8区别

下一篇:RSA 共模攻击

给主人留下些什么吧!~~