Python 搞定Base64 编码/ 解码

Base64 编码核心思想 :

  • 每3个字节断开, 拿出一个3字节, 每6个断开, 成4段
  • 因为每个字节其实只占了6位, 2**6 = 64, 因此有了base64的编码表
  • 每一位当做一个8位看它的值, 这个值就是Base64编码表的索引值(0 ~ 63), 找到对应的字符

下面我们来考虑以下情况:

  • 一个字节能变成几个Base64 的字节 ?
  • 两个字节能变成几个Base64 的字节 ?
  • 字符串 ‘`’ 反引号如何处理 ?

根据上图编码方式不难总结 : ASCII 码一字节可以变成Base64编码2字节, 两字节变成3 字节, 反引号是1字节, 8位被断开, 前6位一组, 余下后2 位
末尾的处理 :

  • 正好是3自己处理方式同上
  • 剩下1 字节或者 2个字节, 用0 补满3 个字节
  • 全部补0 的字节用 “=” 表示
  • 但是这里注意一下, 补零的特殊情况, 例如只有一字节(ASCII码), 需要用0 再补两字节, 这样可以正好转换成Base64编码4 字节, 末尾两字节是后补的0 可以正常用 “=” 补齐(上图中n3, n4 位置), 但是注意n2 位置如果有b1 位置余下的 "0"和后来补齐的零是不同的0, 就不能直接用 "="表示了, 需对应Base64 编码表里的0 索引表示, 也就是 A

代码设计

alphabet = b'ABCDEFGHIJKLMNOPORSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'

def base64code(src:str):
    ret = bytearray()
    if isinstance(src, str):
        _src = src.encode()
    else:
        return
    lenth = len(_src)   # bytes

    offset = 0
    for offset in range(0, lenth, 3):	# 三位一组
        triple = _src[offset:offset+3]

        r = 3 - len(triple)
        if r:	# 不足三位补0
            triple += b'\x00'*r

        x = int.from_bytes(triple, 'big')	# 这里选用的大端模式

        for i in range(18, -1, -6):	# 二进制情况下向右位移18, 12, 6, 0 四种情况的到我们想要的部分
            index = x >> i if i == 18 else x >> i & 0x3F	# 与0x3F 只要目标位 
            ret.append(alphabet[index])

        # for j in range(r):	#	和下面if语句一样, 置换等号
        #     ret[-j-1] = 61
        if r:
            ret[-r:] = b'='*r
            
    return bytes(ret)
    
show_result = base64code('e')
print(show_result)
show_result = base64code('ef')
print(show_result)
show_result= base64code('efg')
print(show_result)
show_result = base64code('`')
print(show_result)

运行结果

b'ZO=='
b'ZWY='
b'ZWZn'
b'YA=='

Base64 解码码思路:

  • 与编码思路基本一致, 首先判断bytes 的长度是否是4 的倍数, 如果是继续
  • a 编码后是YQ== , 先查Y 找到对应的索引24, 这个数要放到最高位, Q 为16 放到次高位, = 找不到就是0 凑够6位, 最终凑成一个24 位数, 追加到一个bytearray 中去

对应关系如下

YWJj --> abc

24 - 0x18 - 左移18位

22 - 0x16 - 左移12位

9 - 0x9 - 左移6位

35 - 0x23 - 左移0位

011000 010110 001001 100011

01100001 01100010 01100011

0x-------61-------62 ---------63

----------a----------b------------c


代码初步实现

alphbet = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

def base64decode(src: bytes):
    ret = bytearray()
    lenth = len(src)
    if lenth % 4 != 0:
        return
    step = 4
    for offset in range(0, lenth, step):
        tmp = 0x00
        block = src[offset:offset+step]
        for i, c in enumerate(reversed(block)):
            index = alphbet.find(c)
            if index == -1:
                continue
            tmp += index << i*6
        ret.extend(tmp.to_bytes(3, 'big'))	# 大端模式
    return bytes(ret.rstrip(b'\x00'))	# 把最右边\x00 去掉


txt = "TWFu"
txt = txt.encode()
print(base64decode(txt).decode())

运行结果

Man

改进

  • alphabet.find 效率太差
from collections import OrderedDict

base_tbl = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
alphbet = OrderedDict(zip(base_tbl, range(64)))
def base64decode(src: bytes):
    ret = bytearray()
    lenth = len(src)
    if lenth % 4 != 0:
        return
    step = 4	# 每次取4 个
    for offset in range(0, lenth, step):
        tmp = 0x00
        block = src[offset:offset+step]
        for i in range(4):	# 反查表, 从字符到index
            index = alphbet.get(block[-i-1])
            if index == -1:
                continue
            tmp += index << i*6
        ret.extend(tmp.to_bytes(3, 'big'))
    return bytes(ret.rstrip(b'\x00'))

txt = 'TWFu'
txt = txt.encode()
print(base64decode(txt).decode())

运行结果

Man

基本实现了base64 编解码的全部功能, 当然是用直接引用 base64
模块更简单, 这里只是为了了解base64 编解码的大致原理, 希望对看到这篇文章的朋友有所帮助

import base64

base64.b64encode()
base64.b64decode()

你可能感兴趣的:(Python)