6. Base 32 Encoding (Base32编码)
以下对Base32的描述源自 [11] (已更正)。此编码可称为"base32"。
Base32编码旨在以不区分大小写但不需要人类可读的形式表示任意八位字节序列。
编码原理
使用US-ASCII的33个字符子集,使每个可打印字符能够表示5位。(额外的第33个字符"="用于表示特殊处理功能。)
编码过程将40位输入位组表示为8个编码字符的输出字符串。从左到右进行,通过连接5个8位输入组形成40位输入组。然后将这40位视为8个连接的5位组,每个5位组被转换为base32字母表中的单个字符。当通过base32编码对位流进行编码时,必须假定位流按最高有效位优先的顺序排列。也就是说,流中的第一位将是第一个8位字节中的高阶位,第八位将是第一个8位字节中的低阶位,依此类推。
Base32字母表
表3: Base32字母表
值 编码 值 编码 值 编码 值 编码
0 A 9 J 18 S 27 3
1 B 10 K 19 T 28 4
2 C 11 L 20 U 29 5
3 D 12 M 21 V 30 6
4 E 13 N 22 W 31 7
5 F 14 O 23 X
6 G 15 P 24 Y 填充 =
7 H 16 Q 25 Z
8 I 17 R 26 2
字符集组成
A-Z: 26个大写字母 (值 0-25)
2-7: 6个数字 (值 26-31)
=: 等号 (填充字符)
注意: 不包含 0, 1, 8, 9
为什么选择这些字符?
避免混淆:
❌ 0 (零) - 容易与 O (字母O) 混淆
❌ 1 (一) - 容易与 I (字母I) 和 l (小写L) 混淆
❌ 8 - 在某些字体中与 B 相似
❌ 9 - 在某些字体中与 g 相似
✅ 使用 2-7 - 清晰易辨
编码过程详解
基本原理 (5字节 → 8字符)
输入: 5个八位字节 (40位)
输出: 8个字符 (8 × 5位 = 40位)
示例: "Hello" (5字节)
H = 0x48 = 01001000
e = 0x65 = 01100101
l = 0x6C = 01101100
l = 0x6C = 01101100
o = 0x6F = 01101111
合并成40位:
01001000 01100101 01101100 01101100 01101111
分割成8个5位组:
01001 00001 10010 10110 11000 11011 00011 01111
9 1 18 22 24 27 3 15
↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓
J B S W Y 3 D P
输出: "JBSWY3DP"
详细步骤
步骤1: 每5字节分组
输入: [byte1] [byte2] [byte3] [byte4] [byte5]
步骤2: 转换为40位
01001000 01100101 01101100 01101100 01101111
步骤3: 按5位分割
01001|00001|10010|10110|11000|11011|00011|01111
步骤4: 查表转换
每个5位值 (0-31) 对应一个字符
填充处理
由于所有base32输入都是整数个八位字节,因此只会出现以下情况:
情况1: 输入是40位的整数倍 (5字节的倍数)
输入: 5字节 (或10、15、20字节...)
输出: 8字符 (或16、24、32字符...)
填充: 无
示例: "Hello" (5字节)
输出: "JBSWY3DP" (8字符,无填充)
情况2: 最后的量子正好是8位 (1字节)
输出将是2个字符,后跟6个"="填充字符。
示例: "H" (1字节)
H = 0x48 = 01001000
分割: 01001|000[00] (补3个零位)
9 0
↓ ↓
J A
输出: "JA======" (2字符 + 6个填充)
情况3: 最后的量子正好是16位 (2字节)
输出将是4个字符,后跟4个"="填充字符。
示例: "He" (2字节)
H = 0x48 = 01001000
e = 0x65 = 01100101
合并: 0100100001100101
分割: 01001|00001|1001[0] (补1个零位)
9 1 18
↓ ↓ ↓
J B S (第4个字符由最后1位+补0组成)
输出: "JBSQ====" (4字符 + 4个填充)
情况4: 最后的量子正好是24位 (3字节)
输出将是5个字符,后跟3个"="填充字符。
示例: "Hel" (3字节)
输出: "JBSWY===" (5字符 + 3个填充)
情况5: 最后的量子正好是32位 (4字节)
输出将是7个字符,后跟1个"="填充字符。
示例: "Hell" (4字节)
输出: "JBSWY3A=" (7字符 + 1个填充)
填充规则总结
| 输入字节数 | 输出字符数 | 填充字符数 | 总长度 |
|---|---|---|---|
| 1 | 2 | 6 | 8 |
| 2 | 4 | 4 | 8 |
| 3 | 5 | 3 | 8 |
| 4 | 7 | 1 | 8 |
| 5 | 8 | 0 | 8 |
| 6 | 10 | 6 | 16 |
| ... | ... | ... | ... |
规律: 输出总是8的倍数
编码算法实现
def base32_encode(data):
"""Base32编码算法"""
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
result = []
# 处理完整的5字节组
for i in range(0, len(data), 5):
group = data[i:i+5]
# 转换为40位整数
value = 0
for byte in group:
value = (value << 8) | byte
# 补齐到40位
value <<= (5 - len(group)) * 8
# 提取8个5位组
chars = []
for j in range(8):
if j < (len(group) * 8 + 4) // 5:
idx = (value >> (35 - j * 5)) & 0x1F
chars.append(alphabet[idx])
else:
chars.append('=')
result.extend(chars)
return ''.join(result)
实际应用场景
TOTP (基于时间的一次性密码)
Google Authenticator使用Base32编码密钥:
原始密钥 (字节):
[0x48, 0x65, 0x6C, 0x6C, 0x6F]
Base32编码:
JBSWY3DP
用户输入: JBSWY3DP
(不区分大小写,易于人工输入)
文件完整性校验
Git使用Base32的变体 (Base32Hex) 来表示对象ID
Base32优势:
- 不区分大小写
- 避免 0/O 和 1/I/l 混淆
- 适合口头传达
Crockford Base32
一种Base32变体,用于人类友好的标识符:
字母表: 0-9, A-Z (去除 I, L, O, U)
特点:
- 不区分大小写
- 自动纠错 (I→1, L→1, O→0)
- 适合手工输入
用途: 订单号、会话ID等
Base32 vs Base64
| 特性 | Base32 | Base64 |
|---|---|---|
| 字符数 | 32 | 64 |
| 每字符位数 | 5位 | 6位 |
| 膨胀率 | 60% | 33% |
| 大小写 | 不区分 | 区分 |
| 人类友好 | ✅ 较好 | ❌ 较差 |
| 效率 | ❌ 较低 | ✅ 较高 |
| 混淆字符 | ✅ 避免 | ❌ 存在 |
选择建议
使用Base32:
✅ 需要人工输入/传达
✅ 不区分大小写的环境
✅ 避免字符混淆很重要
✅ TOTP/2FA密钥
使用Base64:
✅ 机器处理
✅ 存储/传输效率重要
✅ 电子邮件附件
✅ JWT令牌
示例对照
示例1: 短字符串
输入: "A"
十六进制: 0x41
二进制: 01000001
Base32:
01000|001[00] (补零)
8 4
↓ ↓
I E
输出: "IE======"
示例2: 中等长度
输入: "Test"
十六进制: 0x54 0x65 0x73 0x74
Base32: "KRSXG5A="
输入: "Testing"
十六进制: 0x54 0x65 0x73 0x74 0x69 0x6E 0x67
Base32: "KRSXG5BAOJSXC==="
示例3: 与Base64对比
输入: "Hello, World!"
Base64:
"SGVsbG8sIFdvcmxkIQ==" (22字符)
Base32:
"JBSWY3DPEBLW64TMMQQQ====" (32字符)
长度比: 32/22 ≈ 1.45倍
解码注意事项
大小写处理
def base32_decode(encoded):
"""Base32解码 - 大小写不敏感"""
# 转换为大写
encoded = encoded.upper()
# 移除填充
encoded = encoded.rstrip('=')
# 解码...
错误检测
非法字符检测:
alphabet = set('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567')
for char in encoded.upper():
if char not in alphabet and char != '=':
raise ValueError(f"非法字符: {char}")
常见错误
❌ 将数字0输入为字母O
❌ 将数字1输入为字母I
❌ 混用大小写(虽然不影响,但不标准)
❌ 忘记填充(某些实现要求填充)
性能特性
输出长度计算
输入字节数: n
输出字符数: ⌈8n/5⌉ (向上取整到8的倍数)
示例:
1字节 → 8字符 (800% 膨胀)
5字节 → 8字符 (60% 膨胀)
10字节 → 16字符 (60% 膨胀)
100字节 → 160字符 (60% 膨胀)
平均膨胀率: 60%
Base32 vs Base64 效率
相同数据编码:
原始: 100字节
Base32: 160字符 (160字节)
Base64: 136字符 (136字节)
Base32比Base64大: 160/136 ≈ 17.6%
但Base32的优势:
- 不区分大小写
- 人类更易读
- 避免字符混淆
相关规范
- RFC 4648 §7 - Base32Hex变体
- RFC 6238 - TOTP (使用Base32)
- Crockford Base32 - 人类友好的Base32变体