Base64編碼簡介

 
基本概念
 
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編碼分別爲 0100110一、01100001和01101110,合併後獲得一個24位的二進制串 010011010110000101101110
2. 按每6位一組將其分爲4組:0100十一、0101十、00010一、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 
5     byte[] bytes = inputStr.getBytes(charset);
6     encodeStr = encode(bytes, padding);
7 
8     return encodeStr;
9 }

 

encode()方法的核心代碼是:html

 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 
 6     group_6bit_1 =  byte_1 >>> 2;
 7     group_6bit_2 = (byte_1 &  0x03) << 4 | byte_2 >>> 4;
 8     group_6bit_3 = (byte_2 &  0x0F) << 2 | byte_3 >>> 6;
 9     group_6bit_4 =  byte_3 &  0x3F;
10 
11     sb.append(CHARSET[group_6bit_1])
12       .append(CHARSET[group_6bit_2])
13       .append(CHARSET[group_6bit_3])
14       .append(CHARSET[group_6bit_4]);
15 }

 

即將每3個字節轉換爲4個字符。
 
固然還須要判斷最後是否還有剩餘的字節,若是有要單獨處理:
 
 1 if (tail == 1) {
 2     byte_1 = bytes[bytes.length-1] & 0xFF;
 3 
 4     group_6bit_1 =  byte_1 >>> 2;
 5     group_6bit_2 = (byte_1 &   0x03) << 4;
 6 
 7     sb.append(CHARSET[group_6bit_1])
 8       .append(CHARSET[group_6bit_2]);
 9 
10     if (padding) {
11         sb.append('=').append('=');
12     }
13 } else if (tail == 2) {
14     byte_1 = bytes[bytes.length-2] & 0xFF;
15     byte_2 = bytes[bytes.length-1] & 0xFF;
16 
17     group_6bit_1 =  byte_1 >>> 2;
18     group_6bit_2 = (byte_1 &   0x03) << 4 | byte_2 >>> 4;
19     group_6bit_3 = (byte_2 &   0x0F) << 2;
20 
21     sb.append(CHARSET[group_6bit_1])
22       .append(CHARSET[group_6bit_2])
23       .append(CHARSET[group_6bit_3]);
24 
25     if (padding) {
26         sb.append('=');
27     }
28 }

 

decode過程是相似的,具體請自行查閱完整代碼。java

 
引伸話題:利用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等非對稱加密算法也會像字符替換法同樣變得再也不安全,人們就不得不另外尋找新的加密方法嘍。
 
附:源程序
 
package base64;

import java.io.UnsupportedEncodingException;

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

    /*
     * charset used to decode.
     */
    private static final int[] DECODE_CHARSET = new int[128];
    static {
        for (int i=0; i<64; i++) {
            DECODE_CHARSET[CHARSET[i]] = i;
        }
    }

    /**
     * A convenient method for encoding Java String,
     * it uses encode(byte[], boolean) to encode byte array.
     * 
     * @param inputStr a string to be encoded.
     * @param charset charset name ("GBK" for example) that is used to convert inputStr into byte array.
     * @param padding whether using padding characters "="
     * @return encoded string
     * @throws UnsupportedEncodingException if charset is unsupported
     */
    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;
    }

    /**
     * Using Base64 to encode bytes.
     * 
     * @param bytes byte array to be encoded
     * @param padding whether using padding characters "="
     * @return encoded string
     */
    public String encode(byte[] bytes, boolean padding) {
        // 4 6-bit groups
        int group_6bit_1,
            group_6bit_2,
            group_6bit_3,
            group_6bit_4;

        // bytes of a group
        int byte_1,
            byte_2,
            byte_3;

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

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

        // handle each 3-byte group
        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]);
        }

        // handle last 1 or 2 byte(s)
        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('=');
            }
        }

        return sb.toString();
    }

    /**
     * Decode a Base64 string to bytes (byte array).
     * 
     * @param code Base64 string to be decoded
     * @return byte array
     */
    public byte[] decode(String code) {
        char[] chars = code.toCharArray();

        int group_6bit_1,
            group_6bit_2,
            group_6bit_3,
            group_6bit_4;

        int byte_1,
            byte_2,
            byte_3;

        int len = chars.length;
        // ignore last '='s
        if (chars[chars.length - 1] == '=') {
            len--;
        }
        if (chars[chars.length - 2] == '=') {
            len--;
        }

        int groups = len / 4;
        int tail = len % 4;

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

        int byteIdx = 0;

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

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

            bytes[byteIdx++] = (byte) byte_1;
            bytes[byteIdx++] = (byte) byte_2;
            bytes[byteIdx++] = (byte) byte_3;
        }

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

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

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

            bytes[byteIdx++] = (byte) byte_1;
            bytes[byteIdx]   = (byte) byte_2;
        }

        return bytes;
    }

    /**
     * Test.
     * @param args
     */
    public static void main(String[] args) {
        Base64 base64 = new Base64();
        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.";
        System.out.println(str);
        try {
            String encodeStr = base64.encode(str, "GBK", false);
            System.out.println(encodeStr);
            byte[] decodeBytes = base64.decode(encodeStr);
            String decodeStr = new String(decodeBytes, "GBK");
            System.out.println(decodeStr);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }
}
點擊展開

 

參考資料

1. 維基百科「Base64」詞條: http://en.wikipedia.org/wiki/Base64算法

相關文章
相關標籤/搜索