SM2密钥对生成:私钥随机生成、公钥推导、密钥格式(DER/PEM)
搞密码学的人都知道,密钥对是整个系统的根基。私钥一旦泄露,什么加密签名都是白搭。公钥推导错了,对方根本验不了你的签名。我记得刚入行那会儿,自己手写了一个SM2密钥生成,结果公钥算出来总是不对……查了半天才发现是曲线参数搞混了。这章咱们就踏踏实实把密钥对生成的每一环掰开揉碎,包括私钥怎么随机出来,公钥怎么算,最后怎么存成标准格式。
私钥的随机生成
私钥本质上是一个256位的随机整数,范围在1到n-1之间(n是SM2曲线的阶)。说白了,就是从天文数字里抽一个数,不能是0,不能是n。这个随机数必须由合格的随机数发生器产生——不是什么Math.random(),而是国家密码管理局认可的硬件熵源或经过认证的DRBG。
我个人习惯用RNG(Random Number Generator)直接读取/dev/random,或者在安全芯片里调用硬件TRNG。为什么呢?因为我在项目里遇到过两次:一次是测试环境用了伪随机种子,导致多个设备生成了同样的私钥,差点酿成安全事故;另一次是某款低端芯片的TRNG输出不稳定,抽出来的数居然偏小,反复出现n-2这种边角值。
rand()或SecureRandom.getInstance("SHA1PRNG")这种未经国密认证的随机数生成器。一定要用GM/T 0023或FIPS 140-2级别以上的RNG。
私钥的生成步骤其实很简单:
- 生成256位随机字节串
- 将其转为大整数,检查是否在[1, n-1]区间
- 如果越界或为0,重试
- 输出私钥
d
你想想看,这个过程的计算量极小,关键就在于随机源的质量。我建议你在产品中始终做一次私钥熵源健康测试——比如连续生成1000个私钥,检查直方图分布和重复率。
公钥的推导计算
公钥P通过椭圆曲线上的标量乘法得到:P = d * G。G是SM2的基点,坐标固定。说白了就是把私钥这个数,当成“走几步”,从G开始跳跳跳,最后落在曲线上某个点。
这个计算有坑。最大的坑就是验证点是否在曲线上。计算完成后必须检查y² ≡ x³ + ax + b (mod p)。我曾在项目里遇到过第三方库实现bug,返回的点坐标根本不在SM2曲线上,但接口没报错,导致后续加密全部失效。避坑:不要信任任何外部库的“自动验证”,手工验一次,也就几十行代码。
密钥格式与编码
有了私钥和公钥,怎么存?怎么传?标准答案:DER或PEM。别搞什么自定义字节流,后果是跨平台互换时你哭都来不及。
DER是二进制编码,基于ASN.1结构。SM2私钥的ASN.1定义大致如下:
SEQUENCE {
INTEGER 0x01, -- 版本
OCTET STRING (32字节) -- 私钥d
[0] EXPLICIT {
OID 1.2.156.10197.1.301 -- SM2椭圆曲线公钥密码算法
}
[1] EXPLICIT {
BIT STRING (65字节) -- 未压缩公钥04 || x || y
}
}
PEM其实就是DER的Base64编码,加上头尾标识行。私钥头是-----BEGIN EC PRIVATE KEY-----,公钥头是-----BEGIN PUBLIC KEY-----。注意SM2公钥的OID是1.2.156.10197.1.301,别和ECDSA的OID搞混。
| 特性 | DER | PEM |
|---|---|---|
| 存储形式 | 二进制 | Base64文本 |
| 可读性 | 差 | 好,可贴入邮件 |
| 大小 | 约121字节(私钥) | 约168字节(私钥) |
| 标准支持 | 所有语言原生 | 所有语言原生 |
| 传输场景 | 硬件安全模块 | 文件、网络 |
公钥的DER结构略有不同:
SEQUENCE {
SEQUENCE {
OID 1.2.156.10197.1.301
OID 1.2.840.10045.2.1 -- 公钥算法标识
}
BIT STRING (65字节) -- 04 || x || y (未压缩)
}
这里有个小陷阱:公钥的格式里包含了算法标识的SEQUENCE嵌套,很多新手只传BIT STRING,结果对方解码报错。记住:公钥是一个完整的SubjectPublicKeyInfo结构。
密钥校验与验证
生成完之后不能直接拿去用,至少要跑一遍自校验。我习惯做三件事:
- 检查私钥范围:d ∈ [1, n-1],不是0也不是n。
- 验证公钥在曲线上:代入曲线方程检查。
- 签名测试:用一对密钥签名一个随机消息,再用公钥验签。通过才确认密钥对可用。
为什么一定要做签名测试?因为我在一个国产加密卡对接项目里遇到过,密钥对生成成功了,但实际签名时曲线运算器有硬件bug,算出的公钥坐标是镜像点,签名验不过。从那以后我不论多信任硬件,都加这一步。
最后,我给你一个生产环境下的最佳实践清单:
- 私钥生成后立即用DER编码写入安全存储区,同时用PEM备份到HSM外接口(若产品合规允许)。
- 公钥导出时默认用未压缩格式(65字节04开头),避免第三方不支持压缩格式。
- 始终保留一份密钥指纹(SM3 hash)用于快速校验,我在证书签发系统里就是用这个发现重复入库的。