Skip to main content

4. Base 64 Encoding (Base64编码)

以下对Base64的描述源自 [3]、[4]、[5] 和 [6]。此编码可称为"base64"。

Base64编码旨在以允许使用大写和小写字母但不需要人类可读的形式表示任意八位字节序列。


编码原理

使用US-ASCII的65个字符子集,使每个可打印字符能够表示6位。(额外的第65个字符"="用于表示特殊处理功能。)

编码过程将24位输入位组表示为4个编码字符的输出字符串。从左到右进行,通过连接3个8位输入组形成24位输入组。然后将这24位视为4个连接的6位组,每个6位组被转换为base64字母表中的单个字符。

每个6位组用作64个可打印字符数组的索引。索引引用的字符被放入输出字符串。


Base64字母表

表1: Base64字母表

值  编码  值  编码  值  编码  值  编码
0 A 17 R 34 i 51 z
1 B 18 S 35 j 52 0
2 C 19 T 36 k 53 1
3 D 20 U 37 l 54 2
4 E 21 V 38 m 55 3
5 F 22 W 39 n 56 4
6 G 23 X 40 o 57 5
7 H 24 Y 41 p 58 6
8 I 25 Z 42 q 59 7
9 J 26 a 43 r 60 8
10 K 27 b 44 s 61 9
11 L 28 c 45 t 62 +
12 M 29 d 46 u 63 /
13 N 30 e 47 v
14 O 31 f 48 w 填充 =
15 P 32 g 49 x
16 Q 33 h 50 y

字符集组成

A-Z: 26个大写字母 (值 0-25)
a-z: 26个小写字母 (值 26-51)
0-9: 10个数字 (值 52-61)
+: 加号 (值 62)
/: 斜杠 (值 63)
=: 等号 (填充字符)

编码过程详解

步骤1: 分组 (3字节 → 24位)

输入: 3个八位字节 (字节1, 字节2, 字节3)

示例: "Man"
M = 0x4D = 01001101
a = 0x61 = 01100001
n = 0x6E = 01101110

连接成24位:
01001101 01100001 01101110

步骤2: 分割 (24位 → 4个6位组)

24位:
010011 | 010110 | 000101 | 101110
19 22 5 46

查表:
19 → T
22 → W
5 → F
46 → u

输出: "TWFu"

完整示例

输入: "Man"

二进制表示:
M: 01001101
a: 01100001
n: 01101110

合并: 010011010110000101101110

分割: 010011 010110 000101 101110
↓ ↓ ↓ ↓
19 22 5 46
↓ ↓ ↓ ↓
T W F u

输出: "TWFu"

填充处理

如果在被编码数据的末尾可用位少于24位,则执行特殊处理。在一个量的末尾总是完成一个完整的编码量子。当输入组中可用的输入位少于24位时,添加值为零的位(在右侧)以形成整数个6位组。使用'='字符在数据末尾执行填充。由于所有base64输入都是整数个八位字节,因此只会出现以下情况:

情况1: 输入是24位的整数倍

输入: 3字节 (或6字节、9字节...)
输出: 4字符 (或8字符、12字符...)
填充: 无

示例: "Man" (3字节)
输出: "TWFu" (4字符,无填充)

情况2: 最后的量子正好是8位 (1字节)

最后的编码单元将是2个字符,后跟2个"="填充字符。

示例: "M" (1字节)

M = 0x4D = 01001101

分割为6位组 (需要补零):
010011 01[0000] (最后4位补0)
19 16
↓ ↓
T Q

输出: "TQ==" (2字符 + 2个填充)

情况3: 最后的量子正好是16位 (2字节)

最后的编码单元将是3个字符,后跟1个"="填充字符。

示例: "Ma" (2字节)

M = 0x4D = 01001101
a = 0x61 = 01100001

合并: 0100110101100001

分割为6位组 (需要补零):
010011 010110 0001[00] (最后2位补0)
19 22 4
↓ ↓ ↓
T W E

输出: "TWE=" (3字符 + 1个填充)

编码算法伪代码

def base64_encode(data):
"""Base64编码算法"""
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
result = []

# 处理完整的3字节组
for i in range(0, len(data), 3):
group = data[i:i+3]

if len(group) == 3:
# 完整的3字节
b1, b2, b3 = group[0], group[1], group[2]

# 转换为4个6位索引
idx1 = (b1 >> 2) & 0x3F
idx2 = ((b1 & 0x03) << 4) | ((b2 >> 4) & 0x0F)
idx3 = ((b2 & 0x0F) << 2) | ((b3 >> 6) & 0x03)
idx4 = b3 & 0x3F

result.append(alphabet[idx1])
result.append(alphabet[idx2])
result.append(alphabet[idx3])
result.append(alphabet[idx4])

elif len(group) == 2:
# 2字节 + 填充
b1, b2 = group[0], group[1]

idx1 = (b1 >> 2) & 0x3F
idx2 = ((b1 & 0x03) << 4) | ((b2 >> 4) & 0x0F)
idx3 = ((b2 & 0x0F) << 2) # 补零

result.append(alphabet[idx1])
result.append(alphabet[idx2])
result.append(alphabet[idx3])
result.append('=')

elif len(group) == 1:
# 1字节 + 填充
b1 = group[0]

idx1 = (b1 >> 2) & 0x3F
idx2 = ((b1 & 0x03) << 4) # 补零

result.append(alphabet[idx1])
result.append(alphabet[idx2])
result.append('==')

return ''.join(result)

解码算法伪代码

def base64_decode(encoded):
"""Base64解码算法"""
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

# 创建反向查找表
decode_table = {char: idx for idx, char in enumerate(alphabet)}

result = []

# 移除填充
encoded = encoded.rstrip('=')
padding = 4 - (len(encoded) % 4) if len(encoded) % 4 != 0 else 0

# 处理每4个字符
for i in range(0, len(encoded), 4):
chunk = encoded[i:i+4]

# 转换为索引
indices = [decode_table.get(c, 0) for c in chunk]

# 转换为字节
if len(indices) >= 2:
b1 = (indices[0] << 2) | (indices[1] >> 4)
result.append(b1)

if len(indices) >= 3:
b2 = ((indices[1] & 0x0F) << 4) | (indices[2] >> 2)
result.append(b2)

if len(indices) >= 4:
b3 = ((indices[2] & 0x03) << 6) | indices[3]
result.append(b3)

return bytes(result)

实际应用示例

示例1: 短字符串

输入: "A"
二进制: 01000001
分组: 010000 01[0000]
索引: 16, 16
输出: "QQ=="

示例2: 中等长度

输入: "Hello"
H: 01001000
e: 01100101
l: 01101100
l: 01101100
o: 01101111

处理:
组1: 01001000 01100101 01101100 → "SGVs"
组2: 01101100 01101111 [补零] → "bG8="

输出: "SGVsbG8="

示例3: 二进制数据

输入: [0xFF, 0x00, 0xAA, 0x55]
二进制:
11111111 00000000 10101010 01010101

组1: 11111111 00000000 10101010 → "/wCq"
组2: 01010101 [补零] → "VQ=="

输出: "/wCqVQ=="

性能考虑

输出长度计算

输入字节数: n
输出字符数: ⌈4n/3⌉ (向上取整到4的倍数)

示例:
1字节 → 4字符 (效率: 25%)
2字节 → 4字符 (效率: 50%)
3字节 → 4字符 (效率: 75%)
6字节 → 8字符 (效率: 75%)
100字节 → 136字符 (效率: ~73.5%)

平均膨胀率: 约33%

优化建议

1. 批量处理
- 一次处理多个3字节组
- 减少函数调用开销

2. 查表法
- 预先计算查找表
- 避免重复计算

3. 位操作
- 使用位移和掩码
- 比乘除法更高效

4. 向量化
- SIMD指令
- 并行处理多个字节

常见错误

❌ 错误1: 填充位未清零

# 错误
def encode_wrong(b):
idx = (b & 0x03) << 4 # 可能包含垃圾位
return alphabet[idx]

# 正确
def encode_correct(b):
idx = ((b & 0x03) << 4) | 0 # 确保填充位为0
return alphabet[idx]

❌ 错误2: 忘记添加填充

# 错误 - 忘记填充
"SGVsbG8" # 缺少 "="

# 正确
"SGVsbG8=" # 包含填充

❌ 错误3: 错误处理非字母字符

# 错误 - 静默忽略
def decode_wrong(s):
return decode(s.replace(' ', '')) # 不应该接受空格

# 正确 - 拒绝非法输入
def decode_correct(s):
if not all(c in alphabet or c == '=' for c in s):
raise ValueError("非法字符")
return decode(s)

安全注意事项

1. 时序攻击
- 比较时使用常量时间算法
- 避免提前退出

2. 缓冲区溢出
- 验证输入长度
- 预分配足够的输出空间

3. 整数溢出
- 检查长度计算
- 使用安全的算术运算

4. 内存泄漏
- 处理敏感数据后清零内存
- 避免将密钥留在内存中

相关标准

  • RFC 2045 - MIME Part One (定义Base64用于MIME)
  • RFC 3548 - Base Encodings (被本RFC废弃)
  • RFC 4648 §5 - Base64URL变体
  • RFC 7515 - JWS (使用Base64URL)