HMAC-SM3 消息认证码(MAC)原理:消息完整性与来源验证
概述
HMAC(Hash-based Message Authentication Code,基于哈希的消息认证码)是一种使用密码杂凑函数和共享密钥计算消息认证码的结构化算法,最早由 Krawczyk、Belling 和 Ramachandran 于 1996 年在 RFC 2104 中定义。HMAC 的核心目标是同时提供消息完整性保护和消息来源认证:接收方可以确认消息未被篡改,且确实来自声称的发送方。
在国密体系中,将 HMAC 结构与 SM3 杂凑函数结合形成的 HMAC-SM3 是 TLS 1.3 国密套件(如 TLS_SM4_GCM_SM3)中用于消息认证的核心组件,也是 GM/T 0024-2023《SSL VPN 技术规范》和 GM/T 0129-2023《SSH 密码协议规范》中消息完整性验证的底层原语。
注意:本文聚焦 HMAC-SM3 的密码学原理和结构分析,属于概念/算法类 wiki 文章。如需工程实现指南(含 Python 代码),请参考 blog/sm3-hmac-message-authentication。
背景:为什么需要 HMAC
安全威胁场景
假设 Alice 向 Bob 发送一条金融转账指令:
转账 | 收款人:张三 | 金额:10000 元如果仅通过公钥密码(如 SM2)加密,虽然保障了机密性,但存在以下风险:
- 重放攻击:攻击者截获加密消息后重新发送,Bob 无法区分这是新指令还是旧指令的复制
- 替换攻击:攻击者修改密文(在某些模式下),解密后得到不同的明文
- 伪造消息:攻击者构造一条假消息,Bob 无法验证其来源
HMAC 解决了这个问题:只有持有共享密钥的通信双方才能生成和验证 MAC,第三方无法伪造。
与数字签名的区别
| 特性 | HMAC(对称) | SM2 数字签名(非对称) |
|---|---|---|
| 密钥类型 | 共享对称密钥 | 签发方私钥 / 验证方公钥 |
| 身份模型 | 共享密钥意味着信任通道 | 证书 + PKI 信任链 |
| 性能 | 极快(约 SM2 的 1/100) | 较慢 |
| 适用场景 | 高速内部通信(TLS 数据面) | 身份认证、文档签署 |
| 不可否认性 | 无(双方都可生成) | 有(仅私钥持有者可签名) |
HMAC 密码学原理
结构设计思想
HMAC 采用双重哈希结构,其核心思想是:
- 将密钥与一个内填充值(ipad=0x36 重复)做异或,与消息拼接后做哈希——这一步将密钥"嵌入"消息的初始状态
- 将密钥与一个外填充值(opad=0x5C 重复)做异或,与第一步的哈希结果拼接后再做一次哈希——这一步将前一步的结果"封存"
- 密钥不可逆:即使攻击者知道 HMAC 输出和消息,也无法反推密钥
- 防长度扩展攻击:双重哈希结构阻止了 MD/SHA 系列常见的长度扩展攻击
HMAC 通用公式(RFC 2104)
$$\text{HMAC}(K, m) = H\left((K' \oplus opad) \parallel H((K' \oplus ipad) \parallel m)\right)$$
其中:
- $K$:原始密钥(长度任意)
- $K'$:固定长度的密钥(若 $K$ 过长则 $K' = H(K)$,若过短则右补零至块长度)
- $ipad$:内填充,字符
0x36重复 block_size 次 - $opad$:外填充,字符
0x5C重复 block_size 次 - $H$:底层密码杂凑函数(此处为 SM3)
- $\parallel$:串联操作
- $\oplus$:按字节异或
SM3 参数适配
将通用 HMAC 公式中的 $H$ 替换为 SM3:
| 参数 | SM3 值 |
|---|---|
| 杂凑输出长度 $h$ | 256 bit(32 字节) |
| 分组长度 $\ell$ | 512 bit(64 字节) |
| ipad | 0x36 重复 64 次(64 字节) |
| opad | 0x5C 重复 64 次(64 字节) |
| 最大消息长度 | $2^{61}$ 字节(SM3 限制) |
HMAC-SM3 计算步骤详解
第一步:密钥处理
设原始共享密钥为 $K$(长度可任意),目标块长度为 $B = 64$ 字节(SM3 分组长度):
情况 A:$K$ 长度 $\leq B$
直接右补零至 64 字节:
K' = K || 0x00...0x00 (共 B 字节)情况 B:$K$ 长度 $> B$
先对 $K$ 做 SM3 杂凑,得到 $K' = \text{SM3}(K)$,再右补零:
K' = SM3(K) || 0x00...0x00 (共 B 字节)国密工程实践:在国密 TLS 协议中,密钥通常来自 KDF(密钥派生函数),长度已固定为 32 字节(SM3 输出长度),直接属于情况 A。
第二步:内填充与第一次杂凑
将 $K'$ 与内填充 $ipad$(64 字节 0x36)做按字节异或,再与消息 $m$ 拼接,进行 SM3 杂凑:
$$\text{inner} = \text{SM3}((K' \oplus ipad) \parallel m)$$
$K' \oplus ipad$ 的结果是一个 64 字节的"密钥掩码",它混合了密钥熵到哈希的初始状态中。
第三步:外填充与第二次杂凑
将 $K'$ 与外填充 $opad$(64 字节 0x5C)做按字节异或,再与 inner 拼接,进行 SM3 杂凑:
$$\text{HMAC-SM3}(K, m) = \text{SM3}((K' \oplus opad) \parallel \text{inner})$$
两次异或的值不同(0x36 vs 0x5C),确保内层和外层哈希的密钥掩码完全不同,增强了抗分析能力。
完整流程图
K (原始密钥)
│
├─ 若 len(K) > 64 ─→ SM3(K) ─┐
│ │
│ 右补零至 64 字节 │
▼ ▼
K' (64 字节) │
│ │
┌──────┴──────┐ │
│ ⊕ ipad │ ⊕ opad │
│ (0x36×64) │ (0x5C×64) │
└──────┬──────┘ │
│ │
K'⊕ipad K'⊕opad
│ │
▼ │
┌─────────────┐ │
│ ‖ m │ │
└──────┬──────┘ │
▼ │
SM3(·) ──→ inner (32 字节) ─────┘
│
▼
SM3(·) ──→ HMAC-SM3 (32 字节)与国密协议的关联
GM/T 0024-2023 SSL VPN 技术规范
GM/T 0024-2023 定义了 SSL VPN 网关的密码技术要求,其中消息完整性保护使用 HMAC-SM3(与 SM4-GCM 的 AEAD 模式相比,HMAC-SM3 适用于非 AEAD 的 SM4-CBC 模式)。
关键条款映射:
| GM/T 0024 要求 | HMAC-SM3 实现 |
|---|---|
| 数据传输完整性保护 | 使用 HMAC-SM3 生成消息认证码 |
| 密钥派生 | KDF 输出作为 HMAC 共享密钥 |
| 随机数要求 | 符合 GM/T 0005-2012 随机性检测 |
GM/T 0129-2023 SSH 密码协议规范
GM/T 0129-2023《SSH 密码协议规范》规定了国密 SSH 协议的完整交互流程,其中认证阶段和数据传输阶段的完整性校验均使用 HMAC-SM3。
SSH MAC 协商示例:
SSH 客户端 → 服务器:MAC algorithms [hmac-sm3]
SSH 服务器 → 客户端:MAC algorithms [hmac-sm3,hmac-sha2-256]
协商结果:hmac-sm3国密 TLS 1.3 套件
在 TLS 1.3 的国密密码套件中(如 TLS_SM4_GCM_SM3),HMAC-SM3 虽然不再用于记录层(AEAD 模式自带认证),但在以下场景仍有应用:
- 密钥派生:HKDF 中的 hash 函数使用 SM3
- Finished 消息:TLS 1.3 握手后期的 finished 验证使用 PRF,其底层为 HKDF-SM3
- 早期数据(0-RTT):部分国密扩展草案中涉及 HMAC-SM3 用于早期数据验证
安全性分析
抗碰撞安全性
HMAC 的安全性依赖于底层哈希函数的抗碰撞性。对于 HMAC-SM3:
- SM3 输出长度 256 bit,抗碰撞安全性约 128 bit(生日攻击)
- 若攻击者能打破 SM3 的抗碰撞性,则 HMAC-SM3 的安全性随之下降
- 目前 SM3 未发现有效的碰撞攻击
密钥长度与安全性
- HMAC-SM3 的推荐密钥长度为 32 字节(256 bit),与 SM3 输出长度一致
- 密钥长度不足时,安全强度受限于最短的密钥长度
- 国密标准要求 HMAC 密钥使用 GM/T 0005-2012 符合性检测的随机数生成器产生
已知攻击与防御
| 攻击类型 | 对 HMAC-SM3 的影响 | 防御措施 |
|---|---|---|
| 长度扩展攻击 | 不适用(双重哈希结构) | 结构设计已免疫 |
| 密钥重用 | 安全性降低 | 不同用途使用不同密钥(密钥派生分离) |
| 侧信道攻击 | 执行时间/功耗可能泄露 | 使用恒定时间实现 |
与其他 MAC 方案的对比
| 方案 | 底层哈希 | 密钥长度 | 输出长度 | 国密适配 |
|---|---|---|---|---|
| HMAC-SM3 | SM3(256bit) | 任意(推荐 32B) | 256 bit | ✅ 原生支持 |
| HMAC-SHA256 | SHA-256 | 任意(推荐 32B) | 256 bit | ❌ 国际算法 |
| CMAC-SM4 | SM4(分组密码) | 128/192/256 bit | 128 bit | ✅ 需额外定义 |
| Poly1305 | ChaCha20 派生 | 256 bit | 128 bit | ❌ 国际算法 |
选择建议:国密合规场景优先选用 HMAC-SM3;非国密场景(如与 AWS KMS 等第三方集成)可考虑 HMAC-SHA256。
相关实践
- HMAC-SM3 消息认证码实战教程 — Python 实现与测试向量验证
- SM3 杂凑算法深度解析 — SM3 算法内部结构