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

这些参数是公开的,也是固定的。你不需要自己选曲线,直接用就行。我个人习惯把曲线参数写在一个头文件里,编译时静态链接,避免运行时加载出错。

小提示: 椭圆曲线上的加法不是普通的坐标加法。它有一套几何规则:过曲线上两点 P 和 Q 画直线,交曲线于第三点 R',然后 R' 关于 x 轴对称的点就是 P+Q。这就是所谓的「弦切法则」。

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²⁵⁶,这个量级目前是安全的。

注意: 基点 G 和阶 n 必须使用国密标准中规定的值。我曾经见过有人从网上随便找了个椭圆曲线参数,结果生成的密钥对根本不能互通。标准就是标准,别自己发明。

2.3 密钥对生成算法

SM2 密钥对生成的流程,其实就三步:

  1. 生成私钥 d:在 [1, n-2] 范围内随机选一个大整数。
  2. 计算公钥 P:P = dG,即私钥乘以基点。
  3. 输出密钥对:(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 内部会调用随机数生成器,然后执行点乘运算。点乘是椭圆曲线中最耗时的操作,我后面会专门讲怎么优化它。

核心逻辑: 私钥 d 是一个随机数,公钥 P 是 d 在椭圆曲线上的映射。从 P 反推 d 就是椭圆曲线离散对数问题,目前没有多项式时间算法。这就是 SM2 安全性的数学基础。

2.4 随机数质量对安全性的影响

这是整个密钥生成环节中最容易被忽视的一点。随机数质量不好,密钥就不安全。我举个例子:

2012 年,有研究者发现某些 Android 设备生成的比特币私钥存在严重漏洞。原因是这些设备的随机数生成器初始化时熵不足,导致多个用户生成了相同的私钥。嗯,SM2 也一样。

随机数质量的影响体现在两个方面:

问题 后果 我的建议
熵不足 私钥空间缩小,可被暴力枚举 使用硬件随机数发生器或系统熵池
随机数重复 相同私钥被多次生成 生成后校验唯一性(虽然概率极低)
可预测性 攻击者可推断私钥 避免使用 rand() 等伪随机函数

在 Linux 系统上,我习惯从 /dev/urandom 读取随机数。OpenSSL 默认会使用系统熵源,但如果你在嵌入式设备上开发,一定要检查随机数生成器的实现。

避坑指南: 我曾经在一个 IoT 项目里,设备刚上电时熵池还没初始化好,结果连续三台设备生成了完全相同的密钥对。排查了一整天才发现是随机数种子的问题。从那以后,我每次生成密钥对之前都会先调用 RAND_status() 检查熵池状态。

2.5 密钥生成流程总览

下面这张图把整个密钥生成过程串起来了。你可以看到,从系统熵源开始,经过随机数生成、私钥生成、点乘运算,最终得到密钥对。

SM2 密钥生成流程 系统熵源 随机数生成器 私钥 d ∈ [1, n-2] 点乘运算 P = dG 密钥对 (d, P) 关键点 • 私钥必须随机均匀 • 公钥由私钥唯一确定 • 从公钥无法反推私钥 性能瓶颈 • 点乘运算最耗时 • 随机数生成不可忽略

这张图里,最值得关注的是「随机数生成器」和「点乘运算」这两个环节。前者决定了密钥的安全性,后者决定了生成的性能。我后面会专门讲点乘的优化技巧,这里先记住一句话:随机数质量是安全性的基石,点乘效率是性能的关键。

2.6 本章小结

SM2 密钥生成,本质上就是「选一个随机数,算一个点」。但每一步都有坑:

  • 曲线参数必须用国标规定的值,别自己改。
  • 基点 G 和阶 n 是固定的,别搞错。
  • 随机数生成器必须用安全的熵源,别用 rand()。
  • 点乘运算要优化,否则性能上不去。

我个人习惯在项目初始化时,先跑一遍密钥生成的自检程序,确保随机数生成器和点乘库都正常工作。这个习惯帮我避免过好几次线上事故。