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的密钥对生成,在算法层面**极其简单**,但实现时却藏着不少优化点。
- 随机数生成:生成一个随机数
d,范围在$[1, n-2]$。注意,n是上面说的阶。 - 计算公钥:$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次标量乘”:
- 预处理:
Z_A = H_{256}(ENTLA || ID_A || a || b || x_G || y_G || x_A || y_A),然后M' = Z_A || M。 - 算哈希:
e = H_{256}(M')。 - 生成随机数:
k,范围$[1, n-1]$。 - 算椭圆曲线点:$(x_1, y_1) = [k]G$。
- 算部分签名:
r = (e + x_1) mod n。如果r等于0或$r + k = n$,就要重试。 - 算最终签名:
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生成:
- 初步校验:检查r和s是否都在$[1, n-1]$。不满足直接判无效。
- 算哈希:
e' = H_{256}(Z_A || M')。 - 算参数:
t = (r + s) mod n。如果t等于0,判无效。 - 算椭圆曲线点:$(x_1', y_1') = [s]G + [t]P_A$。这是个综合计算。
- 验算:$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% | 中 |
一个我遇见的案例: 某次做SDK适配,发现验签耗时是签名的2.5倍。看了底层才发现,验签里面做了两次独立的标量乘,而我用的软件实现没有复用中间结果。后来改成双标量乘的联合优化方法,耗时降了40%。所以,不要小看这个优化点。