SM4 分组密码填充模式:PKCS7、ANSI X9.23、ISO/IEC 10126 的原理与实现

算法原理 · 2026-10-11

SM4 是中国国家密码管理局发布的分组密码标准(GB/T 32907-2016),块长度为 128 位(16 字节)。分组密码的工作模式(如 ECB、CBC)要求输入数据长度为块大小的整数倍。当明文长度不满足此条件时,必须使用填充方案。

填充方案是密码学基础组件,其设计必须满足可逆性和唯一性两个核心性质:

  • 可逆性:从填充后的数据能够准确还原原始数据
  • 唯一性:填充方案产生的结果不会与原始数据本身产生歧义

一、填充方案的数学定义

1.1 形式化描述

设 $M$ 为明文消息,$n$ 为块大小(SM4 中 $n = 16$ 字节)。填充函数 $Pad: \{0,1\}^* \rightarrow \{0,1\}^{kn}$ 将任意长度消息映射到块大小的整数倍长度,其中 $k = \lceil |M|/n \rceil + 1$(至少填充一块)。

去填充函数 $Unpad: \{0,1\}^{kn} \rightarrow \{0,1\}^*$ 满足 $Unpad(Pad(M)) = M$。

1.2 安全性要求

填充方案不应泄露明文长度的信息。在理想的填充方案中,密文长度应独立于明文长度(除了块大小对齐带来的最小膨胀)。

二、PKCS7 填充

2.1 算法描述

PKCS7 填充(RFC 5652)是最广泛使用的填充方案,其定义如下:

填充过程:

CODE
令 pad_len = n - (|M| mod n)
若 pad_len = 0,则 pad_len = n
填充字节序列为 pad_len 个值为 pad_len 的字节

形式化定义: $$Pad(M) = M \parallel \underbrace{pad\_len \parallel pad\_len \parallel \cdots \parallel pad\_len}_{pad\_len \text{ 次}}$$

去填充过程:

CODE
读取最后一个字节值为 p
若 1 ≤ p ≤ n,则去除最后 p 个字节
否则,填充无效

2.2 示例演示

以 SM4(n=16)为例:

示例 1:明文长度 12 字节

CODE
明文:     M = "Hello World!" (12 字节)
pad_len  = 16 - (12 mod 16) = 4
填充后:   "Hello World!\x04\x04\x04\x04" (16 字节)

示例 2:明文长度恰好 16 字节

CODE
明文:     M = "Hello World!!!" (16 字节)
pad_len  = 16 - (16 mod 16) = 0 → 实际 pad_len = 16
填充后:   "Hello World!!!" + 16 个 0x10 (32 字节)

示例 3:空消息

CODE
明文:     M = "" (0 字节)
pad_len  = 16 - (0 mod 16) = 0 → 实际 pad_len = 16
填充后:   16 个 0x10 (16 字节)

2.3 安全性分析

PKCS7 填充具有以下性质:

  • 确定性:相同的明文产生相同的填充结果
  • 可逆性:最后一个字节明确指示填充长度,去填充无歧义
  • 长度隐藏:加密后密文长度为 $\lceil |M|/n \rceil \times n$,泄露明文长度的上界
已知攻击:
  • _padding oracle 攻击_:某些实现中,解密端对无效填充返回不同的错误信息,攻击者可利用此侧信道逐字节解密。防护措施包括:统一错误响应、使用 AEAD 模式(如 SM4-GCM)。

三、ANSI X9.23 填充

3.1 算法描述

ANSI X9.23 填充(ANSI X9.23-1998)是 ANSI 标准化的填充方案,主要应用于 IBM 系统和 .NET 框架。

填充过程:

CODE
令 pad_len = n - (|M| mod n)
若 pad_len = 0,则 pad_len = n
前 (pad_len - 1) 个字节填充 0x00
最后一个字节填充 pad_len

形式化定义: $$Pad(M) = M \parallel \underbrace{0x00 \parallel 0x00 \parallel \cdots \parallel 0x00}_{pad\_len - 1 \text{ 次}} \parallel pad\_len$$

去填充过程:

CODE
读取最后一个字节值为 p
若 1 ≤ p ≤ n,则去除最后 p 个字节
否则,填充无效

3.2 示例演示

示例:明文长度 12 字节

CODE
明文:     M = "Hello World!" (12 字节)
pad_len  = 16 - (12 mod 16) = 4
填充后:   "Hello World!\x00\x00\x00\x04" (16 字节)

示例:明文长度恰好 16 字节

CODE
明文:     M = "Hello World!!!" (16 字节)
pad_len  = 16
填充后:   15 个 0x00 + 0x10 (16 字节)

3.3 与 PKCS7 的区别

特性PKCS7ANSI X9.23
填充字节全部为 pad_len前 (pad_len-1) 个为 0x00
最后一字节pad_lenpad_len
实现复杂度低低
跨平台支持广泛中等
两者在安全性上等价,区别仅在于填充内容的分布。PKCS7 的所有填充字节相同,便于硬件实现;ANSI X9.23 的前缀为 0x00,在某些二进制协议中更易检测。

四、ISO/IEC 10126 填充

4.1 算法描述

ISO/IEC 10126 填充(ISO/IEC 10126-2:2007)使用随机字节进行填充,是 ISO 标准化的填充方案。

填充过程:

CODE
令 pad_len = n - (|M| mod n)
若 pad_len = 0,则 pad_len = n
前 (pad_len - 1) 个字节为随机值
最后一个字节为 pad_len

形式化定义: $$Pad(M) = M \parallel R \parallel pad\_len$$ 其中 $R$ 是长度为 $(pad\_len - 1)$ 的随机字节序列。

去填充过程:

CODE
读取最后一个字节值为 p
若 1 ≤ p ≤ n,则去除最后 p 个字节
否则,填充无效

4.2 示例演示

示例:明文长度 12 字节

CODE
明文:     M = "Hello World!" (12 字节)
pad_len  = 4
填充后:   "Hello World!\xA3\x7F\x02\x04" (16 字节)
(假设随机字节为 0xA3, 0x7F, 0x02)

4.3 安全性分析

ISO/IEC 10126 填充的主要优势是随机性:

  • 相同明文产生不同密文:每次填充使用不同的随机字节,即使加密相同明文,密文也不同
  • 抗模式分析:填充字节的随机性增加了密文的熵
  • 防填充预言攻击:由于填充内容不可预测,攻击者难以利用填充结构进行侧信道分析
缺点:
  • 需要安全的随机数生成器
  • 解密时必须丢弃随机填充字节,无法验证原始填充内容

五、Zero Padding(零填充)

5.1 算法描述

Zero Padding 是最简单的填充方案,用 0x00 字节填充至块大小的整数倍。

填充过程:

CODE
令 pad_len = n - (|M| mod n)
若 pad_len = 0,则 pad_len = n
填充 pad_len 个 0x00 字节

5.2 不可逆性问题

Zero Padding 存在根本性的不可逆性问题:

示例:

CODE
情况 1: M = "Hello" (5 字节)
填充后: "Hello\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" (16 字节)

情况 2: M = "Hello\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" (16 字节)
填充后: 同上(32 字节)

解密后无法区分这两种情况。因此,Zero Padding 仅适用于已知明文长度或配合长度前缀的场景。

5.3 适用场景

Zero Padding 适合以下场景:

  • 加密前已附加长度字段(如 TLV 编码)
  • 明文内容已知不包含尾部 0x00
  • 内部协议且双方约定固定长度

六、填充方案的选择原则

6.1 决策矩阵

场景推荐方案理由
TLS/HTTPS 通信PKCS7RFC 5246 标准,广泛支持
数据库字段加密PKCS7 或 ISO/10126PKCS7 兼容性好,ISO/10126 更安全
IoT 嵌入式设备ANSI X9.23实现简单,内存开销小
消息队列(高安全)ISO/10126随机填充,抗模式分析
内部协议(已知长度)Zero Padding最简单,无额外开销

6.2 安全性比较

CODE
安全性排序(从高到低):
ISO/10126 > PKCS7 ≈ ANSI X9.23 > Zero Padding

理由:
- ISO/10126 的随机填充使得相同明文产生不同密文,增加攻击难度
- PKCS7 和 ANSI X9.23 是确定性填充,但广泛应用于标准协议
- Zero Padding 完全不可逆,仅在受控场景使用

6.3 互操作性考虑

不同语言和库对填充方案的支持程度不同:

语言/库PKCS7ANSI X9.23ISO/10126Zero Padding
Python (cryptography)✅✅✅❌
Python (gmssl)✅❌❌✅
Java (Cipher)✅✅✅✅
Go (crypto/cipher)✅❌❌❌
.NET (RijndaelManaged)✅✅✅❌

七、常见实现错误

7.1 填充长度计算错误

错误示例:

PYTHON
# 错误:当明文长度是块大小的倍数时,pad_len 应为 n 而非 0
pad_len = n - (len(data) % n)  # 当 len(data) % n == 0 时,pad_len = 0

正确实现:

PYTHON
pad_len = n - (len(data) % n)
if pad_len == 0:
    pad_len = n

7.2 去填充时未验证填充有效性

错误示例:

PYTHON
# 危险:直接信任最后一个字节,可能被攻击者利用
def unpad(data):
    pad_len = data[-1]
    return data[:-pad_len]

正确实现:

PYTHON
def unpad(data):
    pad_len = data[-1]
    if pad_len < 1 or pad_len > n:
        raise ValueError("Invalid padding")
    # 验证所有填充字节是否符合预期
    if all(b == pad_len for b in data[-pad_len:]):
        return data[:-pad_len]
    raise ValueError("Invalid padding")

7.3 混淆参数单位

在 Python 的 cryptography 库中,PKCS7 构造函数的参数是位数而非字节数:

PYTHON
# 错误:16 字节被误解为 16 位
PKCS7(16)  # ValueError: Invalid key length

# 正确:128 位 = 16 字节
PKCS7(128)  # ✅ SM4 块大小

八、与 SM4 工作模式的配合

填充方案通常与分组密码工作模式配合使用:

8.1 ECB 模式

ECB(Electronic Codebook)模式是最简单的工作模式,每个块独立加密。由于相同的明文块产生相同的密文块,ECB 模式不适合加密结构化数据,但在填充方案的演示中常用。

8.2 CBC 模式

CBC(Cipher Block Chaining)模式通过引入 IV(初始化向量)使相同的明文产生不同的密文,是目前最常用的模式。填充在 CBC 模式中同样必要。

8.3 填充与 AEAD

高级加密模式(如 SM4-GCM、SM4-CCM)是认证加密(AEAD)方案,不需要填充。它们直接处理任意长度的数据,并提供加密和完整性认证。对于新系统设计,推荐优先使用 AEAD 模式。

九、总结

SM4 填充方案的选择应综合考虑以下因素:

  • 兼容性:PKCS7 是最广泛支持的方案,适合大多数场景
  • 安全性:ISO/10126 提供最好的抗侧信道能力
  • 性能:ANSI X9.23 和 PKCS7 实现简单,性能相当
  • 标准遵循:遵循相应行业标准(如 TLS 使用 PKCS7)
在实际工程中,建议:
  • 优先使用成熟的密码学库(如 cryptography、gmssl)
  • 避免自行实现填充逻辑
  • 对于新系统,考虑使用 AEAD 模式(如 SM4-GCM)避免填充问题
相关实践参考:SM4 国密对称加密实战、SM4-GCM 认证加密模式原理。

参考标准

  • GB/T 32907-2016《信息安全技术 SM4 分组密码算法》
  • RFC 5246《The Transport Layer Security (TLS) Protocol Version 1.2》
  • ANSI X9.23-1998《Data Encryption Alphabetical Pad Method》
  • ISO/IEC 10126-2:2007《Information technology — Security techniques — Encryption algorithms — Part 2: Mechanisms using a block cipher algorithm》