第3章 曲线参数:p256、G点、阶n的数学定义
搞密码学,尤其是椭圆曲线这一块,参数定义是地基。地基歪了,上面全白搭。SM2 使用的推荐曲线,官方叫 sm2p256v1,本质上是一条定义在素域上的椭圆曲线,参数包括域大小 p、系数 a 和 b、基点 G,以及基点阶 n。这些数字不是拍脑袋给的,背后有一整套安全约束。我自己写优化库时,第一步就是把这几行常量抠出来反复对,少抄一个字节都会出事。
3.1 曲线方程与域参数
SM2 曲线方程跟绝大多数 ECC 标准一样:
y² ≡ x³ + ax + b (mod p)
其中 p 是 256 位素数,a = p − 3,b 是固定值。域参数 p 的十六进制如下:
p = FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFF
这个数等于 2²⁵⁶ − 2²²⁴ + 2¹⁹² + 2⁹⁶ − 1。为什么选这个形式?为了模约减运算能“省”几个时钟周期。我在做 x86 汇编优化时,利用它特殊的结构可以写出比通用 Montgomery 快 30% 的模乘,说白了就是硬件算力不够时,参数数学性质帮你撑速度。
系数 b 为:
b = 28E9FA9E 9D9F5E34 4D5A9E4B CF6509A7 F39789F5 15AB8F92 DDBCBD41 4D940E93
你直接在代码里硬编码就行,不要试图用哈希派生,我遇到过新手自己“推导”b 值,结果出来曲线根本不是 SM2。
3.2 基点 G 与阶 n
基点 G 是一个曲线上能被公开验证的点,它的坐标(十六进制,未压缩)是:
Gx = 32C4AE2C 1F198119 5F990446 6A39C994 8FE30BBF F2660BE1 715A4589 334C74C7
Gy = BC3736A2 F4F6779C 59BDCEE3 6B692153 D0A9877C C62A4740 02DF32E5 2139F0A0
阶 n 是 G 子群的素数阶:
n = FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF 7203DF6B 21C6052B 53BBF409 39D54123
这个 n 保证了 G 点自乘 n 次得到无穷远点,并且 n 与 p 接近(相差约 2¹²⁸),这是为了避免小步子攻击和 Pohlig-Hellman 攻击。我曾在一次性能压测中,用了错误的 n 值(把 P-256 的 n 直接拿来用),结果签名验证偶尔失败,排查了整整一个下午。嗯,从那以后我每条参数都写单元测试,逐个字段比对。
3.3 参数对比:SM2 vs NIST P-256
很多人以为 SM2 曲线和 NIST P-256 是同一根筋,其实 p 和 n 都不同。区别很微妙,但足以让互操作崩掉。我整理了一个表,你保存下:
| 参数 | SM2 sm2p256v1 | NIST P-256 (secp256r1) |
|---|---|---|
| p(十六进制前 8 字节) | FFFFFFFE FFFFFFFF | FFFFFFFF 00000001 |
| b | 28E9FA9E 9D9F5E34...(见上文) | 5AC635D8 AA3A93E7...(不同) |
| Gx | 32C4AE2C 1F198119... | 6B17D1F2 E12C4247... |
| Gy | BC3736A2 F4F6779C... | 4FE342E2 FE1A7F9B... |
| n | FFFFFFFE FFFFFFFF...7203DF6B 21C6052B 53BBF409 39D54123 | FFFFFFFF 00000000 FFFFFFFF BCE6FAAD A7179E84 F3B9CAC2 FC632551 |
3.4 数学定义归纳
下面的 SVG 把 SM2 曲线参数之间的关系画清楚了。你看一眼,复杂度不是线性叠加的。
ecdsa 库验证过,保证 n 确是 G 的阶,彻底安心。如果是自制实现,这一步不能省略。
数学归纳总结:
- SM2 曲线由五元组 (p, a, b, G, n) 定义,其中 a = p − 3 降低计算开销。
- n 必须为素数,且 n 尽可能接近 p,防止小阶子群攻击。
- G 的坐标必须在曲线上:将 Gx 代入方程,计算 Gy,检查是否等于给定值。
- 私钥的取值范围为 [1, n−1],不在此范围内的私钥会直接导致密钥无效。
3.5 代码示例:定义与快速检查
我习惯在项目里放一个 sm2_params.py,把参数写到常量里,然后写一个 _verify() 函数,每次 import 时自动跑一遍。给你看看骨架:
# sm2_params.py
p = 0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF
a = p - 3
b = 0x28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93
Gx = 0x32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7
Gy = 0xBC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0
n = 0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123
# 快速验证:检查 G 在曲线上 —— 我每次部署前都跑一次
def verify_G_on_curve():
lhs = (Gy * Gy) % p
rhs = (Gx * Gx * Gx + a * Gx + b) % p
return lhs == rhs
# 检查 n*G 是否为无穷远点(用倍点算法,略)
# 你想想看,这条检查如果过了,99% 的参数问题都被堵住了。
把参数吃透,后面加解密性能优化才有底气。说白了,曲线参数是算法的 DNA,一旦写错,后续所有优化都是白费力气。我个人习惯把它作为代码仓库的“入口校验”,每次 CI 都会跑一遍。你也试试,省掉的寻错时间远多于那几毫秒。嗯,这一章的核心就是:记住这五个常数,然后验证它们。