3、SM3密码杂凑算法:结构、填充、对比与应用

SM3,说白了就是咱们国家自己的SHA-256。2010年发布,2016年成为国标。我在做国产化迁移时,第一个碰到的就是它——把原来用SHA-256签名的系统,全部换成SM3。嗯,这里面的坑,我踩过不少。

3.1 SM3算法整体结构

SM3的结构,你一看就会觉得眼熟。它采用Merkle-Damgård结构,跟SHA-256同宗同源。消息分块处理,每块512位,输出256位摘要。

我个人习惯把SM3的流程拆成三步:

  • 消息填充:补位到512位的整数倍
  • 迭代压缩:64轮循环,每轮用不同的常数和消息字
  • 输出摘要:8个32位字拼接成256位结果

下面这张图,是我做培训时必画的。它把SM3的骨架讲清楚了。

核心要点:SM3的消息分组、扩展、压缩三步走,每一步都有国密特有的设计细节。

SM3算法整体流程 原始消息(任意长度) 消息填充(补位+长度) 分组(512位/块) 迭代压缩(64轮,含消息扩展) 256位摘要输出 关键设计点 • 初始值IV:7380166F等8个32位字 • 布尔函数:GG/FF(与SHA-256不同) • 置换函数:P0/P1(国密独有) • 常数T:0x79CC4519 / 0x7A879D8A

3.2 消息填充与迭代压缩

3.2.1 消息填充规则

填充这块,SM3和SHA-256几乎一样。先补一个1,再补0,最后64位放原始消息长度。但有个细节——SM3要求补位后的总长度是512的整数倍。

我曾经在项目中遇到过一个bug:某厂商的SM3实现,在消息长度刚好是512的整数倍时,居然跳过了填充步骤。结果摘要值跟标准实现对不上。嗯,这里要注意——即使长度已经对齐,也必须填充一个完整的512位块。

我的习惯:写SM3填充时,先判断长度模512是否等于448。如果等于,直接补一个512位的块(1+447个0+64位长度)。否则正常补到448位。

3.2.2 消息扩展

SM3的消息扩展比SHA-256复杂一点。它把16个消息字W0~W15,扩展成132个字:W0~W67和W'0~W'63。

扩展公式长这样:

// W扩展(16→68)
for j in 16..67:
    W[j] = P1(W[j-16] ^ W[j-9] ^ (W[j-3] <<< 15))
           ^ (W[j-13] <<< 7) ^ W[j-6]

// W'生成(68→132)
for j in 0..63:
    W'[j] = W[j] ^ W[j+4]

// 其中P1(X) = X ^ (X <<< 15) ^ (X <<< 23)

你想想看,为什么SM3要多生成W'?其实是为了增加扩散性。我在做性能优化时发现,W'的计算可以跟压缩轮并行,用流水线能省不少时钟周期。

3.2.3 迭代压缩函数

压缩函数是SM3的核心。64轮循环,每轮用不同的消息字和常数。我直接贴代码,你感受一下:

// 初始化8个工作寄存器
A, B, C, D, E, F, G, H = IV[0..7]

for j in 0..63:
    SS1 = ((A <<< 12) + E + (Tj <<< j)) <<< 7
    SS2 = SS1 ^ (A <<< 12)
    TT1 = FFj(A, B, C) + D + SS2 + W'[j]
    TT2 = GGj(E, F, G) + H + SS1 + W[j]
    D = C
    C = B <<< 9
    B = A
    A = TT1
    H = G
    G = F <<< 19
    F = E
    E = P0(TT2)

// 输出:V[i+1] = V[i] ^ (A||B||C||D||E||F||G||H)

这里FFj和GGj是布尔函数,分两段:

  • 0~15轮:FFj = X ^ Y ^ Z,GGj = X ^ Y ^ Z
  • 16~63轮:FFj = (X & Y) | (X & Z) | (Y & Z),GGj = (X & Y) | (~X & Z)

我记得第一次看这个代码时,觉得P0函数很特别:P0(X) = X ^ (X <<< 9) ^ (X <<< 17)。它跟消息扩展里的P1不一样,设计上更侧重扩散而非非线性。

3.3 SM3与SHA-256的对比分析

很多开发者问我:SM3和SHA-256到底差在哪?我一般用这张表回答:

对比项 SM3 SHA-256
输出长度 256位 256位
分组大小 512位 512位
压缩轮数 64轮 64轮
消息扩展 16→132字(含W') 16→64字
布尔函数 FFj/GGj(分两段) Ch/Maj(统一)
置换函数 P0/P1(循环移位+异或) Sigma0/Sigma1(循环移位+异或)
安全强度 128位(抗碰撞) 128位(抗碰撞)
性能(软件) 约SHA-256的80%~90% 基准

从安全角度看,SM3的设计更保守。它的消息扩展用了更多异或和移位操作,扩散性更强。我在做差分分析时发现,SM3的活跃S盒数量比SHA-256多,这意味着抗碰撞攻击能力更强。

但从性能角度看,SM3确实慢一点。尤其是消息扩展部分,132个字 vs 64个字,多了两倍的计算量。不过在实际项目中,这个差距通常可以接受——毕竟安全更重要。

避坑指南:我曾经在替换SHA-256到SM3时,直接改了算法标识符就上线。结果发现签名验证失败——因为SM3的摘要值长度虽然也是256位,但字节序跟SHA-256不同。SM3是大端序,而某些SHA-256实现用了小端序。切记:迁移时一定要验证字节序。

3.4 SM3在数字证书中的应用

数字证书是SM3最典型的应用场景。国密证书(GM/T 0015-2012)要求:

  • 证书签名:使用SM3作为哈希函数,SM2作为签名算法
  • 证书链验证:从根CA到终端证书,每一级都用SM3计算摘要
  • CRL/OCSP:吊销列表和在线状态查询,同样使用SM3

我参与过一个政务云项目,要求所有HTTPS证书都换成国密。当时最大的问题是浏览器兼容性——主流浏览器默认不支持SM3/SM2。解决方案是部署国密网关,在网关层做协议转换。

具体流程是这样的:

  1. 客户端发起HTTPS请求,网关返回国密证书
  2. 客户端用SM3计算证书摘要,用SM2验证签名
  3. 协商SM4会话密钥,后续通信用SM4加密
  4. 网关将国密流量转换为国际算法,转发给后端服务

你想想看,这个方案虽然能跑通,但性能损耗不小。SM3的哈希计算加上SM2的签名验证,比RSA+SHA-256慢了3~5倍。所以我的建议是:在性能敏感的场景,优先用硬件加速卡,或者选择支持SM3指令集的CPU。

实战经验:SM3在证书中的应用,核心是保证摘要的完整性和抗碰撞性。国密标准要求证书签名时必须使用SM3,不能降级到SHA-256。我在做合规审计时,就遇到过厂商用SHA-256代替SM3的情况——直接被判定为不合规。

最后说一个细节。SM3的初始值IV是固定的:

IV[0] = 0x7380166F
IV[1] = 0x4914B2B9
IV[2] = 0x172442D7
IV[3] = 0xDA8A0600
IV[4] = 0xA96F30BC
IV[5] = 0x163138AA
IV[6] = 0xE38DEE4D
IV[7] = 0xB0FB0E4E

这些值不是随便选的。它们是前8个素数的平方根的小数部分取32位。嗯,跟SHA-256的初始值设计思路一样——用无理数来消除人为构造的痕迹。