椭圆曲线数学基础:有限域、椭圆曲线方程、点加和倍点运算、阶与子群
这一章,我们聊聊椭圆曲线背后的数学。别看公式有点抽象,说白了,国密 SM2 签名的强度就靠它。我刚开始接触时也觉得头疼,后来发现只要抓住几个核心概念,后面就顺了。
1. 有限域:椭圆曲线的“场地”
椭圆曲线不能定义在实数域上,为啥?因为实数域无限大,计算机没法算,而且离散对数问题不好做。所以国密方案选了有限域 Fp,也就是一个大素数 p 生成的整数集合。
有限域长什么样?
- 元素是
0, 1, 2, ..., p-1,共p个。 - 加减乘除都在模
p下进行。 - 除法等于乘以模逆元。
mod p,就提醒自己“所有运算做完都要取模”,尤其是除法,必须先求逆元再乘。
举个例子:p = 7,3 + 5 ≡ 1 (mod 7),2 * 4 ≡ 1 (mod 7),所以 2 的逆元是 4。嗯,就这么简单。
2. 椭圆曲线方程:Weierstrass 标准型
国密 SM2 使用的曲线方程为:y² ≡ x³ + ax + b (mod p),其中 4a³ + 27b² ≠ 0 (mod p)。这个条件保证曲线没有奇异点(就是没有尖点或自交)。
为什么要有这个约束?因为如果曲线有奇点,攻击者能找到“奇点”来破解离散对数问题。我曾在测试网见过一条参数错误的曲线,验证签名死活不通过,最后发现就是 4a³+27b² mod p = 0,那条曲线废了。
| 参数 | 说明 | 国密推荐值 |
|---|---|---|
p |
大素数(256位) | 0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF |
a |
系数 | 0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC |
b |
系数 | 0x28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93 |
G |
基点(x,y) | 标准中定义的坐标 |
n |
基点阶 | 0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123 |
3. 点加和倍点运算:椭圆曲线上的群运算
曲线上两个点 P 和 Q,可以“相加”得到第三个点 R。规则是:过 P 和 Q 画一条直线,与曲线交于第三点,再关于 x 轴对称,就是 R。
如果 P = Q,那就是倍点运算——过 P 做切线,交于曲线另一点,再对称。
这个运算看起来几何味很浓,但实际计算只需要代数公式。拿点加举例:
// 点加 P + Q = R (x3, y3)
λ = (y2 - y1) * inv(x2 - x1) mod p // 斜率
x3 = λ² - x1 - x2 mod p
y3 = λ * (x1 - x3) - y1 mod p
倍点类似,斜率用 λ = (3x1² + a) * inv(2y1) mod p。
我个人建议把点加和倍点写成两个独立函数,别混在一起,否则调试时会疯掉。我以前就吃过这个亏:倍点公式里把 a 忘加了,结果算出来的点全错。
4. 阶与子群:理解有限循环群
椭圆曲线上所有点加上无穷远点 O,构成一个阿贝尔群。这个群有多少个点?就是曲线的“阶”,记为 #E(Fp)。一般用 Schoof–Elkies–Atkin 算法算。
但实际 SM2 里我们只关心一个子群:由基点 G 生成的循环子群。G 的阶 n 就是最小的正整数使得 n*G = O。国密标准规定 n 是素数,且长度跟 p 一样(256 位)。
为什么阶必须是素数?因为如果阶含有小因子,就容易受到 Pohlig–Hellman 攻击——把离散对数分解到小阶子群去算。我曾在某老旧库中看到一条曲线阶是 2 * 素数,导致私钥可以被分解成两个小规模子群分别求解,整个系统几乎透明。
d ∈ [1, n-1],公钥就是 P = d * G。对攻击者来说,要从 P 反推出 d(即离散对数问题)在今天的计算能力下不可能。
5. 国密推荐曲线参数一览
SM2 标准中定义了以下参数,建议写死到代码里:
p = 0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF
a = 0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC
b = 0x28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93
Gx = 0x32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7
Gy = 0xBC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0
n = 0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123
注意:n 近似等于 p,只差一点点,这正是安全设计——避免根据点的坐标泄露阶的信息。
6. 知识体系图
下面这幅 SVG 把本章核心关系梳理出来了:
7. 实战场景:SM2 签名与验签中的数学
有了这些基础,SM2 签名过程就很好理解了:
- 选取随机数
k ∈ [1, n-1]。 - 计算点
(x1, y1) = k*G。注意k每次都要新鲜,不能重复。我见过一个事故:有人用了固定 k,导致两个不同的签名消息都能推导出私钥。嗯,那是灾难。 - 用
x1参与哈希计算得到r。 - 计算
s = (1 + d)⁻¹ * (k - r*d) mod n。
验签方恢复出 t = (r + s) mod n,然后算 (x1', y1') = s*G + t*P,检查 x1' == r。这背后全是椭圆曲线点加和倍点。
k 和 G 算一遍 k*G,和标准库结果对比。这一步通了,后面签名基本不会出大问题。
总结一句话:掌握有限域、曲线方程、点加/倍点、阶与子群,你就掌握了 SM2 的数学心脏。后面 SM3、SM4 虽然不同,但数学深度都不及椭圆曲线。这块啃下来,剩下就是调 API 的事了。