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

SM2椭圆曲线公钥密码算法,是我国商用密码体系中的核心算法之一。它包含了密钥生成、数字签名、加密和密钥交换这几大功能。说白了,它就是用椭圆曲线上的点运算来做“数学锁”,比起RSA那种大整数分解的锁,同等安全强度下钥匙(密钥)长度短得多,运算也快。

我个人做国密改造项目时,第一次把SM2签名的性能压到线上环境,发现一个签名不到1ms,验证也就2ms左右,比同安全级别的RSA-2048快了好几倍。嗯,这个差距在IoT和云原生的Secrets Manager场景里特别明显。

2.1 椭圆曲线的基本概念

我们先不推导复杂的数学公式,你把它想象成一条光滑的曲线就好。SM2使用的是定义在素域 \(F_p\) 上的椭圆曲线,标准方程为:

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

其中 \(a\) 和 \(b\) 是系数,\(p\) 是个大素数。再加上一个无穷远点O,所有满足这个方程的点加上O,就构成了一个加法群。什么叫群?就是点之间可以做加法运算,结果还是群里的点。

实用视角:你不用每次都自己解方程,SM2的标准(GM/T 0003)已经帮你把曲线参数定死了。比如 SM2 推荐曲线 p = 0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF,a、b、基点G、阶n都是固定值。你在代码里直接引用标准库就行,别自己改。

关键点:椭圆曲线的安全性依赖于“椭圆曲线离散对数问题”(ECDLP)。已知基点G和公钥Q = dG,求私钥d在计算上不可行。

2.2 SM2密钥对生成

生成SM2密钥对非常简单。私钥就是一个随机大整数 \(d\),取值范围在 [1, n-2];公钥就是 \(Q = d \cdot G\),其中G是椭圆曲线上的基点,用标量乘法算出一个点坐标 \((x_Q, y_Q)\)。

我记得有一次同事问:“公钥能不能直接存成x坐标?存y不是多占空间吗?”我跟他解释,SM2公钥长度是65字节(压缩格式33字节),但很多Secrets Manager存储场景建议用完整公钥,免得解压缩时引入额外的校验逻辑,反而容易出错。

下面这张图概括了SM2的密钥生成与核心运算流程:

SM2 椭圆曲线公钥密码算法核心流程 密钥生成 私钥 d (随机数) 公钥 Q = d·G 数字签名 (SM2-2) 公钥加密 (SM2-4) 密钥交换 (SM2-3) 签名值 (r,s) 密文 C 协商密钥 K SM2 使用同一对密钥完成签名、加密、密钥交换,但实践中建议签名密钥与加密密钥分开管理

2.3 SM2数字签名算法

SM2数字签名算法整体分为签名生成和签名验证。核心思想是:签名者用自己的私钥对消息的哈希值计算出一个签名(r, s),验证者用公钥检查这个签名是否对应消息。

签名流程大致如下:

  1. 计算消息的哈希值 e = SM3(Z || M),其中 Z 是签名者身份标识与公钥的摘要。
  2. 生成随机数 k,计算椭圆曲线点 (x1, y1) = k · G。
  3. 计算 r = (e + x1) mod n,如果 r = 0 或 r + k = n 则重新选 k。
  4. 计算 s = ((1 + d)^(-1) · (k − r · d)) mod n,如果 s = 0 则重新选 k。
  5. 输出签名 (r, s)。

你想想看,为什么要这么算?关键在于不能让攻击者从签名反推私钥,同时还要保证同一个消息每次签名结果不同(因为有随机数k)。

我曾经踩过一个坑:SM2签名中随机数 k 不能重复使用。如果两次签名用了同样的 k,攻击者只要拿到两个签名就能直接推导出私钥 d。这在Secrets Manager里尤其危险,因为机器可能批量生成签名。一定要确保随机数生成器有足够熵源,最好用硬件随机数或者内核的 /dev/urandom。

2.4 SM2公钥加密算法

SM2加密(GM/T 0003.4-2012)采用的是混合加密:先用点乘生成一个共享秘密,然后用对称算法(SM4)加密实际数据。流程分加密和解密两端。

加密端:

  • 生成随机数 r,计算点 R = r · G。
  • 计算共享点 S = r · Q(Q 是接收方公钥)。
  • 从 S 中导出密钥派生物 K = KDF(x_S || y_S, klen),用于对称加密。
  • 用 K 加密消息 M 得到密文 C = M ⊕ K(按位异或,SM4的话就是标准分组加密)。
  • 计算杂凑值 C3 = SM3(x_S || M || y_S),用于完整性校验。
  • 输出密文三元组 (R, C, C3)。

解密端:

  • 用私钥 d 计算 S = d · R。
  • 同样导出的 KDF,解密得到 M'。
  • 验证 SM3(x_S || M' || y_S) 是否等于 C3。

我个人觉得这种混合设计很巧妙——公钥运算只用来保护一个小型的共享秘密,具体数据加密交给对称算法,性能就能上去。在Secrets Manager里,很多时候我们要加密存储的凭据体积并不大,但每条都要独立密钥,SM2加密的代价完全可以接受。

2.5 SM2密钥交换协议

SM2密钥交换(SM2-3)允许通信双方在不安全的信道上协商出一致的会话密钥。协议基于ECDH的思路,但加了一些身份认证和防重放措施。

标准定义了两个角色:发起方A和响应方B。流程简化为:

  1. A生成随机数 r_A,计算 R_A = r_A · G,发送给B。
  2. B收到后,生成随机数 r_B,计算 R_B = r_B · G,连同身份杂凑一起发回。
  3. 双方各自计算共享点 V = (r_A + d_A) · (R_B + Q_B) 或者类似变换(细节有补偿)。
  4. 从V的坐标导出会话密钥 K。

为什么需要额外加入身份信息?我曾经遇到过一种中间人攻击模拟:如果不做公钥认证,敌手可以替换公钥来劫持密钥。SM2密钥交换在流程里嵌入了双方ID的公钥验证,能抵抗这种攻击。

SM2三种核心功能对比
功能 输入 输出 计算量特征
数字签名 (SM2-2) 消息M, 私钥d 签名(r, s) 签名1次点乘, 验证2次点乘
公钥加密 (SM2-4) 消息M, 公钥Q 密文(R, C, C3) 加密1.5次点乘, 解密1次点乘
密钥交换 (SM2-3) 双方公私钥, 随机数 会话密钥K 每方2次点乘

工程上的一点建议:在Secrets Manager里,管理凭证的加密我倾向于用SM2加密(而非签名+加密的复合方案),因为加密后可以直接存R、C、C3三个字段,解密时只需一次点乘,很适合单次读取场景。如果还要防篡改,再加上签名就够用了。

2.6 椭圆曲线标量乘法的实现注意点

整个SM2的计算瓶颈就是标量乘法(点乘)。比如刚才提到的 k · G 或者 r · Q,这些运算占了90%以上的CPU时间。实现时要注意:

  • 使用固定时间算法:你的代码不能根据私钥或随机数的比特位产生分支耗时差异,否则侧信道攻击能猜出私钥。我见过有开源库用简单的“双倍-加”但没做常数时间处理,直接被SPA攻击打到泄露。
  • 预计算优化:如果多次使用同一个基点G(比如密钥交换时每次都算k·G),可以在内存里预计算好G的2倍点、4倍点……大幅加速。
  • 坐标选择:SM2推荐使用仿射坐标,但雅可比坐标下点加只需要14次乘法,比仿射快。我这边的经验是,标准库一般已经用混合坐标了,你不需要手写。

需要你特别警惕:有些平台(比如JVM或者浏览器里的JavaScript)没有原生的SM2实现,用纯软件做点乘很可能性能翻车。一定要在目标环境里先Benchmark一下,确保签名、加解密都在可接受延时内。

好,以上就是SM2椭圆曲线公钥密码算法的基本原理。从密钥生成→数字签名→加密→密钥交换,加上标量乘法实现的避坑点,基本覆盖了你在Secrets Manager里用SM2时可能遇到的核心知识点。嗯,动手写代码之前,先把这些底层逻辑搞清楚,后面遇到bug才能快速定位。