SM3 密码杂凑算法深度解析:从 Merkle-Damgård 结构到安全性证明

算法原理 · 2026-08-02

SM3 是我国商用密码标准中的密码杂凑算法,输出长度 256 比特,本质上是与 SHA-256 同级别的安全哈希函数。但二者在内部结构、常量设计和安全性边界上存在显著差异。本文从零拆解 SM3 的完整算法流程,结合 GM/T 0004-2012 标准原文,分析其设计思想与安全特性。


一、算法总览:输入输出与标准体系

SM3 将任意长度的消息映射为 256 比特(32 字节) 的杂凑值,适用于数字签名、消息认证码、随机数生成等场景。

标准映射

标准编号名称定位
GM/T 0004-2012SM3 密码杂凑算法算法核心规范(密码行业标准)
GB/T 32905-2016信息安全技术 SM3 密码杂凑算法国家标准(公开出版物)

基础性质

  • 抗碰撞性:找到两个不同消息产生相同哈希值的计算复杂度为 $2^{128}$(生日攻击下限)
  • 抗原像性:给定哈希值 $H$,找原消息 $M$ 使 $H(M)=H$ 的复杂度为 $2^{256}$
  • 雪崩效应:输入 1 比特变化导致输出约 50% 比特翻转
  • 设计范式:Merkle-Damgård 迭代结构(与 SHA-256、MD5 同族)

二、消息填充:任意长度 → 512 比特分组

SM3 的填充规则与 SHA-256 一致,确保消息长度对齐到 512 比特(64 字节) 的整数倍。

填充步骤

CODE
步骤1: 附加比特 '1'(0x80)
步骤2: 填充 k 个 '0' 比特,使消息长度 ≡ 448 (mod 512)
步骤3: 附加 64 比特,表示原始消息长度的二进制(大端序)

示例:消息 "abc"

CODE
原始消息: 0x61 0x62 0x63(24 比特 = 3 字节)
填充后(64 字节 = 512 比特):
  61 62 63 80 00 00 ... 00 00 18
  └─原始─┘ ↑  └──── 43 个 0x00 ────┘  └长度─┘
           0x80 = 1000 0000b               24 = 0x18(原始长度)

验证:消息 "abc" 的填充过程:

  • 原始:3 字节 = 24 比特
  • 附加 0x80(1 字节)+ 52 字节 0x00 = 53 字节
  • 附加长度 8 字节 = 8 字节
  • 总计:3 + 53 + 8 = 64 字节 = 512 比特 ✓
计算零字节数:
  • 要求:$(3 + 1 + k + 8) \times 8 \equiv 0 \pmod{512}$
  • $(12 + k) \times 8 = 512 \Rightarrow k = 52$
标准测试向量(GM/T 0004-2012 附录 A):

输入SM3 输出
""(空字符串)1ab21d83 55cfa17f 8e611948 31e81a8f 22bec8c7 28fefb74 7ed035eb 5082aa2b
"abc"66c7f0f4 62eeedd9 d1f2d46b dc10e4e2 4167c487 5cf2f7a2 297da02b 8f4ba8e0
"abcd" × 16(64 字节)debe9ff9 2275b8a1 38604889 c18e5a4d 6fdb70e5 387e5765 293dcba3 9c0c5732
"abcd" × 1,000,000(4MB)507d91ff 0f89981a eba2b582 dd198721 eeeddb35 3fa0260e 04d59223 35caf653

三、压缩函数:512 比特 → 256 比特的核心变换

SM3 的核心是压缩函数 $CF$,它将当前的 256 比特链变量 $CV^{(i)}$ 与 512 比特消息分组 $B^{(i)}$ 混合,输出新的链变量 $CV^{(i+1)}$。

$$CV^{(i+1)} = CF(CV^{(i)}, B^{(i)})$$

3.1 消息扩展(Message Expansion)

512 比特的消息分组首先被扩展为 132 个字(word,32 比特),供压缩函数使用:

CODE
步骤1: 将 512 比特分为 16 个字 W[0]..W[15](每字 32 比特)
步骤2: 计算 W[16]..W[67]:
   for j = 16 to 67:
     W[j] = P1(W[j-16] XOR W[j-9]) XOR (W[j-3] <<< 15) XOR W[j-13]
步骤3: 并行计算 W'[0]..W'[63]:
   for j = 0 to 63:
     W'[j] = W[j] XOR W[j+4]

其中:

  • <<< k 表示循环左移 k 比特
  • P1(x) = x XOR (x <<< 15) XOR (x <<< 23)(置换函数)
设计思想:W 数组的递推确保输出的每一位都依赖输入的多个比特,实现快速雪崩;W' 与 W 的异或进一步混合,增加差分传播难度。

3.2 布尔函数(Boolean Functions)

SM3 使用两个布尔函数,在不同轮次执行不同的非线性操作:

$$ FF_j(X,Y,Z) = \begin{cases} X \oplus Y \oplus Z & 0 \le j \le 15 \\ (X \wedge Y) \vee (X \wedge Z) \vee (Y \wedge Z) & 16 \le j \le 63 \end{cases} $$

$$ GG_j(X,Y,Z) = \begin{cases} X \oplus Y \oplus Z & 0 \le j \le 15 \\ (X \wedge Y) \vee (\neg X \wedge Z) & 16 \le j \le 63 \end{cases} $$

与 SHA-256 对比:

函数SM3(0-15轮)SM3(16-63轮)SHA-256
FF$X \oplus Y \oplus Z$(线性)多数函数 $Maj(X,Y,Z)$$Ch(X,Y,Z) = (X \land Y) \oplus (\neg X \land Z)$
GG$X \oplus Y \oplus Z$(线性)$IF(X,Y,Z) = (X \land Y) \vee (\neg X \land Z)$$Maj(X,Y,Z) \oplus ...$
关键差异:SM3 在 0-15 轮使用线性 XOR,16-63 轮切换为非线性操作。SHA-256 则始终使用非线性函数。这种"先线性后非线性"的设计需要特别注意差分攻击的累积效应。

3.3 置换函数(Permutation)

SM3 使用两个置换函数实现比特级扩散:

$$ P_0(X) = X \oplus (X \lll 9) \oplus (X \lll 17) $$ $$ P_1(X) = X \oplus (X \lll 15) \oplus (X \lll 23) $$

其中 <<< k 表示 32 位字的循环左移。

置换矩阵分析:

  • $P_0$ 的移位量 (9, 17) 确保相邻比特在三次 XOR 后充分混合
  • $P_1$ 的移位量 (15, 23) 在消息扩展中产生长距离依赖
  • 两者的循环移位距离差为 8,确保低频和高频分量均被扩散

3.4 单轮变换(64 步迭代)

压缩函数的核心是 64 步迭代,每步更新 8 个 32 比特寄存器 A-H:

3.5 轮常量 Tj

每轮使用不同的常量,打破对称性:

$$ T_j = \begin{cases} \text{79cc4519} & 0 \le j \le 15 \\ \text{7a879d8a} & 16 \le j \le 63 \end{cases} $$

设计分析:

  • Tj 在 0-15 轮和 16-63 轮分别取固定值,轮内通过循环移位 <<< j 产生变化
  • 这种"常量 + 循环移位"的设计既保证每轮差异化,又避免了 64 个独立常量的存储开销

四、初始值(IV)与输出变换

初始向量 IV

SM3 的初始链变量为固定常量:

$$ IV = \text{7380166f 4914b2b9 172442d7 da8a0600 a96f30bc 163138aa e38dee4d b0fb0e4e} $$

这是标准的"nothing-up-my-sleeve"数,通过特定数学常数导出,确保无后门。

输出变换

最终所有消息分组处理完毕后,链变量直接作为哈希输出:

$$H = CV^{(N)} = (A \parallel B \parallel C \parallel D \parallel E \parallel F \parallel G \parallel H)$$

与 MD5/SHA-1 的区别:SM3 没有额外的输出变换(如 MD5 的逆序输出),直接拼接。


五、安全性分析

5.1 抗碰撞攻击

生日攻击下限:$2^{128}$ 次运算(256 比特输出)

已知攻击结果(公开文献):

  • 截至 2024 年,SM3 完整 64 轮的抗碰撞攻击尚未公开,无实际威胁
  • 已有学术研究集中在简化轮次(约 20-30 轮)的碰撞/近碰撞攻击,使用差分分析、多差分分析等技术
  • 这些攻击仅针对轮次缩减版本,不影响实际部署的安全性

5.2 与 SHA-256 的安全对比

维度SM3SHA-256
抗碰撞$2^{128}$(生日界限)$2^{128}$
抗原像$2^{256}$$2^{256}$
抗第二原像$2^{256}$$2^{256}$
差分传播前 16 轮线性区可能积累差分始终非线性
代数攻击抵抗结构复杂,难以建模结构较简单
公开密码分析相对较少(2012 年发布)广泛研究(2001 年发布)
关键差异:SM3 的"先线性后非线性"结构在理论上不如 SHA-256 的全程非线性,但实际安全性依赖于:
  • 后 48 轮的非线性操作足以消除前 16 轮积累的差分
  • 消息扩展的强扩散性
  • 置换函数 $P_0$ 的有效比特混合

5.3 侧信道抵抗

SM3 的纯比特操作(XOR、AND、移位)天然抵抗时序攻击,无查表操作(不像 AES/S-box),因此无需额外防护。


六、工程实现与性能

6.1 Python 实现(gmssl)

PYTHON
from gmssl.sm3 import sm3_hash
from gmssl.func import bytes_to_list, list_to_bytes

def sm3_hash_bytes(data: bytes) -> bytes:
    """计算 SM3 哈希值"""
    hash_hex = sm3_hash(bytes_to_list(data))
    return bytes.fromhex(hash_hex)

# 测试向量验证
assert sm3_hash_bytes(b"abc") == bytes.fromhex(
    "66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0"
)

6.2 性能参考

在典型 x86-64 处理器(Intel i7-10700)上:

算法吞吐量(MiB/s)每字节时钟周期
SM3 (gmssl Python)~50~1200
SM3 (C/openssl)~200-300~150-200
SHA-256 (openssl)~350-400~100-130
注意:Python 实现比 C 慢 5-7 倍,生产环境应使用国密硬件加速或 C 扩展。


七、标准符合性与密评要求

7.1 密评场景中的 SM3 用途

在 GB/T 22239 等保三级和 GM/T 0054-2018 中,SM3 应用于:

  • 完整性校验:数据/配置文件的杂凑保护
  • 数字签名:SM2 with SM3(OID: 1.2.156.10197.1.501)
  • 消息认证码:HMAC-SM3
  • 随机数生成:GM/T 0005-2012 要求使用 SM3 进行随机性检测后处理

7.2 常见误用

误用问题正确做法
SHA-256 代替 SM3国密改造不合规使用 SM3 或 HMAC-SM3
SM3(password) 存储密码无 salt,易受彩虹表攻击SM3(salt + password) 或专用 KDF
使用弱随机源作为输入哈希输出可预测使用 GM/T 0005-2012 合规随机源

八、总结

SM3 作为国密体系的哈希基石,其设计融合了 Merkle-Damgård 结构的成熟性和国产密码标准的自主性。理解 SM3 的内部构造(消息扩展、布尔函数、置换操作)不仅有助于正确实现,更能帮助工程师在密评场景中准确评估哈希使用的合规性。

核心要点:

  • SM3 输出 256 比特,抗碰撞 $2^{128}$
  • 压缩函数使用 64 轮迭代,布尔函数分前后两段设计
  • 消息扩展通过 $W$ 和 $W'$ 两轮扩散实现强雪崩
  • 与 SHA-256 安全性相当,但密码分析公开程度较低
  • 密评场景必须使用 SM3,禁止用 SHA-256 替代

相关实践:

参考标准:
  • GM/T 0004-2012《SM3 密码杂凑算法》
  • GB/T 32905-2016《信息安全技术 SM3 密码杂凑算法》