3. SM2密钥对生成:椭圆曲线参数、私钥生成、公钥推导、密钥文件格式

SM2密钥对的生成,是整个国密体系的基石。说白了,就是要在椭圆曲线上找到一个点,然后把这个点的坐标和你的私钥关联起来。我刚开始接触SM2时,觉得这跟RSA差不多,后来才发现——嗯,完全不是一回事。

3.1 椭圆曲线参数:SM2的“游戏规则”

SM2使用的是一条特定的椭圆曲线,参数由国家密码管理局定义。我个人习惯把这些参数称为“SM2的DNA”——它们决定了整个密码系统的安全边界。

参数 含义 SM2标准值(16进制)
p 素数模数,定义有限域GF(p) FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFF
a 椭圆曲线方程 y² = x³ + ax + b 中的系数 FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFC
b 椭圆曲线方程中的常数项 28E9FA9E 9D9F5E34 4D5A9E4B CF6509A7 F39789F5 15AB8F92 DDBCBD41 4D940E93
Gx 基点G的x坐标 32C4AE2C 1F198119 5F990446 6A39C994 8FE30BBF F2660BE1 715A4589 334C74C7
Gy 基点G的y坐标 BC3736A2 F4F6779C 59BDCEE3 6B692153 D0A9877C C62A4740 02DF32E5 2139F0A0
n 基点G的阶(素数) FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF 7203DF6B 21C6052B 53BBF409 39D54123
小提示: 这些参数在OpenSSL 1.1.1+中已经内置,你不需要手动输入。但如果你在做硬件移植或嵌入式开发,记得用官方文档逐字节核对——我曾经因为一个字节的拷贝错误,排查了整整两天。

3.2 私钥生成:随机数的艺术

SM2私钥本质上是一个大整数,取值范围在 [1, n-2] 之间。n就是上表中的基点阶。生成过程其实很简单:

  1. 获取一个高质量的随机数源(比如操作系统的/dev/urandom)
  2. 生成256位的随机字节序列
  3. 将其转换为大整数,检查是否在有效范围内
  4. 如果不在范围内,重新生成

你想想看,如果随机数生成器出了问题,整个密钥体系就崩塌了。我在项目中遇到过一台老旧服务器,它的硬件随机数生成器已经损坏,结果生成的私钥全是重复的——那场面,真是灾难。

警告: 绝对不要使用伪随机数生成器(如C语言的rand())来生成私钥。必须使用密码学安全的随机数生成器(CSPRNG)。在Linux上,优先使用getrandom()系统调用或/dev/urandom。

3.3 公钥推导:从私钥到椭圆曲线上的点

公钥的计算公式很简单:P = d * G

其中d是私钥,G是基点,*表示椭圆曲线上的标量乘法。这个运算不可逆——从公钥反推私钥,就是椭圆曲线离散对数问题(ECDLP),目前没有多项式时间算法。

公钥是一个点,包含(x, y)两个坐标。在SM2中,每个坐标是256位,所以未压缩的公钥是64字节(不含前缀)。

核心理解: 公钥推导是单向的。你可以从私钥算出公钥,但反过来不行。这就是非对称密码学的根基。

3.4 密钥文件格式:PEM vs DER

生成密钥对之后,怎么存?怎么传?这就涉及到文件格式了。我见过太多人把PEM和DER搞混,其实它们的关系很简单:

  • DER:二进制格式,使用ASN.1编码,紧凑但不可读
  • PEM:对DER进行Base64编码,加上头尾标识,可读且便于文本传输

说白了,PEM就是DER的“文本外壳”。

3.4.1 SM2私钥的PEM格式

-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBG0wawIBAQQgYF0g3I5zXqZJ3kFz
Vq3Y5k8vKjKX8X5z7Y9p0G2h3Z6hRANCAAQYxJ6vL5z7Y9p0G2h3Z6hRANCAAQY
xJ6vL5z7Y9p0G2h3Z6hRANCAAQYxJ6vL5z7Y9p0G2h3Z6hRANCAAQYxJ6vL5z7Y
-----END PRIVATE KEY-----

3.4.2 SM2公钥的PEM格式

-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEYxJ6vL5z7Y9p0G2h3Z6hRANCAAQY
xJ6vL5z7Y9p0G2h3Z6hRANCAAQYxJ6vL5z7Y9p0G2h3Z6hRANCAAQYxJ6vL5z7Y
-----END PUBLIC KEY-----
经验之谈: 我建议在开发阶段使用PEM格式,方便调试和查看。生产环境中,如果对性能有要求,可以考虑使用DER格式,解析速度更快。

3.5 使用OpenSSL生成SM2密钥对

实际操作中,我们通常用OpenSSL命令行工具。下面是我常用的命令:

# 生成SM2私钥(PEM格式)
openssl ecparam -genkey -name SM2 -out sm2_private.pem

# 从私钥导出公钥
openssl ec -in sm2_private.pem -pubout -out sm2_public.pem

# 查看私钥详细信息
openssl ec -in sm2_private.pem -text -noout

# 转换为DER格式
openssl ec -in sm2_private.pem -outform DER -out sm2_private.der

执行完这些命令,你会看到类似这样的输出:

read EC key
Private-Key: (256 bit)
priv:
    00:9a:3b:5c:7d:8e:9f:0a:1b:2c:3d:4e:5f:6a:7b:
    8c:9d:0e:1f:2a:3b:4c:5d:6e:7f:8a:9b:0c:1d:2e:
    3f:4a:5b
pub:
    04:12:34:56:78:9a:bc:de:f0:12:34:56:78:9a:bc:
    de:f0:12:34:56:78:9a:bc:de:f0:12:34:56:78:9a:
    bc:de:f0:12:34:56:78:9a:bc:de:f0:12:34:56:78:
    9a:bc:de:f0:12:34:56:78:9a:bc:de:f0:12:34:56:
    78:9a:bc:de:f0
ASN1 OID: SM2
注意: 私钥文件一旦生成,必须妥善保管。权限设置为600(仅所有者可读写),并考虑使用硬件安全模块(HSM)或密钥管理服务(KMS)进行保护。我曾经见过有人把私钥放在Git仓库里——嗯,那家公司后来被通报了。

3.6 密钥文件格式对比

特性 PEM DER
编码方式 Base64 + 头尾标识 二进制ASN.1
可读性 高(文本可查看) 低(二进制乱码)
文件大小 约比DER大33% 紧凑
解析速度 需要Base64解码 直接解析
常见用途 配置文件、API传输 证书存储、硬件设备

3.7 知识体系总览

下面这张图,把SM2密钥对生成的整个流程串起来了。我每次给团队培训都会画一遍——它帮你理解每个环节的输入输出。

SM2密钥对生成流程 SM2密钥对生成流程 椭圆曲线参数 p, a, b, G, n 随机数生成 CSPRNG / /dev/urandom 私钥 d 256位随机整数 ∈ [1, n-2] 公钥 P = d * G 椭圆曲线标量乘法 密钥文件格式 PEM / DER 密钥对输出 私钥文件 + 公钥文件 💡 关键理解 • 椭圆曲线参数是固定的,由国家密码管理局定义 • 私钥必须来自密码学安全的随机数源 • 公钥推导是单向的,从私钥到公钥容易,反过来不可能

3.8 避坑指南

最后,分享几个我踩过的坑:

  • 随机数质量:我曾经在Docker容器里发现/dev/random阻塞,导致密钥生成超时。后来改用getrandom()系统调用,问题解决。
  • 私钥范围检查:SM2标准要求私钥在[1, n-2]之间,但有些实现只检查[1, n-1]。虽然概率极低,但万一生成n-1,整个密钥对就废了。
  • 公钥压缩:SM2支持压缩格式的公钥(只存x坐标和y的奇偶性),但很多国密网关不支持。我建议统一使用未压缩格式(04前缀 + x + y)。
  • PEM换行:PEM格式要求每行64个字符,换行符必须是LF(\n)。Windows环境下生成的CRLF换行,在Linux上解析会报错。

嗯,密钥对生成看起来简单,但每个细节都关系到整个系统的安全。希望这些经验能帮你少走弯路。