Base64及其Python實現

1. 什么是Base64

Base64是一種基于64個可打印字符來表示二進制數據的表示方法

Base64是一種編碼方式,提及編碼方式,必然有其對應的字符集合。在Base64編碼中,相互映射的兩個集合是:

  • 二進制數據{0, 1}
  • {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, +, /}

Base64編碼方式可使得信息在這兩種字符集表示法之間相互等價轉換

因為Base64的編碼方式是公開的,所以base64也可以算是公開算法的加密方法;但是只能簡單的“加密”保護某些數據,決不能在需要安全等級較高的場景中使用,因為可以使用公開的編碼方法輕易從base64字符表示的數據解碼二進制數據。

2. base64編碼過程

由于base64的字符集大小為64,那么,需要6個比特的二進制數作為一個基本單元表示一個base64字符集中的字符。因為6個比特有2^6=64種排列組合。

具體來說,編碼過程如下:

  1. 將每三個字節作為一組,共24bit,若不足24bit在其后補充0;
  2. 將這24個bit分為4組,每一組6個bit;
  3. 在每組前加00擴展為8個bit,形成4個字節,每個字節表示base64字符集索引;
  4. 擴展后的8bit表示的整數作為索引,對應base64字符集的一個字符,這就是base64編碼值;在處理最后的不足3字節時,缺一個字節索引字節取3個,最后填充一個=,;缺兩個字節取2個索引字節,最后填充==。

解碼時將過程逆向即可。

Base64索引表:

圖片來源維基百科

3.編碼示例

示例一
Man的base64編碼

圖片來源維基百科
  1. 第一步,'M', 'a', 'n'的ASCII值分別為77, 97, 110,對應的二進制值分別為:01001101, 01100001, 01101110;取三個字節共24bit:010011010110000101101110
  2. 第二步,將這24bit分為4組,每組6個bit:010011, 010110, 000101, 101110
  3. 每組前面加00,形成4個字節的,00010011, 00010110, 00000101, 00101110, 即19, 22, 5, 46
  4. 根據索引表,對應的base64字符分別是T, W, F, u

最后的base64字符串是: TWFu。
解碼時將過程逆向即可。

示例二
剩余兩個字節,BC的base64編碼

圖片來源維基百科
  1. 第一步,'B', 'C'的ASCII值分別為66, 64, 對應二進制值分別為:01000010, 01000011;取三個字節,不足不0,共24bit:01000010, 01000011, 00000000
  2. 第二步,將這24bit分為4組,每組6個bit:010000, 100100, 001100, 000000
  3. 每組前面加00,形成4個字節的,00010000, 00100100, 00001100, 00000000,即16, 36, 12, 0
  4. 由于'B', 'C'只有兩個字節,缺一個字節,因此取3個索引;根據索引表,對應的base64字符分別是Q, k, M,最后填充一個=

最后的base64字符串是:QkM=

示例三
剩余一個字節,A的base64編碼

圖片來源維基百科
  1. 第一步,'A'的ASCII值65, 對應二進制值為:01000001; 取三個字節,不足不0,共24bit:01000001, 00000000, 00000000
  2. 第二步,將這24bit分為4組,每組6個bit:010000, 010000, 000000, 000000
  3. 每組前面加00,形成4個字節的,00010000, 00010000, 00000000, 00000000,即16, 16, 0, 0
  4. 由于'A'只有一個字節,缺兩個字節,因此取2個索引;根據索引表,對應的base64字符分別是Q, Q,最后填充==

最后的base64字符串是:QQ==

4. Python實現

"""
base64實現
"""

import base64
import string

# base 字符集

base64_charset = string.ascii_uppercase + string.ascii_lowercase + string.digits + '+/'


def encode(origin_bytes):
    """
    將bytes類型編碼為base64
    :param origin_bytes:需要編碼的bytes
    :return:base64字符串
    """

    # 將每一位bytes轉換為二進制字符串
    base64_bytes = ['{:0>8}'.format(str(bin(b)).replace('0b', '')) for b in origin_bytes]

    resp = ''
    nums = len(base64_bytes) // 3
    remain = len(base64_bytes) % 3

    integral_part = base64_bytes[0:3 * nums]
    while integral_part:
        # 取三個字節,以每6比特,轉換為4個整數
        tmp_unit = ''.join(integral_part[0:3])
        tmp_unit = [int(tmp_unit[x: x + 6], 2) for x in [0, 6, 12, 18]]
        # 取對應base64字符
        resp += ''.join([base64_charset[i] for i in tmp_unit])
        integral_part = integral_part[3:]

    if remain:
        # 補齊三個字節,每個字節補充 0000 0000
        remain_part = ''.join(base64_bytes[3 * nums:]) + (3 - remain) * '0' * 8
        # 取三個字節,以每6比特,轉換為4個整數
        # 剩余1字節可構造2個base64字符,補充==;剩余2字節可構造3個base64字符,補充=
        tmp_unit = [int(remain_part[x: x + 6], 2) for x in [0, 6, 12, 18]][:remain + 1]
        resp += ''.join([base64_charset[i] for i in tmp_unit]) + (3 - remain) * '='

    return resp


def decode(base64_str):
    """
    解碼base64字符串
    :param base64_str:base64字符串
    :return:解碼后的bytearray;若入參不是合法base64字符串,返回空bytearray
    """
    if not valid_base64_str(base64_str):
        return bytearray()

    # 對每一個base64字符取下標索引,并轉換為6為二進制字符串
    base64_bytes = ['{:0>6}'.format(str(bin(base64_charset.index(s))).replace('0b', '')) for s in base64_str if
                    s != '=']
    resp = bytearray()
    nums = len(base64_bytes) // 4
    remain = len(base64_bytes) % 4
    integral_part = base64_bytes[0:4 * nums]

    while integral_part:
        # 取4個6位base64字符,作為3個字節
        tmp_unit = ''.join(integral_part[0:4])
        tmp_unit = [int(tmp_unit[x: x + 8], 2) for x in [0, 8, 16]]
        for i in tmp_unit:
            resp.append(i)
        integral_part = integral_part[4:]

    if remain:
        remain_part = ''.join(base64_bytes[nums * 4:])
        tmp_unit = [int(remain_part[i * 8:(i + 1) * 8], 2) for i in range(remain - 1)]
        for i in tmp_unit:
            resp.append(i)

    return resp


def valid_base64_str(b_str):
    """
    驗證是否為合法base64字符串
    :param b_str: 待驗證的base64字符串
    :return:是否合法
    """
    if len(b_str) % 4:
        return False

    for m in b_str:
        if m not in base64_charset:
            return False
    return True


if __name__ == '__main__':
    s = '我的目標是星辰大海. One piece, all Blue'.encode()
    local_base64 = encode(s)
    print('使用本地base64加密:', local_base64)
    b_base64 = base64.b64encode(s)
    print('使用base64加密:', b_base64.decode())

    print('使用本地base64解密:', decode(local_base64).decode())
    print('使用base64解密:', base64.b64decode(b_base64).decode())

5. 中文的base64編碼

其實base64編碼只是在二進制與base64字符集之間映射的編碼,與其他字符集毫無關系。其他字符集想要轉換為base64編碼,只需先將其轉換為二進制,再做base64編碼即可。

那么對于Unicode字符集而言,有多種編碼方式將其裝換為二進制,所以在編碼過程中就需要統一編碼,以免造成亂碼。上述Python示例就將中文轉換為base64,首先使用默認編碼utf-8將字符串轉換為二進制(使用Python的str.encode()),再做base64編碼;解碼時候同樣如此,先將base64字符串解碼為二進制,再將二進制轉換為字符串(使用Python的str.decode()

6. 參考資料

  1. 維基百科-Base64
  2. 百度百科-ASCII
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內容