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的密钥生成与核心运算流程:
2.3 SM2数字签名算法
SM2数字签名算法整体分为签名生成和签名验证。核心思想是:签名者用自己的私钥对消息的哈希值计算出一个签名(r, s),验证者用公钥检查这个签名是否对应消息。
签名流程大致如下:
- 计算消息的哈希值 e = SM3(Z || M),其中 Z 是签名者身份标识与公钥的摘要。
- 生成随机数 k,计算椭圆曲线点 (x1, y1) = k · G。
- 计算 r = (e + x1) mod n,如果 r = 0 或 r + k = n 则重新选 k。
- 计算 s = ((1 + d)^(-1) · (k − r · d)) mod n,如果 s = 0 则重新选 k。
- 输出签名 (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。流程简化为:
- A生成随机数 r_A,计算 R_A = r_A · G,发送给B。
- B收到后,生成随机数 r_B,计算 R_B = r_B · G,连同身份杂凑一起发回。
- 双方各自计算共享点 V = (r_A + d_A) · (R_B + Q_B) 或者类似变换(细节有补偿)。
- 从V的坐标导出会话密钥 K。
为什么需要额外加入身份信息?我曾经遇到过一种中间人攻击模拟:如果不做公钥认证,敌手可以替换公钥来劫持密钥。SM2密钥交换在流程里嵌入了双方ID的公钥验证,能抵抗这种攻击。
| 功能 | 输入 | 输出 | 计算量特征 |
|---|---|---|---|
| 数字签名 (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才能快速定位。