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就是上表中的基点阶。生成过程其实很简单:
- 获取一个高质量的随机数源(比如操作系统的/dev/urandom)
- 生成256位的随机字节序列
- 将其转换为大整数,检查是否在有效范围内
- 如果不在范围内,重新生成
你想想看,如果随机数生成器出了问题,整个密钥体系就崩塌了。我在项目中遇到过一台老旧服务器,它的硬件随机数生成器已经损坏,结果生成的私钥全是重复的——那场面,真是灾难。
警告: 绝对不要使用伪随机数生成器(如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密钥对生成的整个流程串起来了。我每次给团队培训都会画一遍——它帮你理解每个环节的输入输出。
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上解析会报错。
嗯,密钥对生成看起来简单,但每个细节都关系到整个系统的安全。希望这些经验能帮你少走弯路。