2. SM2椭圆曲线公钥密码算法原理归纳总结

SM2算法,说白了就是中国自己的椭圆曲线公钥密码标准。它跟国际上的ECDSA、ECIES很像,但细节上有不少差别。我在做国密合规项目时,最头疼的就是把原来用ECDSA的代码迁移到SM2……嗯,后面会聊到坑点。先看看核心原理。

2.1 椭圆曲线基础

椭圆曲线不是椭圆,而是一条方程 y² = x³ + ax + b 的曲线。群运算定义在曲线上:两个点相加得到第三个点。这构成了一个循环群。

SM2用的是素数域上的曲线。参数由国家密码管理局指定。我看到不少初学者问:“为什么曲线参数是固定的?” 其实这样更安全,因为不用自己去猜哪个曲线好。我习惯直接使用官方推荐的参数组,省心。

参数名 含义 SM2推荐值(简写)
p 素数域 mod 0xFFFFFFFE…(256位)
a 曲线系数 0xFFFFFFFE…(减2? 不对,标准值) 等
b 曲线系数 0x28E9FA9E…
G 基点 04 04D7… (32+32字节)
n 基点的阶 0xFFFFFFFE…

一个关键点:SM2的基点 G 的阶 n 是一个素数,且约等于 2²⁵⁶。这意味着私钥空间足够大,暴力破解是不可能的。

2.2 密钥对生成

生成过程很简单:

  1. 随机选一个 d ∈ [1, n-2] 作为私钥。
  2. 计算公钥 P = d * G(标量乘法)。

我个人习惯用操作系统提供的随机数源。有一次我在嵌入式设备上写SM2,发现随机数生成器没做好,生成的私钥重复了——那真是噩梦。从那以后,我每次都会检查随机数质量。

提示: SM2公钥有两种编码格式:压缩(33字节)和非压缩(65字节)。非压缩的头部字节是04,压缩的是02或03。我建议优先用非压缩,省去解压缩的麻烦。

2.3 数字签名(SM2-SM)

SM2签名算法含有一个独特步骤:使用签名者的公钥和用户ID生成一个“签名者标识”的杂凑值。在国际标准ECDSA里没有这个。这叫什么?SM2签名里用了一个Z值,它把公钥和用户ID绑定在一起。

流程大致是:

  1. 计算 Z = H(ENTLA || IDA || a || b || x_G || y_G || x_P || y_P)。
  2. 计算消息杂凑 e = H(Z || M)。
  3. 随机生成 k,计算椭圆曲线点 (x₁, y₁) = k*G。
  4. 计算 r = e + x₁ (mod n),如果 r = 0 或 r + k = n 则重试。
  5. 计算 s = (d + 1)⁻¹ · (k - r·d) mod n。

签名就是 (r, s),各256位。验证时用公钥反向计算。

注意: SM2签名中必须检查 r + s ≠ 0 和 r ≠ 0。我曾经见过一份实现漏了检查,结果导致签名可被伪造……修复起来很麻烦。

2.4 公钥加密(SM2-ENC)

SM2的公钥加密是基于ECIES的变体。它用了KDF(密钥派生函数)和对称加密。流程如下:

  1. 随机生成临时私钥 k,计算临时公钥 C₁ = k*G。
  2. 计算共享点 (x₂, y₂) = k * P(P是接收方公钥)。
  3. 用KDF(x₂ || y₂) 派生出对称密钥 t。
  4. 用对称加密(SM4)加密明文,得到 C₂。
  5. 计算杂凑 C₃ = Hash(x₂ || M || y₂)。

密文 = C₁ || C₃ || C₂。

注意:SM2加密输出比明文多了约96字节(临时公钥65字节 + 杂凑32字节)。这个开销在物联网场景下需要考虑带宽。我之前在NB-IoT项目里就因为这个纠结过传输大小的问题。

2.5 密钥交换(SM2-KE)

SM2的密钥交换协议比ECDH复杂,因为它加入了相互认证和密钥确认。双方各自生成临时密钥对,交换临时公钥,计算共享秘密,然后再通过杂凑确认一致性。

协议包含三个步骤:

  1. 发起方:生成临时私钥 rA,计算 RA = rA * G,发送 RA。
  2. 响应方:生成 rB,计算 RB = rB * G,计算共享秘密 K = KDF(rB * RA || rB * PA) 等,发送 RB 和确认码。
  3. 发起方:收到 RB 后计算同样的 K,验证确认码。

说白了这个协议同时完成了密钥协商和双方身份认证。做应用的时候要注意,协商出的 K 可以派生出会话密钥,而且还能选协商的密钥长度。

重点: SM2加密和签名中的KDF使用SM3杂凑算法。SM3的杂凑值长度为256位,正好适合。我个人建议在实现时把SM2+SM3+SM4打包成一套国密套件,调用接口统一。

2.6 知识体系图

下面这张图把SM2的三个主要功能模块和底层依赖关系说清楚了。

SM2椭圆曲线公钥密码算法知识体系 椭圆曲线群 (E(Fp)) 数字签名 SM2-SM (r,s) 公钥加密 SM2-ENC (C₁||C₃||C₂) 密钥交换 SM2-KE (相互认证) 椭圆曲线标量乘法 核心运算:d*G 或 k*P SM3杂凑算法 Z值、KDF、C₃计算 随机数生成 私钥 & 临时私钥 📌 核心关系: SM2三个功能 (签名/加密/密钥交换) 共享椭圆曲线群运算和SM3杂凑。 密钥对 (d, P) 通用;签名和加密使用不同的派生公式,不可混用。 安全强度:256位 → 等效RSA-3072

2.7 实现中的几个坑

  • 标量乘法性能: 256位的点乘在软件上大约需要几百微秒。嵌入式设备(比如Cortex-M4)可以做到<5ms,但要注意使用上开窗法(wNAF)优化。我曾在一个MCU上裸写,没优化,结果一次签名花了50ms……后来换了混合坐标wNAF,直接降到3ms。
  • 随机数质量: SM2私钥、临时k、加密时临时私钥都需要真随机。别用rand()。我推荐用硬件TRNG或系统getrandom()。
  • 中间人攻击: 密钥交换协议如果没有验证公钥,容易遭到中间人攻击。SM2-KE用杂凑确认码解决了这个问题。但实现时千万注意确认码的计算方式要和标准一致。
  • 曲线参数校验: 接收到公钥时,必须验证公钥在曲线上(即坐标满足曲线方程),并且不是无穷远点。有些实现漏了这步,会被注入无效曲线攻击……我吃过这个亏。

2.8 为什么中国要用SM2?

实质原因是:国外ECC算法的实现可能有后门(比如P-256曲线是否含NSA后门的争议),而SM2的曲线参数由中国自主生成,可以规避供应链风险。而且SM2同时提供了签名、加密、密钥交换三个功能,统一了算法体系,减少了维护成本。

我个人认为,在政务、金融、电力这些关键基础设施中,SM2已经是必选项。即使做国际业务,也要考虑叠加国密支持。你想想看,如果将来国际间互认国密标准,现在铺好SM2就省心了。

避坑指南: 我曾经把一个项目中所有ECDSA签名换成SM2时,发现原有签名长度(64字节)变成64字节?不对,SM2签名也是64字节。但SM2公钥比ECDSA公钥多了1字节头部。这个细节容易导致数据库字段长度不够。记得检查底层存储。

以上就是SM2算法原理的核心归纳。下次你遇到需要实现SM2时,可以直接拿这张图对照着做。代码方面,推荐用OpenSSL 1.1.1+ 的国密引擎,或者GmSSL库。自己手写的话注意测试向量——国家密码管理局官网有标准测试数据,跑通它们才算做对了。