SM3密码杂凑算法:从原理到固件实战

SM3,说白了就是咱们国标里的“杂凑算法”。很多人叫它哈希算法,其实一回事。我最早接触SM3是在一个电力行业的固件升级项目里,甲方明确要求必须用国密,不能用SHA-256。那时候我还在想,这俩到底差在哪儿?后来踩了几个坑,才算真正搞明白。

这一章,咱们就把它彻底说透。

SM3算法原理与流程

SM3的输出长度是256位,也就是32字节。它的核心思想很简单:把任意长度的消息,通过填充、迭代压缩,最终变成一个固定长度的摘要。

整个流程可以分成四步:

  1. 消息填充:补位,让消息长度变成512的倍数
  2. 初始值设定:8个32位寄存器,赋初值
  3. 迭代压缩:64轮循环,每轮用消息扩展和压缩函数
  4. 输出摘要:8个寄存器的值拼接成256位

核心要点:SM3的压缩函数采用了“Feistel结构”的变体,每轮用两个消息字参与运算。这一点和SHA-256不太一样,后面会细说。

我习惯把SM3的迭代过程想象成一个“绞肉机”。消息块进去,经过64轮搅拌,出来一堆看起来毫无规律的比特。嗯,这个比喻虽然糙,但道理不糙。

SM3算法整体流程 原始消息 消息填充(补位+长度) 分组(512bit/块) 迭代压缩(64轮) 消息扩展 + 压缩函数 256位摘要 注意 填充时先补1,再补0 最后64位放原始长度 长度单位:比特

消息填充与迭代压缩

消息填充这块,我见过不少新手栽跟头。咱们直接说规则:

  • 先在消息末尾补一个“1”
  • 然后补“0”,直到长度 mod 512 = 448
  • 最后64位放原始消息的长度(单位是比特)

我曾经在一个项目里,因为填充时长度单位搞错了(用了字节而不是比特),导致摘要死活对不上。查了两天才发现,原来是填充的长度值少乘了8。这种错误,编译器不会报错,只有比对结果时才能发现。

迭代压缩是SM3的核心。它把512位的消息块扩展成132个32位字(W0~W67, W'0~W'63),然后做64轮压缩。

压缩函数里有两个关键操作:

  • SS1, SS2:中间变量,由寄存器值和消息字计算
  • TT1, TT2:临时变量,用于更新寄存器

每轮更新8个寄存器(A~H),64轮后输出最终值。我个人的理解是:这就像8个变量在64轮里互相“搅和”,每一轮都引入新的消息字,最终达到雪崩效应。

小技巧:调试SM3时,可以先用官方测试向量验证中间值。我习惯把每轮的A~H值打印出来,和标准结果逐轮比对。一旦某轮对不上,问题就锁定在那轮的消息扩展或压缩函数里。

SM3与SHA-256的对比分析

很多人问我:SM3和SHA-256到底谁更强?我的回答是:从安全强度看,两者旗鼓相当。但细节上确实有差异。

对比项 SM3 SHA-256
输出长度 256位 256位
消息分组 512位 512位
压缩轮数 64轮 64轮
消息扩展 生成132个字 生成64个字
布尔函数 FF, GG(含异或) Ch, Maj
移位操作 循环左移+右移 右移为主
国标编号 GB/T 32905-2016 FIPS PUB 180-4

你看,SM3的消息扩展更复杂,生成了132个字,而SHA-256只生成64个。这意味着SM3每轮有更多的消息字参与运算,扩散性理论上更好。

另外,SM3的布尔函数FF和GG里引入了异或操作,而SHA-256用的是Ch和Maj。异或的非线性特性更强,对抵抗差分攻击有帮助。

但说实话,在实际工程中,两者的性能差异很小。我做过对比测试,在ARM Cortex-M4上,SM3比SHA-256慢大约10%~15%。这个差距在大多数场景下可以忽略。

我的建议:如果项目要求国密合规,直接用SM3。如果没有合规要求,SHA-256也完全够用。不要为了“哪个更安全”纠结,两者的安全余量都足够。

SM3在固件完整性校验中的应用

固件完整性校验,说白了就是确保你烧录进去的固件没被篡改。SM3在这里扮演的角色就是“指纹生成器”。

典型的流程是这样的:

  1. 编译生成固件二进制文件(.bin或.hex)
  2. 用SM3计算整个固件的摘要值
  3. 把摘要值存储在固件的固定位置(比如末尾或头部)
  4. 设备启动时,重新计算固件摘要,和存储的摘要比对

如果比对一致,说明固件没被改过。不一致,说明固件可能被篡改或损坏。

我在一个物联网网关项目里用过这个方案。当时遇到一个问题:固件里包含了摘要值本身,计算摘要时要不要包含它?

答案是:不要包含。否则就成了“自己算自己”,永远对不上。

我曾经犯过这个错误。第一次做固件校验时,我把整个固件文件(包括预留的摘要区)都拿去算SM3,结果算出来的摘要和存储的摘要永远不一致。后来才意识到,摘要区在计算时应该填0,算完后再把真实摘要写进去。

具体实现时,我习惯这样做:

  • 在固件链接脚本里预留32字节的摘要区
  • 编译后,用脚本计算固件(摘要区填0)的SM3值
  • 把SM3值写入摘要区
  • 设备启动时,同样把摘要区当0处理,重新计算并比对

另外,SM3还可以和签名算法配合使用。比如先用SM3算摘要,再用SM2签名。这样既能保证完整性,又能保证真实性——确保固件来自合法的发布者。

避坑指南:固件校验时,注意字节序。SM3输出是大端序,如果你的MCU是小端序,直接比对可能会失败。我习惯在比对前统一转成大端序,或者用memcmp逐字节比对。