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的消息分组、扩展、压缩三步走,每一步都有国密特有的设计细节。
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。解决方案是部署国密网关,在网关层做协议转换。
具体流程是这样的:
- 客户端发起HTTPS请求,网关返回国密证书
- 客户端用SM3计算证书摘要,用SM2验证签名
- 协商SM4会话密钥,后续通信用SM4加密
- 网关将国密流量转换为国际算法,转发给后端服务
你想想看,这个方案虽然能跑通,但性能损耗不小。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的初始值设计思路一样——用无理数来消除人为构造的痕迹。