杂凑算法 SM3 介绍

说起哈希算法,大家肯定不陌生。MD5、SHA-1 这些老面孔在不少系统里都用过,但国密标准里指定的是 SM3。我刚开始接触的时候心想:“不就是又一个哈希吗,能有多大差别?” 等我在一个电子认证项目中真正用它替代 SHA-256 时,才发现坑不少。今天就带你从头捋一遍 SM3,顺带聊聊我踩过的那些雷。

SM3 是什么

SM3 是中国国家密码管理局发布的杂凑算法,输出 256 位(32 字节)摘要,安全性与 SHA-256 相当。它在结构上继承了 Merkle-Damgård 框架,但每一轮的处理细节做了独特设计。说白了,它就是在我们熟悉的 MD 结构上换了自家配方。

核心特点:

  • 消息分组长度:512 位(64 字节)
  • 摘要长度:256 位(32 字节)
  • 压缩函数:64 轮迭代
  • 基于 32 位字运算,适合 32 位 CPU 高效实现

算法流程总览

SM3 的处理流程并不复杂,就三步:

  1. 消息填充——补位到 512 位的整数倍
  2. 迭代压缩——把每组 512 位数据扔进压缩函数,更新 256 位链接变量
  3. 输出摘要——最后链接变量的值就是摘要

下面我画了一张流程图,能帮你一眼看清全貌。

SM3 算法整体流程 原始消息 M 消息填充 分组 (512bit) 迭代压缩 64轮 / 消息扩展+压缩 输出摘要 256位 迭代时,每组处理结果会作为下一组的初始链接变量(除第一组使用固定 IV 外)

消息填充——细节决定成败

填充规则跟 SHA-256 很像,但有一个容易踩坑的点。我直接说规则:

  • 先在消息末尾补一个 0x80(二进制 1000 0000)
  • 然后补 0x00,直到长度满足 len(M) ≡ 448 mod 512
  • 最后附加 64 位的原始消息长度(大端序)

注意: 长度字段用的是比特长度,而不是字节长度!
我曾经在实现时粗心写了字节数,结果每条消息摘要都错位,排查了整整一下午。就是因为忘记把“字节数 × 8”转成比特数。

// C 语言风格的填充示例(仅示意)  
void sm3_pad(unsigned char *msg, int len) {  
    msg[len] = 0x80;  
    int zero_len = ( (448 / 8) - (len + 1) % 64 + 64 ) % 64;  
    // 补 0x00  
    for (int i = 0; i < zero_len; i++)  
        msg[len + 1 + i] = 0x00;  
    // 附加 64 位长度(大端序)  
    uint64_t bit_len = len * 8;  
    for (int i = 7; i >= 0; i--)  
        msg[ ... ] = (bit_len >> (8 * i)) & 0xFF;  
}

迭代压缩——SM3 的“心脏”

这部分最值得细看。每 512 位分组进入压缩函数后,经历两大步骤:

  1. 消息扩展:将 16 个 32 位字扩展成 132 个字(W0~W67 + W'0~W'63)
  2. 64 轮压缩:每轮使用两个扩展字,更新 8 个链接变量(A~H)

我自己的习惯是先把 SHA-256 的轮函数源码摆出来,再对比 SM3,你会发现 SM3 的布尔函数 GG/FF 设计得更有“国密味”。具体公式这里不列了,但有个重点:

SM3 的轮运算中,每轮要同时使用 Wj 和 W'j,相当于在 SHA-256 基础上多了一道“校验”逻辑,这也是它抗碰撞能力更强的原因之一。

与 SHA-256 的直观对比

我用一张表格帮你快速抓住差异:

对比项 SM3 SHA-256
输出长度 256 位 256 位
分组大小 512 位 512 位
轮数 64 64
每轮使用字 Wj + W'j 仅 Wj
布尔函数 FF、GG(含循环移位) Ch、Maj、Σ0、Σ1
国密合规 是 否

实际性能上,纯软实现 SM3 比 SHA-256 慢 5%~10% 左右,但硬件加速后差距基本抹平。如果你在国密合规场景下,选 SM3 就是唯一解。

应用场景与踩坑记录

SM3 最常见的用法是:

  • 数字证书签名前的摘要计算(SM2 签名算法内部就是用 SM3)
  • 密钥派生函数(如 TLS 1.3 国密套件中的 KDF)
  • 消息完整性校验(HMAC-SM3)

避坑: 我曾经在一个项目中直接用 OpenSSL 的 EVP 接口调 SM3,结果输出长度拿到 256 字节……后来才发现是调用时没设置正确的摘要长度参数。SM3 输出固定 32 字节,千万别被某些旧驱动的缓冲区大小坑了。

另外,SM3 的初始值 IV 也很关键——它是固定的 8 个 32 位字。我建议你在实现时直接写死,不要通过计算得到,否则容易引入隐性 bug。当时我手贱想用 MD5 的 IV 生成方式自动算,结果报错惨不忍睹……老老实实贴常量才是正道。

好了,这一节我们就把 SM3 的来龙去脉讲清楚了。从填充到压缩,从轮函数到对比,核心点在表格和流程图里都能找到。后面几章我们会拿 SM3 和其他国密算法(SM2、SM4)配合密钥分层架构搭一个完整的方案,到时候 SM3 会频繁出现。