【字符編碼系列】Base64編碼原理以及實現

寫在前面的話

本文屬於 字符編碼系列文章之一,更多請前往 字符編碼系列html

大綱

  • 簡介
  • 編碼原理
  • 轉碼對照表
  • 示例步驟分析
  • 源碼實現

簡介

Base64是一種編碼方式,一般用於將二進制數據編碼爲可打印字符組成的數據格式。git

爲何要有Base64編碼

在好久之前,發送郵件時只支持ASCII字符的發送,若是有非ASCII碼字符,則發送不了,因而須要在不改變傳統協議的狀況下,作一種擴展方案來支持這類字符的傳送。Base64編碼應運而生。github

Base64的常見誤區

不少開發者喜歡直接用Base64進行加密解密工做,實際上這個是徹底無心義的,由於Base64這種編碼規則是公開的,基本只要有程序能力都能解開,因此請勿用做加密用途。segmentfault

Base64編碼的主要的做用不在於安全性,而在於讓內容能在網絡間無錯的傳輸。(經常使用語編碼特殊字符,編碼小型二進制文件等)安全

編碼原理

  • 將數據按照 3個8位字節一組的形式進行處理,每3個8位字節在編碼以後被轉換爲4個6位字節服務器

    • 3*8=24變爲4*6=24
    • 在編碼後的6位的前面補兩個0,造成8位一個字節的形式
    • 這樣,編碼後3個8位字節則自動轉化成4個6位字節了
    • 緣由是2的6次方爲64,因此每6個位爲一個單元,能夠轉換爲對應64個字符中的某一個
  • 當數據的長度沒法知足3的倍數的狀況下,最後的數據須要進行填充操做網絡

    • 當原數據不是3的整數倍時,會自動補0。也就是說,若是原數據剩餘1個字節,那麼,另外兩個都是補的0,若是剩餘2個字節,另一個字節補得0。
    • 而後編碼時,對於後面自動補0的字符,會用=做爲填充字符(這裏=不是第65個字符,僅僅作填充做用)
    • 之因此要用=號進行填充是爲了解碼時方便還原(由於=號只須要還原爲0便可)

解碼

  • 解碼是編碼的逆過程
  • 其中的=號還原爲0便可

轉碼對照表

每6個單元高位補2個零造成的字節位於0~63之間,經過在轉碼錶中查找對應的可打印字符。「=」用於填充。以下所示爲轉碼錶。編碼

示例步驟分析

以」Word」字符串的編碼和解碼爲例。加密

編碼

├  原始字符 | W | o | r | d(因爲不是3的倍數,因此要補0) |
├─────────────────────────────────|
├ ASCII碼 |  87 | 111 | 114 | 100 |
├─────────────────────────────────|
├ 8bit字節 |  01010111 | 01101111 | 01110010 | 01100100 | 00000000 | 00000000 |
├─────────────────────────────────|
├ 6bit字節 |  010101 | 110110 | 111101 | 110010 | 011001 | 000000 | 000000 | 000000 |
├─────────────────────────────────|
├ B64十進制 |  21 | 54 | 61 | 50 | 25 | 0(注意,這裏有兩位是d裏面的,因此是正常的0) | 異常(須要補上=號) | 異常 |
├─────────────────────────────────|
├ 對應編碼 |  V | 2 | 9 | y | Z | A | = | = |
└───────────────────────────────────────────

因此’Word’的編碼結果是V29yZA==spa

解碼

├ 原始編碼 |  V | 2 | 9 | y | Z | A | = | = |
├─────────────────────────────────|
├ B64十進制 |  21 | 54 | 61 | 50 | 25 | 0 | 異常 | 異常 |
├─────────────────────────────────|
├ 6bit字節 |  010101 | 110110 | 111101 | 110010 | 011001 | 000000 | 000000 | 000000 |
├─────────────────────────────────|
├ 8bit字節 |  01010111 | 01101111 | 01110010 | 01100100 | 00000000 | 00000000 |
├─────────────────────────────────|
├ ASCII碼 |  87 | 111 | 114 | 100 |異常 |異常 |
├─────────────────────────────────|
├  對應字符 | W | o | r | d | 無 | 無 |
└─────────────────────────────────────

因而可知,解碼過程就是編碼過程的逆過程。

源碼實現

須要注意的是,實際編碼時須要注意程序內部的編碼,例如Javascript內置的編碼如今能夠當作是UTF-16,因此若是編碼成GBK或UTF-8時須要通過必定的轉換

/**
 * @description 建立一個base64對象
 */
(function(base64) {
    /**
     * Base64編碼要求把3個8位字節(3*8=24)轉化爲4個6位的字節(4*6=24),
     * 以後在6位的前面補兩個0,造成8位一個字節的形式。
     * 因爲2的6次方爲64, 因此每6個位爲一個單元, 對應某個可打印字符。
     * 當原數據不是3的整數倍時, 若是最後剩下兩個輸入數據,
     * 在編碼結果後加1個「=;若是最後剩下一個輸入數據,編碼結果後加2個「=;
     * 若是沒有剩下任何數據,就什麼都不要加,這樣才能夠保證資料還原的正確性。
     */
    /**
     * base64轉碼錶,最後一個=號是專門用來補齊的
     */
    var keyTable = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
    /**
     * @description 將一個目標字符串編碼爲base64字符串
     * @param {String} str 傳入的目標字符串
     * 能夠是任何編碼類型,傳入什麼類型就輸出成了什麼樣的編碼
     * 因爲js內置是utf16編碼,而服務器端通常不使用這種,
     * 因此傳入的編碼通常是採起utf8或gbk的編碼
     * @return {String} 編碼後的base64字符串
     */
    function encodeBase64(str) {
        if (!str) {
            return '';
        }
        // 遍歷索引
        var i = 0;
        var len = str.length;
        var res = [];
        var c1, c2, c3 = '';
        // 用來存對應的位置
        var enc1, enc2, enc3, enc4 = '';
        while (i < len) {
            c1 = str.charCodeAt(i++) & 0xFF;
            c2 = str.charCodeAt(i++);
            c3 = str.charCodeAt(i++);
            enc1 = c1 >> 2;
            enc2 = ((c1 & 0x3) << 4) | ((c2 >> 4) & 0x0F);
            enc3 = ((c2 & 0x0F) << 2) | ((c3 & 0xC0) >> 6);
            enc4 = c3 & 0x3F;
            // 專門用來補齊=號的
            if (isNaN(c2)) {
                enc3 = enc4 = 0x40;
            } else if (isNaN(c3)) {
                enc4 = 0x40;
            }
            res.push(keyTable.charAt(enc1));
            res.push(keyTable.charAt(enc2));
            res.push(keyTable.charAt(enc3));
            res.push(keyTable.charAt(enc4));
            c1 = c2 = c3 = "";
            enc1 = enc2 = enc3 = enc4 = "";
        }
        return res.join('');
    };
    /**
     * @description 解碼base64字符串,還原爲編碼前的結果
     * @param {String} str 傳入的目標字符串
     * 能夠是任何編碼類型,傳入什麼類型就輸出成了什麼樣的編碼
     * 因爲js內置是utf16編碼,而服務器端通常不使用這種,
     * 因此傳入的編碼通常是採起utf8或gbk的編碼
     * @return {String} 編碼後的base64字符串
     */
    function decodeBase64(str) {
        if (!str) {
            return '';
        }
        // 這裏要判斷目標字符串是否是base64型,若是不是,直接就不解碼了
        // 兩層判斷
        if (str.length % 4 != 0) {
            return "";
        }
        var base64test = /[^A-Za-z0-9\+\/\=]/g;
        if (base64test.exec(str)) {
            return "";
        }
        var len = str.length;
        var i = 0;
        var res = [];
        var code1, code2, code3, code4;
        var c1, c2, c3 = '';
        while (i < len) {
            code1 = keyTable.indexOf(str.charAt(i++));
            code2 = keyTable.indexOf(str.charAt(i++));
            code3 = keyTable.indexOf(str.charAt(i++));
            code4 = keyTable.indexOf(str.charAt(i++));

            c1 = (code1 << 2) | (code2 >> 4);
            c2 = ((code2 & 0xF) << 4) | (code3 >> 2);
            c3 = ((code3 & 0x3) << 6) | code4;

            res.push(String.fromCharCode(c1));

            if (code3 != 64) {
                res.push(String.fromCharCode(c2));
            }
            if (code4 != 64) {
                res.push(String.fromCharCode(c3));
            }
        }
        return res.join('');
    };

    /**
     * @description 將字符串進行base64編碼,以後再進行uri編碼
     * @param {String} str 傳入的utf16編碼
     * @param {String} type 類別,是utf8,gbk仍是utf16,默認是utf16
     * @param {Boolean} isUri 是否uri編碼
     * @return {String} 編碼後並uri編碼的base64字符串
     */
    base64.encode = function(str, type, isUri) {
        type = type || 'utf16';
        if (type == 'gbk') {
            // 轉成 gbk
            str = exports.utf16StrToGbkStr(str);
        } else if (type == 'utf8') {
            // 轉成 utf8
            str = exports.utf16StrToUtf8Str(str);
        }
        // 不然就是默認的utf16不要變
        // 先b64編碼,再uri編碼(防止網絡傳輸出錯)
        var b64Str = encodeBase64(str);
        if (isUri) {
            b64Str = encodeURIComponent(b64Str);
            console.log(b64Str);
        }
        return b64Str;
    };

    /**
     * @description 將字符串先進行uri解碼,再進行base64解碼
     * @param {String} str 傳入的編碼後的base64字符串
     * @param {String} type 類別,是utf8,gbk仍是utf16,默認是utf16
     * @param {Boolean} isUri 是否uri解碼
     * @return {String} 編碼後並uri編碼的base64字符串
     */
    base64.decode = function(str, type, isUri) {
        type = type || 'utf16';
        if (isUri) {
            str = decodeURIComponent(str);
        }
        var decodeStr = decodeBase64(str);
        if (type == 'gbk') {
            return exports.gbkStrToUtf16Str(decodeStr);
        } else if (type == 'utf8') {
            return exports.utf8StrToUtf16Str(decodeStr);
        }
        // 不然就是默認的utf16不要變
        return decodeStr;
    };
})(exports.Base64 = {});

源碼

詳細能夠參考源碼: https://github.com/dailc/charset-encoding-series

附錄

博客

初次發佈2017.06.10於我的博客

http://www.dailichun.com/2017/06/10/base64encoding.html

參考資料

相關文章
相關標籤/搜索