3. SM2 密钥对生成:椭圆曲线参数、公私钥生成原理、使用 OpenSSL 生成 SM2 密钥对
聊到 SM2,绕不开的就是密钥对生成。很多刚接触国密的朋友,上来就敲命令,结果生成的密钥格式不对,或者曲线参数没配好,后面签名验签全乱套。我刚开始搞 SM2 的时候也踩过这个坑——拿着 OpenSSL 1.1.1 直接生成,发现私钥文件里居然没有曲线 OID,导致 Java 那边死活不认。嗯,咱们今天就把这块彻底捋清楚。
3.1 椭圆曲线参数:SM2 的“基因”
SM2 用的是基于椭圆曲线密码学(ECC)的算法。说白了,它依赖一条特定的椭圆曲线。这条曲线不是随便选的,是国家密码管理局定义的。参数是固定的,你不需要自己生成,但必须知道它们长什么样。
SM2 推荐使用的曲线是 256 位素域上的椭圆曲线,参数如下:
| 参数 | 含义 | 值(十六进制) |
|---|---|---|
| p | 素域模数 | FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFF |
| a | 曲线方程系数 a | FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFC |
| b | 曲线方程系数 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 |
b 和 n,这两个值最容易在复制粘贴时出错。一旦参数错了,生成的密钥对就是废的。
3.2 公私钥生成原理:从点到密钥
SM2 密钥对生成的数学原理,其实不复杂。你想想看,椭圆曲线上有一个公开的基点 G。私钥就是一个随机的大整数 d,公钥就是 d 乘以 G 得到的另一个点 Q。
具体流程是这样的:
- 生成随机数 d:在 [1, n-2] 范围内,通过密码学安全的随机数生成器产生一个整数。这就是你的私钥。
- 计算公钥 Q:执行椭圆曲线标量乘法 Q = d * G。G 是已知的基点,d 是私钥,算出来的 Q 是一个坐标点 (x, y)。
- 输出密钥对:私钥 d 自己保管好,公钥 Q 可以公开。
这里有个关键点——从公钥反推私钥,就是椭圆曲线离散对数问题。目前没有多项式时间的解法。所以只要私钥 d 足够随机,你的密钥就是安全的。
核心结论: SM2 的安全性,建立在“已知 Q 和 G,求 d 极其困难”这个数学假设上。私钥的随机性质量,直接决定了整个系统的安全底线。
我曾经在一个项目中,发现某厂商的硬件加密机生成的 SM2 私钥居然有规律可循——连续几次生成的私钥,高位字节几乎一样。后来一查,是随机数种子没刷新。嗯,这个坑大家一定要避开。
3.3 使用 OpenSSL 生成 SM2 密钥对
OpenSSL 从 1.1.1 版本开始原生支持 SM2。我个人建议用 1.1.1 以上版本,最好是 3.x,因为对国密的支持更完善。下面我带你走一遍完整的生成流程。
3.3.1 检查 OpenSSL 版本
先确认你的 OpenSSL 支持 SM2:
openssl version
# 输出示例:OpenSSL 3.0.12 24 Oct 2023
openssl ecparam -list_curves | grep SM2
# 如果支持,会输出:SM2 : SM2 curve over a 256 bit prime field
3.3.2 生成 SM2 私钥
有两种方式。第一种,先生成参数文件,再生成私钥:
# 生成 SM2 曲线参数文件
openssl ecparam -name SM2 -genkey -out sm2_private.pem
# 查看私钥内容
openssl ec -in sm2_private.pem -text -noout
第二种,一步到位:
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:SM2 -out sm2_private.pem
我个人更推荐第二种。为什么?因为 genpkey 是新一代的密钥生成工具,格式更规范,跟其他密码库的兼容性更好。
3.3.3 从私钥导出公钥
openssl ec -in sm2_private.pem -pubout -out sm2_public.pem
# 查看公钥内容
openssl ec -in sm2_public.pem -pubin -text -noout
输出结果类似这样:
read EC key
Private-Key: (256 bit)
priv:
00:9f:7c:8e:... (私钥的十六进制值)
pub:
04:8b:21:4f:... (公钥的 x 坐标)
:c3:9a:7e:... (公钥的 y 坐标)
ASN1 OID: SM2
04 是未压缩格式的标志字节。SM2 标准要求公钥必须包含完整的 x 和 y 坐标,所以一定是 04 开头。如果你看到 02 或 03,那是压缩格式,SM2 不推荐使用。
3.3.4 验证密钥对
生成完了,一定要验证一下:
# 验证私钥文件是否完整
openssl ec -in sm2_private.pem -check -noout
# 验证公钥是否与私钥匹配(通过生成一个临时签名来验证)
openssl pkeyutl -sign -inkey sm2_private.pem -in /dev/null -out /tmp/sig.bin
openssl pkeyutl -verify -pubin -inkey sm2_public.pem -in /dev/null -sigfile /tmp/sig.bin
如果验证通过,说明你的密钥对是有效的。
3.4 密钥格式与存储建议
生成的 PEM 文件,里面是 DER 编码的密钥结构。私钥文件包含曲线 OID、私钥值 d;公钥文件包含曲线 OID 和公钥点 Q。
关于存储,我有几条实战经验:
- 私钥必须加密存储:生产环境不要用裸的 PEM 文件。用
openssl ec -aes-256-cbc加密,或者直接上硬件安全模块(HSM)。 - 公钥可以明文分发:但一定要校验完整性。我习惯在分发公钥时附带一个 SHA256 哈希值。
- 备份私钥:我曾经见过客户把私钥存在一台虚拟机上,结果虚拟机快照丢了,所有证书都得重新签发。惨痛教训。
3.5 本章知识体系
下面这张图,把 SM2 密钥对生成的核心脉络串起来了:
这张图把整个流程分成了三层:输入层(曲线参数 + 随机数)、计算层(私钥生成 + 标量乘法)、输出层(公钥 + 格式封装)。你照着这个脉络去理解,就不会乱。
openssl ec -in xxx.pem -text -noout | grep OID 确认一下曲线 OID 是不是 SM2。
好了,SM2 密钥对生成这块,从数学原理到命令行实操,咱们都过了一遍。你只要记住:参数是固定的,私钥要随机,公钥靠计算,格式要规范。做到这四点,密钥对生成就不会出问题。