zoukankan      html  css  js  c++  java
  • Base64编码详解及其变种(解决加号在URL变空格问题)

    Base64在我看来很重要的一个用途是将复杂的码比如GBK、UTF8、MIME等复杂的码,变成单字节的简单ASCII字符,便于在各种不同环境(计算机)之间传递信息。这很好理解,只要是计算机,必须得支持ASCII标准,但不一定支持其它编码。

    编码

    Base64的编码过程是将每三个字节即24个bit,变成以6个bit为一组的组集,共有24/6=4组。每个组的6个bit最高能表示2^6即64个数,这也是Base64的由来。这64个数的表示区间为[0,63],建立一个字符索引表,输入值为[0,63],输出表中对应字符。不同的Base64变种在编码过程主要是索引表不一样。(可能你有个疑问,每轮需要3个byte,这必须使得需要编码的byte长度整除3,不整除3怎么办?后面后讲到)。

    image

    如图所示,3个byte用红、紫、绿表示,4个组A=A1 A2 A3 A4 A5 A6,B= B1 B2 B3 B4 B5 B6,C= C1 C2 C3 C4 C5 C6,D = D1 D2 D3 D4 D5 D6。因为在绝大多数语言中,byte都为最小操作单元,所以这四个组的输出byte值将会是A’ = 0 0 A1 A2 A3 A4 A5 A6,B’ = 0 0 B1 B2 B3 B4 B5 B6 , C’= 0 0 C1 C2 C3 C4 C5 C6,D’ = 0 0 D1 D2 D3 D4 D5 D6。一个byte只需要用到其中的6个bit,当然最高两位要置0了。

    应用一点点计算机编码知识,假设红byte为R,紫byte为P,绿byte为G,那么:

    • A’ = R >> 2,红byte右移两位,表示A取R的高六位。
    • B’ = (R << 4 & 0x3F) | P >> 4。R左移4位变为A5 A6 B1 B2 0 0 0 0,看B’的红色部分高两位为零,所以要 & 上0x3f,因为0x3f的二进制表示为0 0 1 1 1 1 1 1,这样就变成 0 0 B1 B2 0 0 0 0 。P >> 4将P的高四位变为低四位,高四位置0,变为0 0 0 0 B3 B4 B5 B6。很显然 0 0 B1 B2 0 0 0 0  | 0 0 0 0 B3 B4 B5 B6 = 0 0 B1 B2 B3 B4 B5 B6
    • C’ = (P << 2 & 0x3F) | G >> 6。P左移2位变为B5 B6 C1 C2 C3 C3 C4 0 0,看C’的红色部分高两位为零,所以要 & 上0x3f,这样值为0 0 C1 C2 C3 C3 C4 0 0。G右移6位为 0 0 0 0 0 0 C5 C6。很显然 0 0 C1 C2 C3 C3 C4 0 0 | 0 0 0 0 0 0 C5 C6 = 0 0 C1 C2 C3 C4 C5 C6。
    • D’ = G & 0x3F。只需将G的高两位C5,C6置0,就是0 0 D1 D2 D3 D4 D5 D6。

    不能整除3怎么办?

    上面讲的是byte长度能整除3,实际只有1/3概率可整除,另两个1/3是余数为1和余数为2。对于不整除的情况,Base64的做法是补齐,不是补齐byte,而是补齐编码之后的子串,使编码字串能够被4整除,因为解码只能是4个字符解成3个byte。补齐字串用了第65个字符 = 即等号。下面分别描述。

    余数为1的情况

    余数为1也就是上图只能剩红byte R了,那么:

    • A’ = R >> 2。这个保持不变。
    • B’ = R << 4 & 0x3F。P没有了,只能取R的最低两位了。
    • C’ = '=',即C’为填充字符= 。
    • D’ = '=',即D’为填充字符= 。

    余数为2的情况

    余数为2也就是上图有红byte R,紫byte P,绿byte G没有了。那么:

    • A’ = R >> 2。这个保持不变。
    • B’ = (R << 4 & 0x3F) | P >> 4。这个也保持不变。
    • C’ = P << 2 & 0x3F。因为G没有了,只能取P的低四位。
    • D’ = '=',即D’为填充字符= 。

    前面讲到,不同Base64编码只是字符索引表不一样,最正宗的Base64使用了如下字符索引表。

       1: static final char intToBase64[] = { 'A', 'B', 'C', 'D', 'E', 'F', /* 索引 0 ~ 5*/
       2:             'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',  /* 索引6 ~ 18*/
       3:             'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',  /* 索引 19 ~ 31*/
       4:             'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',  /* 索引 32 ~ 44*/
       5:             't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5',  /* 索引 45 ~ 57*/
       6:             '6', '7', '8', '9', '+', '/' };  /* 索引58 ~ 63*/

    那么一个Java Base64算法实现如下:

       1: private static String byteArrayToBase64(byte[] a) {
       2:     int aLen = a.length; //总长度
       3:     int numFullGroups = aLen / 3; //以3个byte组成以4个字符为一组的组数
       4:     int numBytesInPartialGroup = aLen - 3 * numFullGroups; //余数
       5:     int resultLen = 4 * ((aLen + 2) / 3); //输出长度总是4倍数,如果有余数,(aLen+2)/3保证将余数包含,并有空间放置填充符=
       6:     StringBuffer result = new StringBuffer(resultLen);
       7:  
       8:     int inCursor = 0;
       9:     for (int i = 0; i < numFullGroups; i++) {
      10:         int byte0 = a[inCursor++] & 0xff;
      11:         int byte1 = a[inCursor++] & 0xff;
      12:         int byte2 = a[inCursor++] & 0xff;
      13:         result.append(intToBase64[byte0 >> 2]);
      14:         result.append(intToBase64[(byte0 << 4) & 0x3f | (byte1 >> 4)]);
      15:         result.append(intToBase64[(byte1 << 2) & 0x3f | (byte2 >> 6)]);
      16:         result.append(intToBase64[byte2 & 0x3f]);
      17:     }
      18:     //处理余数
      19:     if (numBytesInPartialGroup != 0) {
      20:         int byte0 = a[inCursor++] & 0xff;
      21:         result.append(intToBase64[byte0 >> 2]);
      22:         //余数为1
      23:         if (numBytesInPartialGroup == 1) {
      24:             result.append(intToBase64[(byte0 << 4) & 0x3f]);
      25:             result.append("==");
      26:         } else {
      27:             // 余数为2
      28:             int byte1 = a[inCursor++] & 0xff;
      29:             result.append(intToBase64[(byte0 << 4) & 0x3f | (byte1 >> 4)]);
      30:             result.append(intToBase64[(byte1 << 2) & 0x3f]);
      31:             result.append('=');
      32:         }
      33:     }
      34:     return result.toString();
      35: }

    解码

    解码是将4个字符变成三个byte,编码是通过字符表映射索引值到字符上,那么显然解码就是将字符回索引值,即有个反向索引表,这个反向索引表与索引表一一对应,每个Base64变种修改一下这两个表即可。反向索引表是以字符的ASCII码 码值作为下标查找索引表索引值。比如上图的正向索引表intToBase64定义了加号+的索引值为62,字符+的ASCII码值为43,那么反向索引表下标值为43的值一定是62。再比如字符A的索引表值为0,A的ASCII码值为65,那么反向索引表下标为65的值一定是0。在基本Base64中,最大ASCII码值为z即122,那么反向索引表的长度为122+1=123。下面是对应前面的基本索引表的基本反向索引表。

       1: static final byte base64ToInt[] = { -1, -1, -1, -1, -1, -1, -1, -1,
       2:             -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
       3:             -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
       4:             -1, 62/* 符号+*/, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1,
       5:             -1, -1, -1, -1, -1, -1, 0 /* 符号A */, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
       6:             13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1,
       7:             -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
       8:             41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 };
    因为编码有余数情况,所以解码同样要处理余数情况。设这四个字符的对应的反向索引值为Q1、Q2、Q3、Q4,要求解的3个byte分别为M1,M2,M3。注意反向索引值最高为63,所以其byte表示的最高两位总为0

    没有余数的情况

    • M1 = Q1 << 2 | Q2 >> 4。Q1可表示为0 0 x x x x x x,左移两位变成x x x x x x 0 0。Q2也表示为 0 0 x x x x x x,右移四位变成0 0 0 0 0 0 x x 。那么这两个或一下正好是 x x x x x x x x。也就是由字符Q1的有效六位组成M1的高六位,然后用Q2的最高两个有效位组成M2的最低两位。
    • M2 = Q2 << 4 | Q3 >> 2。如M2所描述那样,M2的高四位是Q2的低四位,低四位是Q3的高四位。Q2:0 0 x x x x x x –> x x x x 0 0 0 0,Q2:0 0 0 x x x x x x –> 0 0 0 0 x x x x。很显然x x x x 0 0 0 0 | 0 0 0 0 x x x x = x x x x x x x x。

    • M3 = Q3 << 6 | Q4。M3的高两位是Q3的最低两位,低六位是Q4的有效六位。

    余数为1的情况

    余数为1即编码的最后两个字符都是=。也就是说只有Q1、Q2。只需要联合Q1和Q2组成余出来的1个字节M1即可。

    • M1 = Q1 << 2 | Q2 >> 4。

    余数为2的情况

    余数为2即编码的只有最后一位是=。也就是说通过Q1,Q2,Q3组成余下来的两个字节M1,M2即可。

    • M1 = Q1 << 2 | Q2 >> 4。
    • M2 = Q2 << 4 | Q3 >> 2。

    以下是解码的Java实现。

       1: private static byte[] base64ToByteArray(String s) throws Exception {
       2:     //字符总长必须是4的倍数
       3:     int sLen = s.length();
       4:     int numGroups = sLen / 4;
       5:     if (4 * numGroups != sLen)
       6:         throw new IllegalArgumentException(
       7:                 "字串长度必须是4的倍数");
       8:     //余1个byte则算漏了两个byte,余2个byte则算漏掉了1个byte
       9:     int missingBytesInLastGroup = 0;
      10:     int numFullGroups = numGroups;
      11:     if (sLen != 0) {
      12:         //余2个byte的情况
      13:         if (s.charAt(sLen - 1) == '=') {
      14:             missingBytesInLastGroup++;
      15:             //如果有余数发生,则完整3个byte组数少一个。
      16:             numFullGroups--;
      17:         }
      18:         //余1个byte的情况
      19:         if (s.charAt(sLen - 2) == '=')
      20:             missingBytesInLastGroup++;
      21:     }
      22:     //总字节长度
      23:     byte[] result = new byte[3 * numGroups - missingBytesInLastGroup];
      24:  
      25:     try {
      26:         int inCursor = 0, outCursor = 0;
      27:         for (int i = 0; i < numFullGroups; i++) {
      28:             int ch0 = base64toInt(s.charAt(inCursor++), base64ToInt);
      29:             int ch1 = base64toInt(s.charAt(inCursor++), base64ToInt);
      30:             int ch2 = base64toInt(s.charAt(inCursor++), base64ToInt);
      31:             int ch3 = base64toInt(s.charAt(inCursor++), base64ToInt);
      32:             result[outCursor++] = (byte) ((ch0 << 2) | (ch1 >> 4));
      33:             result[outCursor++] = (byte) ((ch1 << 4) | (ch2 >> 2));
      34:             result[outCursor++] = (byte) ((ch2 << 6) | ch3);
      35:         }
      36:         if (missingBytesInLastGroup != 0) { 
      37:             int ch0 = base64toInt(s.charAt(inCursor++), base64ToInt);
      38:             int ch1 = base64toInt(s.charAt(inCursor++), base64ToInt);
      39:             //不管余1还是余2个byte,肯定要解码一个byte。
      40:             result[outCursor++] = (byte) ((ch0 << 2) | (ch1 >> 4));
      41:  
      42:             //如果余2个,即差一个才构成3byte,那么还要解码第二个byte。
      43:             if (missingBytesInLastGroup == 1) {
      44:                 int ch2 = base64toInt(s.charAt(inCursor++), base64ToInt);
      45:                 result[outCursor++] = (byte) ((ch1 << 4) | (ch2 >> 2));
      46:             }
      47:         }
      48:     } catch (Exception e) {
      49:         throw e;
      50:     }
      51:     return result;
      52: }
      53:  
      54: private static int base64toInt(char c, byte[] alphaToInt) throws Exception {
      55:     int result = alphaToInt[c];
      56:     if (result < 0)
      57:         throw new Exception("非法索引值");
      58:     return result;
      59: }

    变种

    因为Base64编解码的变种只与索引表和反向索引表有关系,所以可以在ASCII码(1字节范围内)做任意变种。下面描述一个变种例子。

    假如要将中文用基本Base64索引表编码成字串,将其作为参数在浏览器里传输,很不幸,因为基本表中会出现+和/字符,这个一般会被浏览器理解成空格和路径分割符。所以为了让其工作正常,需要把索引表的最后两个字符+和/分别替换成点 . 和下划线 _

    正向索引表:

       1: static final char intToBase64[] = { 'A', 'B', 'C', 'D', 'E', 'F', /* 索引 0 ~ 5*/
       2:             'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',  /* 索引6 ~ 18*/
       3:             'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',  /* 索引 19 ~ 31*/
       4:             'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',  /* 索引 32 ~ 44*/
       5:             't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5',  /* 索引 45 ~ 57*/
       6:             '6', '7', '8', '9', '.'/*原先是字符+*/, '_'/*原先是字符/ */ };  /* 索引58 ~ 63*/

    反向索引表改的稍微多点,字符. 的ASCII码值为46,下划线码值为95。则需要将原来+和/ 的索引位置改成-1,将索引位置46从-1改成62,位置95处从-1改成63。

       1: static final byte base64ToInt[] = { -1, -1, -1, -1, -1, -1, -1, -1,
       2:             -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
       3:             -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
       4:             -1, -1/*原先是62*/, -1, -1, 62/*原先是-1*/, -1/*原先是63*/, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1,
       5:             -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
       6:             13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1,
       7:             63/*原先是-1*/, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
       8:             41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 };
  • 相关阅读:
    User Get 'Access Denied' with Excel Service WebPart
    How To Search and Restore files from Site Collection Recycle Bin
    How To Collect ULS Log from SharePoint Farm
    How To Restart timer service on all servers in farm
    How to Operate SharePoint User Alerts with PowerShell
    How to get Timer Job History
    Synchronization Service Manager
    SharePoint 2007 Full Text Searching PowerShell and CS file content with SharePoint Search
    0x80040E14 Caused by Max Url Length bug
    SharePoint 2007 User Re-created in AD with new SID issue on MySite
  • 原文地址:https://www.cnblogs.com/lifesting/p/2587923.html
Copyright © 2011-2022 走看看