密钥生成原理:椭圆曲线基础、基点与阶、公私钥对生成数学原理
聊到 SM2,绕不开椭圆曲线。很多人一听到「椭圆曲线」就觉得头大,觉得这是纯数学家玩的东西。其实没那么玄乎。我刚开始接触的时候也懵,后来在项目中踩过几次坑,才真正理解它到底在干什么。
说白了,椭圆曲线就是一条方程,长这样:y² = x³ + ax + b。SM2 用的是一条特定的曲线,参数是固定的。你不需要自己选曲线,直接用国家给的就行。我个人习惯把这条曲线想象成一个「密码学游乐场」,所有的运算都在这个游乐场里进行。
椭圆曲线长什么样?
这条曲线不是椭圆,名字有点误导人。它是一条关于 x 轴对称的曲线,形状像拱桥。SM2 使用的曲线方程是:
y² = x³ + ax + b
其中 a = FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFC
b = 28E9FA9E 9D9F5E34 4D5A9E4B CF6509A7 F39789F5 15AB8F92 DDBCBD41 4D940E93
这些参数是国密标准里写死的。嗯,这里要注意:千万不要自己改参数。我在项目中见过有人想「优化」参数,结果搞出来的曲线根本不安全,后来全部重做。
核心概念:椭圆曲线上的点构成一个群。两个点可以「相加」,结果还是曲线上的另一个点。这个加法规则不是普通的坐标相加,而是几何意义上的——过两点画直线,与曲线交于第三点,再关于 x 轴对称,就是和。
基点 G 与阶 n
有了曲线,还需要一个起点,这就是基点 G。SM2 的基点 G 是一个固定点,坐标也是标准里写死的:
Gx = 32C4AE2C 1F198119 5F990446 6A39C994 8FE30BBF F2660BE1 715A4589 334C74C7
Gy = BC3736A2 F4F6779C 59BDCEE3 6B692153 D0A9877C C62A4740 02DF32E5 2139F0A0
基点 G 有一个重要的属性——阶 n。阶的意思是:从 G 开始,不断做自加运算(G + G + G + ...),经过多少次会回到无穷远点(单位元)?这个次数就是 n。
SM2 的阶 n 是:
n = FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF 7203DF6B 21C6052B 53BBF409 39D54123
为什么阶这么重要?你想想看,如果阶太小,那私钥的范围就很小,暴力破解很容易。SM2 的 n 是一个 256 位的素数,保证了安全性。我记得有一次做安全审计,对方问「你们的阶校验了吗?」我当时一愣——后来才知道,有些实现会忽略检查公钥是否在正确的子群中,这是个安全隐患。
避坑指南:我曾经在对接第三方 SM2 库时,发现对方没有校验公钥是否在基点 G 生成的子群中。这意味着攻击者可以注入一个低阶点,诱导你的系统泄露私钥。所以,接收公钥后一定要检查:公钥是否在曲线上?公钥乘以阶 n 是否等于无穷远点?
公私钥对的生成数学原理
好了,曲线有了,基点有了,阶也有了。怎么生成密钥对?
过程其实很简单:
- 选一个随机数 d,范围是 [1, n-2]。这个 d 就是你的私钥。
- 计算公钥 P = d * G。这里的乘法是椭圆曲线上的标量乘法,不是普通整数乘法。
就这么两步。私钥是一个随机数,公钥是曲线上的一个点。为什么这样是安全的?因为从公钥 P 反推私钥 d,需要解椭圆曲线离散对数问题——目前没有多项式时间的算法,256 位的曲线足够安全。
我习惯把私钥比作「门禁卡密码」,公钥比作「门锁」。你可以把门锁给任何人,但只有你有密码能开门。别人看到门锁,不可能反推出密码。
个人经验:生成随机数 d 时,一定要用密码学安全的随机数生成器。我见过有人用 rand() 或者 Math.random() 生成私钥,结果私钥空间被严重缩小,几分钟就能被暴力破解。SM2 标准推荐使用国家密码管理局认可的随机数发生器。
标量乘法怎么算?
公钥计算 P = d * G 看起来简单,但背后涉及大量的点加和倍点运算。举个例子,如果 d = 100,那就要算 G + G + ... + G(100 次)。实际中 d 是 256 位的数,不可能真的加那么多次。
工程上用的是「双倍-累加」算法:
输入:私钥 d(二进制表示),基点 G
输出:公钥 P = d * G
1. 初始化 Q = 无穷远点
2. 从 d 的最高位到最低位遍历:
a. Q = 2 * Q(倍点运算)
b. 如果当前位为 1,则 Q = Q + G(点加运算)
3. 返回 Q
这个算法的时间复杂度是 O(log₂(d)),对于 256 位的私钥,只需要 256 次倍点和平均 128 次点加。效率很高。
我当年第一次实现这个算法时,犯了一个低级错误——忘记处理无穷远点。结果在某些边界条件下,公钥算出来全是错的。调试了两天才发现,唉。
密钥生成流程总览
下面这张图把整个流程串起来了:
密钥对验证
生成完密钥对,别急着用。我建议做一次验证:
- 私钥检查:确保 d 在 [1, n-2] 范围内,不能为 0,不能为 n-1。
- 公钥检查:把公钥代入曲线方程,验证
Py² ≡ Px³ + a*Px + b (mod p)。 - 子群检查:计算
n * P,结果应该是无穷远点。这能确保公钥在正确的子群中。
这些检查看起来繁琐,但能避免很多坑。我曾经接手过一个项目,对方生成的公钥不在曲线上,结果签名验证时好时坏,排查了整整一周才发现是密钥生成环节的问题。
总结一下:SM2 密钥生成的核心就是「随机数 + 标量乘法」。曲线参数和基点是固定的,你只需要生成一个安全的随机数作为私钥,然后用标量乘法算出公钥。就这么简单,但每一步都有细节要注意——随机数质量、范围检查、公钥验证,缺一不可。