Base64#
-
将二进制数据转成 ASCII 字符串 (“binary to text” scheme*)
-
在 CSS 中直接嵌入小图标或者在 Data URI
-
核心: 将字节转换为一个取字符于一个由 64 个可打印字符组成的集合的可打印字符串
然而 64 个字符只能映射 6 位比特,所以使用三个字节分段以便映射到 4 个字符的 base64 的字符串。如果字节数不是 3 的倍数则会出现补位需求,如果字节数模三余一,则二进制需要补 4 个 0;如果字节数模 3 余 2,则二进制需要补 2 个 0。计算过程如下
已知字节数模三余一:
$$
\text{Bytes Number} \equiv 1 \pmod 3
$$由模运算性质可知, 可推知
$$
8 \times \text{Bytes Number} \equiv 2 \pmod 3
$$显然有
$$
8 \times \text{Bytes Number} \equiv 0 \pmod 2
$$我们知道 $gcd(2, 3) = 1$,因此可以根据中国剩余定理(CRT)得到:
$$
\begin{align}
8 \times \text{Bytes Number} &\equiv 8 + 0 \pmod 6 \newline \notag
8 \times \text{Bytes Number} &\equiv 2 \pmod 6 \newline
\end{align}
$$同理,已知字节数模三余二:
$$
\text{Bytes Number} \equiv 2 \pmod 3
$$通过同样的过程可推得:
$$
\begin{align}
8 \times \text{Bytes Number} &\equiv 4 + 0 \pmod 6 \newline \notag
8 \times \text{Bytes Number} &\equiv 4 \pmod 6 \newline
\end{align}
$$所以需要将剩下的比特填充为 6 位进行 base64 编码:
- 字节数模三余一时,需要末尾填充 $6-2=4$ 位二进制
0
- 字节数模三余二时,需要末尾填充 $6-4=2$ 位二进制
0
base64 要求四个为一组, 不足四个的,填充 =。
当字节数无法整除 3 时,生成的未加 padding = 的 base64 字符串也无法整除 4,计算过程如下
$n \coloneqq \text{Bytes Number}$
$m \coloneqq \text{Base64 Output Length without padding '='}$
当 $n \nmid 3$ 时,$n$ 可以取值:
$$
n = 3k + 1 \text{ or } n = 3k + 2
$$相应的 $m$ 为:
$$
m = 4k + \lceil\frac{4}{3}\rceil \text{ or } m = 4k + \lceil\frac{8}{3}\rceil
$$显然 $m$ 无法整除 4, 当字节数 $n\equiv 1 \pmod 3$ 时,$m \equiv 2 \pmod 4$, 填充两个 =;当 $n \equiv 2 \pmod 3$ 时,$m \equiv 3 \pmod 4$, 填充一个 =。
于是通过填充 = 可以告知二进制的填充情况:
- 一个
= $\rightarrow$ 填充了两个零
- 两个
= $\rightarrow$ 填充了四个零
为什么需要填充 =#
RFC4648 说明了这个问题:

通常来说如果传输的数据大小无法被确定,padding(“=”)是需要用来保证生成正确的解码数据的。所以“=”是用于保证传输过程中错误的截断不会导致数据解读错误。
对于标准的带填充的 base64 编码后的数据,我们可以通过读取末尾 = 的数量去确定二进制补位的数量;然而对于省略了 = 的,我们需要计算 base64 字符串模四的余数与四的差确定二进制补位的数量。
Encoding Algorithm (Python 实现):#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
def my_base64_encode(content: str):
_bytes = content.encode('utf-8') # Convert string into bytes
# compute the binary padding and base64 padding
eq_pad_unit = 0
zero_pad_unit = 0
bytes_reminder = len(_bytes) % 3
if bytes_reminder == 1:
eq_pad_unit = 2
zero_pad_unit = 4
elif bytes_reminder == 2:
eq_pad_unit = 1
zero_pad_unit = 2
# binary form
bit_list_8 = [format(byte, '08b') for byte in _bytes]
bit_string_8 = ' '.join(bit_list_8)
print(f"Bit String (Group by 8 length): {bit_string_8}")
bit_string = ''.join(bit_list_8)
print(f"Total {len(bit_string)} bits: need {zero_pad_unit} bits padding")
# add binary padding
bit_string = bit_string + '0' * zero_pad_unit
# split the bit string into several 6 bits
bit_list_6 = [bit_string[slice_point:slice_point+6] for slice_point in range(0, len(bit_string), 6)]
bit_string_6 = ' '.join(bit_list_6)
print(f"Bit String (Group by 6 length): {bit_string_6}")
# do the mapping
result_list = [lookupbtoa_unit(int(unit,base=2)) for unit in bit_list_6]
# add base64 padding
result_list.extend(['='] * eq_pad_unit)
printable_result = ''.join(format(unit, '>6') for unit in result_list)
print(f" {printable_result}")
print('Result of Encoding',''.join(result_list))
return "".join(result_list)
|
Decoding Algorithm (Python 实现):#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
def my_base64_decode(content: str):
# determine the padding setting
eq_pad_unit = 0
for i in reversed(content):
if i == '=':
eq_pad_unit = eq_pad_unit + 1
else:
break
zero_pad_unit = eq_pad_unit * 2
print(f"Find {zero_pad_unit} bits padding")
content = content[0: len(content)-eq_pad_unit] # eliminate the base64 padding
# do the mapping
bit_list_6 = [format(lookupatob_unit(char), '06b') for char in content]
bit_string_6 = ' '.join(bit_list_6)
print(f"Bit String (Group by 6 length): {bit_string_6}")
# recover binary string
bit_string = ''.join(bit_list_6)
bit_string = bit_string[0:len(bit_string)-zero_pad_unit] # eliminate the padding
# split by every 8 bits
bit_list_8 = [bit_string[slice_point:slice_point+8] for slice_point in range(0, len(bit_string), 8)]
bit_string_8 = ' '.join(bit_list_8)
print(f"Bit String (Group by 8 length): {bit_string_8}")
# convert into bytes
result_bytes = bytes(int(literal_bytes, 2) for literal_bytes in bit_list_8)
print("Result of decoding", result_bytes.decode('utf-8'))
return result_bytes
|
字符表查询操作(Python 实现):#
1
2
3
4
5
6
7
8
9
|
RawLookupTable = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
btoaTable = {i: char for i, char in enumerate(RawLookupTable)}
atobTable = {char: i for i, char in enumerate(RawLookupTable)}
def lookupatob_unit(char: str):
return atobTable[char]
def lookupbtoa_unit(num: int):
return btoaTable[num]
|