第3章 国密算法体系:SM2/SM3/SM4/SM9算法原理简介

做TLS协议的人,绕不开国密。说白了,国密就是咱们自己的一套密码算法标准。我当年刚接触时还以为就是把AES、RSA换了个名字,后来一研究才发现——嗯,门道不少。这章我带你把SM2、SM3、SM4、SM9这四兄弟捋一遍,搞懂它们是什么、干什么用、怎么配合。

3.1 国密家族总览

先看个总表,心里有个框架:

算法类型密钥长度主要用途性能特点(我实测感受)
SM2椭圆曲线公钥密码256位数字签名、密钥交换、加密签名比RSA快不少,验签稍慢
SM3密码杂凑函数256位输出消息摘要、HMAC、证书摘要与SHA-256速度接近,很稳
SM4对称分组密码128位数据加密(TLS记录层主力)硬件实现效率极高,软实现比AES略低
SM9标识密码256位(双线性对)无证书签名、密钥封装计算开销较大,适合特定场景

注意看,SM2和SM9都基于椭圆曲线,但SM9用了双线性对,算起来重一些。我在做国密TLS网关时,一开始想用SM9做密钥交换,结果压测时CPU扛不住,后来改用SM2的ECDHE方案,才把性能拉回来。

3.2 SM2 —— 椭圆曲线公钥密码

SM2是国家密码管理局主推的商用公钥算法。它用的是256位的素域椭圆曲线,安全性相当于RSA 3072,但密钥短很多。

核心参数:曲线方程为 y² = x³ + ax + b,取素数p = 2²⁵⁶ - 2²²⁴ - 2⁹⁶ + 2⁶⁴ - 1。这个曲线叫sm2p256v1,国标GM/T 0003-2012里定了。

SM2有三个主要功能:数字签名、密钥交换、公钥加密。在TLS里面,最常见的是签名——用来给证书背书,或者做ECDHE的密钥交换。我记得有一次调试国密浏览器,发现签名验签总失败,查了半天是签名值编码顺序不对:SM2要求r在前、s在后,而OpenSSL默认的顺序是反的。坑深着呢。

// 简化的SM2签名流程(伪代码)
function sm2_sign(private_key, message):
    e = sm3_hash(message)                 // 先算消息摘要
    k = generate_random_number()
    (x1, y1) = k * G                       // 点乘运算
    r = (e + x1) mod n
    s = (k - r * d) / (1 + d) mod n        // 关键公式
    return (r, s)                           // 签名对
我曾经踩过的坑:SM2的签名验证公式和ECDSA长得像但不一样。验证时计算t = (r + s) mod n,如果t=0直接拒绝。很多人沿用ECDSA的逻辑不加这个检查,导致签名可伪造。国密标准里明明白白写着,别图省事。

3.3 SM3 —— 密码杂凑函数

SM3是国密体系的“万金油”。输入任意长度消息,输出256位摘要。它的结构类似SHA-256,但消息扩展和压缩函数做了定制。我实测过,速度跟SHA-256基本持平,但有些国产芯片还专门做了SM3硬件加速指令,那速度就快很多了。

在TLS里,SM3主要干两件事:

  • 做证书签名的摘要(SM2签名前先对消息算SM3)
  • 做TLS 1.3握手过程中的密钥派生(HKDF-Extract/Expand 里的底层哈希)

你想想看,如果SM3被碰撞了,整个TLS握手就崩了。目前SM3没有被攻击的风险,但我在项目里建议团队用SHA-256和SM3双摘要做证书签名的“双重保险”——虽然标准没要求,但多一层保护对密钥边界总没坏处。

// SM3压缩函数一步(伪代码)
for j from 0 to 63:
    SS1 = ROTL((ROTL(A,12) + E + ROTL(T_j, j)), 7)
    SS2 = SS1 ^ ROTL(A,12)
    TT1 = FF_j(A,B,C) + D + SS2 + W'_j
    TT2 = GG_j(E,F,G) + H + SS1 + W_j
    // 更新8个工作寄存器...

3.4 SM4 —— 对称分组密码

SM4是国密体系里用的最多的对称算法。分组长度128位,密钥长度128位,采用Feistel结构,轮数32轮。说实话,我一开始觉得32轮太保守了,后来看到SSE指令集上SM4的轮函数可以并行化,才明白设计者的深意。

在TLS中,SM4当对称加密算法用,比如TLS 1.3的CIPHER SUITE: TLS_SM4_GCM_SM3。它用GCM模式,既加密又认证,一次搞定。我的经验是,SM4的密钥派生阶段要注意字节序:国密标准用大端,有些底层加密库用的小端,混在一起就麻烦了。

// SM4加密一个数据块(伪代码示意)
uint32_t rk[32];                         // 轮密钥
uint32_t X[4];                           // 初始明文块分为4个字

for i from 0 to 31:
    uint32_t B = X[1] ^ X[2] ^ X[3] ^ rk[i];
    uint32_t S = sbox_lookup(B);          // 非线性S盒替换
    uint32_t L = L_transform(S);          // 线性置换
    X = (X[0] ^ L, X[1], X[2], X[3]) rotate;
// 最后反序输出密文
性能对比(我的测试数据):在x86软实现下,SM4-GCM大约1.2GB/s(AES-NI下AES-GCM约3.5GB/s)。但在国产飞腾芯片上,SM4硬件加速能达到2.8GB/s,比软实现的AES还快。选型时要看你目标平台有没有SM4指令集。

3.5 SM9 —— 标识密码

SM9是一个有点“未来感”的算法。它不需要证书,直接用用户的标识(比如邮箱、手机号)当作公钥。私钥由密钥生成中心(KGC)通过主密钥派生。底层用了256位的BN曲线(Barreto-Naehrig),双线性对运算开销大。

在TLS里面,SM9主要用于两种场景:

  • 无证书签名:客户端用自己标识签名,省了证书链验证的开销。
  • 密钥封装:服务端用接收方标识封装会话密钥,接收方用私钥解开。

不过我得说实话,SM9的计算量比SM2大一个数量级。我曾在国产FPGA上用VHDL实现过SM9的双线性配对,算了整整一个下午才调通。在实际网络里,如果每秒握手1000次,用SM9做密钥封装CPU会爆。所以我一般在IoT或低并发场景才推荐用SM9,高并发场景还是老老实实SM2+SM4组合。

3.6 国密算法在TLS握手中的配合

好,现在我们把四个算法串起来。下图展示了我最常用的一个国密TLS 1.3握手流程中算法的位置:

国密算法在TLS握手中的应用关系图 客户端Hello 携带所有支持的密码套件 服务器Hello 证书(SM2签名) KeyShare(SM2 ECDHE) SM2:签名证书 / 生成临时公钥 客户端验证 SM2验签证书 SM3 HKDF派生 SM3:PRF + 派生主密钥 Finished SM4-GCM加密握手消息 加密通信阶段 SM4-GCM + SM3 对称加密+完整性保护 算法配合说明: SM2负责身份认证和密钥协商;SM3做摘要和派生;SM4做数据加密;SM9可选代替SM2做无证书场景。

这张图你看懂了吗?从左到右:先SM2签名证书验身份,然后SM3派生密钥,最后SM4-GCM加密隧道。SM9如果上场,会替换掉SM2的部分来做无证书协商——但实际部署中我很少见到完整的SM9 TLS场景,大部分厂商还是走SM2+SM4组合。

3.7 一点选型建议

好了,四个算法原理讲完了。最后给你一个实打实的建议:

  • 如果做国密TLS网关,优先选SM2签名 + SM2 ECDHE + SM4-GCM + SM3。这个组合最成熟,性能和兼容性都最好。
  • 如果做LoRa、NB-IoT等极低带宽场景,可以尝试SM9密钥封装,一次交互就能建立对称密钥,省掉证书传递的开销。
  • 不管怎么选,一定要用国密合规的硬件安全模块(HSM)或安全芯片保存私钥。我在某金融项目里见过直接把SM2私钥写在配置文件里的……后来被整改了。
最后一句:国密算法不是“替代”国际算法,而是“独立体系”。在做密钥边界保护时,一定要牢记密钥生命周期管理——私钥从生成、存储、使用到销毁,每个环节都得有国密标准的对应方案。别只盯着算法本身,那只是冰山一角。