国密算法核心原理:SM2、SM3、SM4
做国密 SSL 传输加密,绕不开这三驾马车。我个人习惯把国密算法分成三类来看:非对称、杂凑、对称。
SM2 管密钥协商和数字签名,SM3 管数据完整性,SM4 管数据加密。说白了——SSL 握手靠 SM2,握手通信用 SM4,中间任何篡改由 SM3 揪出来。
一句话理解:SM2 解决「你是谁」和「密钥怎么安全抵达」,SM3 解决「数据有没有被改过」,SM4 解决「数据只有你能看」。
💡 我的习惯:画这种图帮你理清算法关系。SM2 管密钥分发,SM3 管签名摘要,SM4 管数据体。三者配合,才算一套完整的 SSL 加密通道。
SM2:椭圆曲线公钥密码
SM2 基于椭圆曲线上的离散对数难题。它比 RSA 更「省力气」——256 比特的密钥,能达到 RSA-3072 的安全强度。
我在项目里常见 SM2 用在两个场景:
- 密钥协商:SSL 握手阶段,客户端和服务端各自生成临时 SM2 密钥对,通过交换公钥与各自的私钥算出一个共享秘密。具体流程叫 ECKA(基于椭圆曲线的密钥协商),和 ECDH 思路类似。
- 数字签名:对 SM3 摘要签名。签名结果是一对整数 (r, s)。验签时用公钥算出 (x1, y1) 再恢复 r' 比较。
SM2 在 SSL 中主要做密钥协商。它不加密大量数据——公钥加密效率低,而且有密钥长度的限制,只能加密很短的消息。所以我们用它来「交换密钥」,真正的数据加密交给 SM4。
| 参数 | SM2 | RSA-2048 |
|---|---|---|
| 密钥长度 | 256 比特 | 2048 比特 |
| 签名长度 | 约 512 比特 | 2048 比特 |
| 签名速度 | 快(ECC 点乘) | 慢(大数模幂) |
| 验签速度 | 快 | 较快 |
| 密钥生成速度 | 极快 | 慢 |
避坑:SM2 密钥对必须使用安全随机数生成器。我曾经见过有人用伪随机种子生成 SM2 私钥,结果每次重启服务器得到相同密钥对——握手直接暴露。
SM3:密码杂凑算法
SM3 输出 256 比特(32 字节)的摘要。它和 SHA-256 结构很像,都用了 Merkle–Damgård 迭代结构,但在细节上有差异:
- 消息分组 512 比特,填充方式与 SHA-256 类似
- 64 步迭代压缩,每步 16 个字
- 初始值 IV 与 SHA-256 不同
- 压缩函数中的布尔函数和移位操作有区别
说白了,SM3 是国标自己设计的,不是 SHA-256 的拷贝。安全强度也达到了 128 比特。做 SSL 时,SM3 用于:
- 证书签名摘要(对证书 DER 编码做 SM3 摘要,再用 SM2 签名)
- HMAC-SM3 用于消息认证码(有些国密 SSL 库支持)
- 密钥派生函数中的散列步骤
经验:我曾经在对接某国产密码机时,遇到 SM3 输出的摘要顺序问题和 HMAC 的 inner/outer padding 字节序不匹配。后来发现是平台大小端不同。建议写代码时统一用大端表示法,国密标准都是大端。
SM4:分组密码算法
SM4 是国标的分组密码,也叫 SMS4。它和 AES 一样是 128 比特分组、128 比特密钥,但内部结构完全不同:
- 32 轮非线性迭代(AES 是 10 轮)
- S 盒固定 8x8(AES 是 16x16)
- 加解密结构完全对称(解密是加密的逆过程,使用相同的轮密钥顺序相反)
SM4 在 SSL 中用于记录层加密。握手完成后,双方用 SM4 加密传输的应用数据。支持的模式有:
- ECB:不推荐,明文模式容易泄露模式
- CBC:需要 IV,有填充,必须配合 HMAC 保证完整性
- CTR:流密码模式,不需要填充,效率高
- GCM:认证加密模式,同时保证机密性和完整性——我个人最推荐。
⚠️ 重要:国密 SSL 在记录层一般使用 SM4-GCM 或者 SM4-CBC + SM3-HMAC。GCM 模式自带认证标签,不需要额外 HMAC,但需要硬件支持加解密和 GHASH。如果你的密码机不支持 GCM,可以退而使用 CBC + HMAC,但要注意:先加密后 HMAC,不要反过来。
SM2 / SM3 / SM4 的协同工作
一个典型的国密 SSL 握手过程:
- 客户端和服务端交换 SM2 公钥证书
- 使用 SM2 密钥协商(ECKA)生成共享秘密
- 通过密钥派生函数(KDF,内部使用 SM3)派生出 SM4 的会话密钥
- 后续所有应用数据使用 SM4 加密,并对关键数据(如 Finished 消息)使用 SM3 计算摘要
你看,三个算法一个都不能少。我遇到过不少初学者只关注 SM4 加解密,忘了 SM2 的密钥协商和 SM3 的完整性校验,结果握手能通但安全强度大打折扣。
// C 伪代码:国密 SSL 密钥协商后派生会话密钥
// SM2 协商出共享秘密 Z,长度视曲线而定
byte Z[32]; // 256-bit 椭圆曲线共享秘密
// 使用 SM3 计算密钥派生
byte K[16]; // SM4 128-bit 密钥
KDF_SM3(Z, 32, "TLS1.3-GM", &K, 16);
// 派生出的 K 用于 SM4 加密
SM4_set_key(&key, K);
SM4_GCM_encrypt(plaintext, key, nonce, aad, ciphertext, tag);
📌 小提醒:KDF 的输入字符串(比如 "TLS1.3-GM")是国密 SSL 协议协商时双方约定好的。如果你自定义协议,注意使用固定的、不会产生歧义的字符串,防止不同实体派生出不同密钥。
好了,核心算法的原理先聊到这儿。接下来在实际的密钥边界设计中,你要不断回到这三个算法的基础上去思考——密钥怎么协商、怎么派生、怎么加密。搞懂它们,后面的边界设计才立得住。