第二章:国密算法概述(SM2 / SM3 / SM4)
搞国密改造,第一关就是先把这三个算法吃透。SM2、SM3、SM4,说白了就是国标版的“公钥加密+哈希+对称加密”。很多第一次接触的朋友容易把它们跟国际算法混着用,结果互操作性测试一跑就崩。我刚开始做第一个改造项目时也踩过这个坑——SM2的签名值格式跟ECDSA不一样,直接用OpenSSL的ECDSA验签,验一百次失败一百次。
所以这一章,咱不绕弯子,直接把这哥仨的定位、原理、关键参数和常见误区捋清楚。你往后写测试用例、搭验证环境,都会用到这些底子。
2.1 SM2——椭圆曲线公钥密码算法
SM2对标的是ECC(椭圆曲线密码),但曲线参数是自主研发的。嗯,这里要注意:SM2的曲线方程虽然是 y² = x³ + ax + b,但参数 a、b、p、G、n 都是国家指定的一套常量值,不能自己随便改。
我建议你在做互操作性测试时,先确认两件事:
- 对方用的曲线是不是 SM2 标准曲线(OID 1.2.156.10197.1.301)
- 签名格式是 DER 编码的
(r, s),还是简单的字节拼接
大部分国密SDK默认输出DER编码,但有些国产密码机偏好裸的64字节 r||s。我曾经因为这个差异在联调时熬了两个通宵,最后发现是签名格式没对齐。
核心参数速查表
| 参数 | 值 | 备注 |
|---|---|---|
| 曲线长度 | 256 位 | 安全性等价 RSA-3072 |
| 基点 G | 固定值(国标 GM/T 0003) | 验签时必须使用 |
| 密钥长度 | 私钥 32 字节,公钥 65 字节(含前缀04) | 压缩格式极少用 |
| 签名长度 | DER 编码约 70~72 字节 | 裸格式固定 64 字节 |
04 开头,说明对方没做任何格式转换,直接用了原始点。
2.2 SM3——密码杂凑算法
SM3 就是国标的 SHA-256。输出长度 256 位,消息分组长度 512 位。说实话,它的结构跟 SHA-256 很像——消息扩展、压缩函数、迭代轮数都是 64 轮。但这里有区别:SM3 的初始向量和常数是重新定义的,而且压缩函数里用了一个独立的置换函数 P。
你能用 SM3 的地方:
- 数字签名的消息哈希(SM2 签名前必须用 SM3)
- 消息认证码(HMAC-SM3)
- 基于哈希的随机数生成
我曾遇到一个坑:某密码卡输出的 SM3 摘要长度是 32 字节没错,但它在大端序和小端序上跟软件库不一致。后来发现是底层字节序没转过来。我的建议是——拿到摘要后,先跟开源实现(比如 GmSSL)跑一遍相同的输入对比,别盲目信任对方文档。
简单算个 SM3 的伪代码:
输入: 消息 m (任意长度)
输出: 256位摘要 h
1. 填充: 在 m 后补 1 个 '1',再补 '0',最后附上 64 位长度
2. 分组: 每 512 位为一个分组
3. 迭代压缩:
每组做 64 轮扩展(W0~W67,W'0~W'63)
每轮更新 8 个寄存器 (A~H)
4. 输出串联: A||B||C||D||E||F||G||H
2.3 SM4——分组对称密码算法
SM4 对标的是 AES-128。密钥长度 128 位,分组长度也是 128 位。加密模式上,它支持 ECB、CBC、CTR、GCM 等常见模式。不过你可能觉得奇怪——为什么偏偏是 128 位?因为国密设计时认为 128 位在安全性和性能上已经足够,而且 SM4 的轮数只有 32 轮,比 AES 的 10/12/14 轮还多,看起来更保守。
实际项目中,我最常用的是 SM4-CBC 和 SM4-GCM。给你个对比:
| 模式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| SM4-CBC | 实现简单,几乎所有密码设备都支持 | 不能并行,需要 IV | 通用数据加密(文件、数据库) |
| SM4-GCM | 认证加密,防止篡改 | 实现复杂,部分老密码机不支持 | 通信协议(TLS、IPsec) |
我记得有一次测试 SM4-CBC 加解密,发现用同一个密钥和 IV,不同平台加出来的密文竟然差几个字节。查到最后,原来是 PKCS#7 填充方式不同——有的平台缺省用全0填充,有的用标准 PKCS7。互操作性测试里,填充方式必须明确写在测试报告里。
2.4 三者的协作关系
你现在应该能想象到一个典型的国密应用流程了:
- 发送方用 SM3 计算消息摘要
- 用 SM2 私钥签名摘要,生成签名值
- 用 SM4 加密整个消息(或者用 SM4 密钥交换后加密通信)
- 接收方用 SM4 解密,用 SM3 重新算摘要,用 SM2 公钥验签
这种“SM3+SM2+SM4”的组合,就是国密体系的基本模型。当然,实际协议里还要处理密钥协商、身份认证等环节,但核心算法就是这三个。
整个关系的逻辑我画了张图,帮你快速建立框架:
2.5 互操作性测试中的常见误区和检查点
说完了原理,我给你列几条在实际测试中容易翻车的地方。这些是我一个个项目摔出来的:
- 算法 OID 不匹配:SM2 签名的 OID 是 1.2.156.10197.1.301,SM3 摘要的 OID 是 1.2.156.10197.1.401,SM4 的 OID 是 1.2.156.10197.1.104。测试时务必将 OID 作为必测项目。
- 字节序问题:SM2 签名值
(r, s),有些平台输出大端序,有些输出小端序。SM3 摘要全是大端序,但不同语言实现可能在内存中存储为小端序,需要转换。 - 填充模式:SM4 的 ECB/CBC 常用 PKCS7 填充。如果一方用了无填充或 ANSI X.923,另一方就必须对应校验。建议在测试用例中显式写明填充模式。
- 密钥编码:SM2 公钥常用 04||X||Y 格式(65字节),私钥是 32 字节的原始值。但有些密码机输出的是 DER 编码的 SubjectPublicKeyInfo,你又得多做一步解析。
2.6 补充两个实用工具
平时做快速验证,我会用以下两个小工具(虚拟示例,无需实际下载):
- GmSSL 命令行:
gmssl sm2sign -in msg.bin -out sig.der,支持所有国密算法,且开源。 - Python pysmx 库:轻量级国密实现,适合写自动化测试脚本。
你想想看,如果连开源库和自己写的测试代码都串不通,那跟第三方设备联调就更不用说了。所以我的做法是:先拿 GmSSL 作为“黄金参考”,把所有测试向量跑通,再用它来验证待测密码模块的输出。
好了,这章内容就到这里。SM2、SM3、SM4 的原理和互操作性要点你已经清楚了。后面几章我们会围绕这三个算法,深挖具体的测试流程和工具链。你先把这张图印在脑子里,下一步就好走了。