2、SM2算法原理:椭圆曲线基础、密钥对生成、签名与验签核心流程、性能热点标注

2.1 椭圆曲线基础:为什么偏偏是它?

SM2基于椭圆曲线密码体制,也就是ECC。你可能会问,那么多数学结构,为什么选椭圆曲线?说白了,它在同样的安全强度下,**密钥长度比RSA短得多**。

SM2使用的是**素数域上的Weierstrass曲线**。标准方程是:

y² = x³ + ax + b  (mod p)

其中,a、b是系数,p是素数。国密标准SM2推荐了一条特定曲线,参数是固定的。我刚开始搞椭圆曲线时,总觉得这堆数很抽象,但后来发现,你只需要记住三个核心概念:

  • 基点G:椭圆曲线上的一个公共点,所有人共用。
  • 阶n:基点G的阶,表示$[n]G = O$(无穷远点)。n很大,保证了安全性。
  • 余因子h:通常取1,简化计算。

真正重要的是理解“标量乘法”。SM2里几乎所有的计算,落到最底层都是$[k]P$,也就是把点P跟自己加k次。嗯,这里要注意,这个“加法”不是坐标相加,是曲线上的几何加法。

我踩过的坑: 我曾经搞混过“点加”和“点倍”。在实现时,点加是两个不同点相加,点倍是同一个点自己加自己。千万别写错,否则整个签名验签流程全崩。

2.2 密钥对生成:就是这么简单直接

SM2的密钥对生成,在算法层面**极其简单**,但实现时却藏着不少优化点。

  1. 随机数生成:生成一个随机数d,范围在$[1, n-2]$。注意,n是上面说的阶。
  2. 计算公钥:$P = [d]G$。d是私钥,P是公钥。

怎么算?就是标量乘法$[d]G$。你想想看,d是一个256位的随机数,凭空硬算256次点加,性能肯定灾难。所以这里就成为第一个性能热点。

性能热点 #1:标量乘法 $[k]P$ —— 占整个密钥生成计算量的99%以上。

我建议团队在写SM2的密钥生成时,直接上**Window-Non-Adjacent-Form (wNAF)**算法。我之前做一款单片机的国密适配,没加wNAF,生成一对密钥用了1.2秒,优化后降到320毫秒。这个差距,就是是否理解椭圆曲线计算本质的差距。

2.3 签名与验签:核心流程与避坑指南

SM2的签名验签流程,很多人觉得复杂,其实拆解开来就几步。

签名过程(用户A要签消息M)

我个人习惯把SM2签名简化为“2次变换+1次标量乘”:

  1. 预处理:Z_A = H_{256}(ENTLA || ID_A || a || b || x_G || y_G || x_A || y_A),然后M' = Z_A || M。
  2. 算哈希:e = H_{256}(M')。
  3. 生成随机数:k,范围$[1, n-1]$。
  4. 算椭圆曲线点:$(x_1, y_1) = [k]G$。
  5. 算部分签名:r = (e + x_1) mod n。如果r等于0或$r + k = n$,就要重试。
  6. 算最终签名:s = ( (1 + d_A)^{-1} * (k - r * d_A) ) mod n。如果s等于0,也要重试。

最终签名值是(r, s),长度为2 * 256 = 512位。

注意随机数质量! 我在项目中遇到过,伪随机数生成器泄露了,攻击者拿到了k值,就能直接解出私钥d_A。SM2签名里,k值必须是一次性的、不可预测的、不可重复的。我见过有人用getpid() + time()混出来的随机数……嗯,那是漏洞。

验签过程(用户B收到消息M'和签名(r, s))

验签就是验证签名是否真由用户A生成:

  1. 初步校验:检查r和s是否都在$[1, n-1]$。不满足直接判无效。
  2. 算哈希:e' = H_{256}(Z_A || M')。
  3. 算参数:t = (r + s) mod n。如果t等于0,判无效。
  4. 算椭圆曲线点:$(x_1', y_1') = [s]G + [t]P_A$。这是个综合计算。
  5. 验算:$R = (e' + x_1') mod n$。如果R等于r,验签通过;否则不通过。

你看,签名流中有一次标量乘,验签流中有两次标量乘(还是综合了倍点和加点的)。这个计算量,直接决定了SDK的吞吐量。

2.4 性能热点标注:算力砸在哪?

我们直接上总结表。这是我梳理SM2底层时,列出的硬算瓶颈:

操作环节 具体运算 性能占比 优化意义
密钥生成 $[d]G$ 标量乘法 99%+ 高
签名(随机数阶段) $[k]G$ 标量乘法 90%-95% 极高
验签(综合阶段) $[s]G + [t]P_A$ 双标量乘法 95%+ 最高
模逆运算 $(1 + d_A)^{-1} \mod n$ 约2% 中
SM2算法核心流程与性能热点框架图 SM2算法核心流程与性能热点框架 椭圆曲线参数 (p, a, b, G, n) 基点G / 阶n 固定的系统参数 密钥对生成 私钥d ← 随机数 公钥P = [d]G 签名流程 预处理 (Z_A = H(ID || 参数)) 计算哈希 e = H(Z_A || M) 生成随机数k 标量乘: [k]G (性能热点!) 计算 r = (e + x_1) mod n 计算 s = (1+d)^-1 * (k - r*d) 验签流程 校验 r,s ∈ [1,n-1] 计算哈希 e' = H(Z_A || M') 计算 t = (r + s) mod n 双标量乘: [s]G + [t]P (性能热点!) 计算 R = (e' + x_1') mod n 比较 R ?= r 核心性能热点 标量乘法 [k]P 和 双标量乘法 [s]G + [t]P 占整体计算量 85%+ 优化方向:wNAF/固定窗口/预计算表 & 模逆算法选型
一个我遇见的案例: 某次做SDK适配,发现验签耗时是签名的2.5倍。看了底层才发现,验签里面做了两次独立的标量乘,而我用的软件实现没有复用中间结果。后来改成双标量乘的联合优化方法,耗时降了40%。所以,不要小看这个优化点。