3、SM2密钥对生成:私钥生成规范、公钥推导计算、密钥存储格式(PEM/DER)

好,咱们进入第三章。密钥对生成,说白了就是给SM2算法造一把「锁」和一把「钥匙」。锁是公钥,可以公开;钥匙是私钥,必须藏好。

我刚开始接触国密时,觉得这不就是随机数加个椭圆曲线乘法嘛。后来踩了坑才发现——规范、推导、存储,每一步都有讲究。今天咱们就掰开揉碎讲清楚。

3.1 私钥生成规范

SM2私钥本质上是一个大整数,取值范围在 [1, n-2] 之间。这里的 n 是SM2椭圆曲线的阶,一个256位的素数。

生成私钥的核心要求就两条:真随机、不越界。

关键规范(GM/T 0003-2012)

  • 私钥 d 必须通过国家密码管理局批准的随机数发生器生成
  • 取值范围:1 ≤ d ≤ n-2(注意不是 n-1,留了一个安全余量)
  • 禁止使用固定值、弱随机数、可预测种子

我在项目中见过有人直接用 rand() 生成私钥——嗯,那玩意儿周期太短,暴力破解分分钟的事。国密要求必须用硬件随机数或经认证的DRBG(确定性随机数发生器)。

我的习惯

我一般用OpenSSL的 RAND_bytes() 配合熵池,或者直接调用硬件加密卡的TRNG接口。生成后做个范围校验,防止边界溢出。

3.2 公钥推导计算

公钥 P 的计算公式很简单:

P = d * G

其中 d 是私钥,G 是SM2椭圆曲线的基点。这是个标量乘法,说白了就是私钥个基点相加。

你想想看,从私钥推公钥很容易,但从公钥反推私钥——那就是椭圆曲线离散对数难题,目前无解。这也是SM2安全性的根基。

公钥是一个点,包含两个坐标 (x, y),每个坐标256位。存储时有两种格式:

格式 描述 长度
未压缩格式 04 || x || y,完整坐标 65字节
压缩格式 02/03 || x,通过x恢复y 33字节

我个人习惯用未压缩格式。虽然多32字节,但省去了恢复坐标的计算,兼容性也更好。压缩格式在带宽受限的场景下有用,比如物联网。

我曾经踩过的坑

有一次做互操作测试,对方给的公钥是压缩格式,我这边解析时忘了判断第一个字节是02还是03,结果恢复出来的y坐标符号反了。SM2的y坐标恢复需要根据奇偶性决定正负,千万别搞错。

3.3 密钥存储格式:PEM vs DER

密钥不能裸存,得有个标准包装。目前主流就两种:DER和PEM。

3.4.1 DER格式

DER是二进制格式,用ASN.1编码。SM2私钥的DER结构长这样:

ECPrivateKey ::= SEQUENCE {
  version        INTEGER { ecPrivkeyVer1(1) },
  privateKey     OCTET STRING,
  parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
  publicKey  [1] BIT STRING OPTIONAL
}

说白了就是:版本号 + 私钥字节串 + 曲线参数 + 公钥(可选)。

我建议公钥字段一定要带上。虽然可以从私钥推导,但带上后解析时少一次计算,也方便做公私钥配对校验。

3.4.2 PEM格式

PEM就是在DER外面套了一层Base64编码,再加头尾标识。看起来像这样:

-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBG0wawIBAQQg...
-----END PRIVATE KEY-----

PEM的好处是文本可读,方便复制粘贴。DER更紧凑,适合嵌入式或硬件存储。

特性 DER PEM
编码方式 二进制 Base64文本
可读性 差 好
文件大小 小 大(约多33%)
常见用途 硬件、嵌入式 文件交换、配置

我的建议

开发调试阶段用PEM,方便肉眼检查。生产环境如果存文件,用DER更省空间。如果存数据库或HSM,直接存二进制DER字节流。

3.5 完整流程:从随机数到PEM文件

咱们把整个流程串起来,看一张图就清楚了。

SM2密钥对生成与存储流程 SM2密钥对生成与存储流程 随机数发生器(TRNG/DRBG) 私钥 d ∈ [1, n-2] 公钥 P = d * G ASN.1 DER 编码 DER 二进制文件 Base64 → PEM 文件 注意:私钥必须安全存储,建议加密保护或使用HSM硬件安全模块 公钥可公开分发,但需校验其是否在曲线上(点校验)

流程其实就五步:

  1. 熵源:从TRNG或DRBG获取高质量随机数
  2. 私钥:生成 d,校验范围
  3. 公钥:计算 P = d * G,选择压缩或未压缩格式
  4. 编码:用ASN.1 DER编码密钥对
  5. 存储:直接存DER,或转PEM存文本文件

重要提醒

私钥文件权限一定要设成600(仅属主可读写)。我曾经见过一个测试环境,私钥权限是644,结果被同事的脚本误读上传到了日志服务器——虽然没出事,但吓得我连夜改了所有密钥。

3.6 实操:用OpenSSL生成SM2密钥对

理论讲完了,咱们动手试试。OpenSSL 1.1.1+ 已经支持SM2,命令很简单:

# 生成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

执行完你会看到类似这样的输出:

read EC key
Private-Key: (256 bit)
priv:
    00:9f:7c:8b:...(私钥十六进制)
pub:
    04:1a:2b:3c:...(公钥未压缩格式)
ASN1 OID: SM2

注意看OID字段,必须是 SM2 而不是 prime256v1。这两个曲线参数不同,混用会导致签名验证失败。我遇到过好几次这种问题——对方给了个P-256的密钥,我当SM2用,结果死活验不过。

快速验证

生成后可以用 openssl ec -in sm2_private.pem -check 校验密钥一致性。它会自动验证私钥和公钥是否匹配。

好了,这一章就到这里。密钥对生成是SM2应用的起点,规范、推导、存储这三块搞清楚了,后面证书签发和链式验证就顺了。