2. SM2 密钥生成原理
密钥生成,是所有密码操作的起点。SM2 的密钥对生成,说白了就是在一个椭圆曲线上找一个点,然后算出一个大数。听起来简单,但里面的门道不少。我刚开始接触 SM2 时,觉得这不就是调个库函数嘛,直到有一次线上环境出现了重复密钥……嗯,从那以后我再也不敢轻视这一步了。
2.1 椭圆曲线基础
SM2 使用的椭圆曲线,是国家密码管理局定义的 sm2p256v1。它的方程是:
y² = x³ + ax + b (mod p)
其中参数是固定的:
| 参数 | 值(十六进制) |
|---|---|
| p | FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFF |
| a | FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFC |
| b | 28E9FA9E 9D9F5E34 4D5A9E4B CF6509A7 F39789F5 15AB8F92 DDBCBD41 4D940E93 |
这些参数是公开的,也是固定的。你不需要自己选曲线,直接用就行。我个人习惯把曲线参数写在一个头文件里,编译时静态链接,避免运行时加载出错。
2.2 基点 G 与阶 n
基点 G 是椭圆曲线上一个预先选好的点。SM2 的 G 点坐标也是固定的:
Gx = 32C4AE2C 1F198119 5F990446 6A39C994 8FE30BBF F2660BE1 715A4589 334C74C7
Gy = BC3736A2 F4F6779C 59BDCEE3 6B692153 D0A9877C C62A4740 02DF32E5 2139F0A0
阶 n 是 G 点的阶,也就是最小的正整数 n,使得 nG = O(无穷远点)。SM2 的 n 是:
n = FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF 7203DF6B 21C6052B 53BBF409 39D54123
为什么要有阶?因为密钥空间的大小就是 n。你想想看,如果 n 太小,暴力破解就变得可行。SM2 的 n 大约是 2²⁵⁶,这个量级目前是安全的。
2.3 密钥对生成算法
SM2 密钥对生成的流程,其实就三步:
- 生成私钥 d:在 [1, n-2] 范围内随机选一个大整数。
- 计算公钥 P:P = dG,即私钥乘以基点。
- 输出密钥对:(d, P)。
代码实现起来也很直接:
#include <openssl/ec.h>
#include <openssl/obj_mac.h>
EC_KEY *generate_sm2_keypair(void) {
EC_KEY *key = EC_KEY_new_by_curve_name(NID_sm2);
if (!key) return NULL;
// 生成密钥对
if (!EC_KEY_generate_key(key)) {
EC_KEY_free(key);
return NULL;
}
return key;
}
这段代码看起来简单,但背后做了不少事。EC_KEY_generate_key 内部会调用随机数生成器,然后执行点乘运算。点乘是椭圆曲线中最耗时的操作,我后面会专门讲怎么优化它。
2.4 随机数质量对安全性的影响
这是整个密钥生成环节中最容易被忽视的一点。随机数质量不好,密钥就不安全。我举个例子:
2012 年,有研究者发现某些 Android 设备生成的比特币私钥存在严重漏洞。原因是这些设备的随机数生成器初始化时熵不足,导致多个用户生成了相同的私钥。嗯,SM2 也一样。
随机数质量的影响体现在两个方面:
| 问题 | 后果 | 我的建议 |
|---|---|---|
| 熵不足 | 私钥空间缩小,可被暴力枚举 | 使用硬件随机数发生器或系统熵池 |
| 随机数重复 | 相同私钥被多次生成 | 生成后校验唯一性(虽然概率极低) |
| 可预测性 | 攻击者可推断私钥 | 避免使用 rand() 等伪随机函数 |
在 Linux 系统上,我习惯从 /dev/urandom 读取随机数。OpenSSL 默认会使用系统熵源,但如果你在嵌入式设备上开发,一定要检查随机数生成器的实现。
2.5 密钥生成流程总览
下面这张图把整个密钥生成过程串起来了。你可以看到,从系统熵源开始,经过随机数生成、私钥生成、点乘运算,最终得到密钥对。
这张图里,最值得关注的是「随机数生成器」和「点乘运算」这两个环节。前者决定了密钥的安全性,后者决定了生成的性能。我后面会专门讲点乘的优化技巧,这里先记住一句话:随机数质量是安全性的基石,点乘效率是性能的关键。
2.6 本章小结
SM2 密钥生成,本质上就是「选一个随机数,算一个点」。但每一步都有坑:
- 曲线参数必须用国标规定的值,别自己改。
- 基点 G 和阶 n 是固定的,别搞错。
- 随机数生成器必须用安全的熵源,别用 rand()。
- 点乘运算要优化,否则性能上不去。
我个人习惯在项目初始化时,先跑一遍密钥生成的自检程序,确保随机数生成器和点乘库都正常工作。这个习惯帮我避免过好几次线上事故。