SM2密钥对生成与格式归纳总结
说实话,密钥对生成这件事,很多开发者觉得就是调个函数完事。但我在项目中吃过亏,才明白这里头门道不少。今天咱们就把它彻底理清楚。
密钥对生成的两种方式
SM2密钥对,说白了就是一对儿钥匙:私钥自己藏着,公钥给别人。我通常把它分成两个场景:
- 直接生成:底层用国密硬件或真随机数,一出厂就是一对
- 从种子派生:用相同种子+派生算法,可以在不同设备上算出同一对密钥
你想想看,要是做密钥备份,第二种方法就特别有用。我之前有个项目,需要给几百台设备灌入密钥对,就是靠种子派生搞定的——省去了挨个烧录的麻烦。
私钥到底长什么样
SM2私钥就是一个256位的随机数。嗯,这里要注意:必须是真正的随机数。
我在项目中遇到过用伪随机数生成密钥对的情况——开发环境没问题,但安全评审直接被pass了。所以记住:必须用真实的熵源。
私钥格式上,常见的三种:
| 格式 | 说明 | 长度 |
|---|---|---|
| 原始字节 | 32字节大端序整数 | 32字节 |
| DER编码 | ASN.1结构,带OID标识 | 通常121字节 |
| PEM格式 | DER的Base64封装+首尾行 | 约180字符 |
我个人的习惯是:内部存储用原始字节,对外接口用PEM。因为原始字节简单直接,PEM在各种工具间传输方便。
公钥的结构
公钥本质上是椭圆曲线上的一个点,包含x和y两个坐标。但有个小技巧:
- 非压缩格式:04开头 + x坐标(32字节) + y坐标(32字节),共65字节
- 压缩格式:02或03开头 + x坐标(32字节),共33字节
为什么要压缩?公钥存储和传输时,压缩能省一半空间。但压缩后需要计算y值,会多一步运算。我记得有个项目对接口带宽要求苛刻,就是用压缩格式传公钥的。
关键点:SM2公钥的压缩和非压缩是可以互相转换的。而且,必须验证公钥在椭圆曲线上——我曾经踩过这个坑,用了未经验证的公钥,结果签名验不过去。
格式转换实战
我经常要在这几种格式间来回倒。下面是个常见的转换流程:
// SM2私钥原始字节 -> PEM格式
原始私钥(32字节) -> DER编码(加入ASN.1结构) -> Base64编码 -> 加上 "-----BEGIN PRIVATE KEY-----" 头尾
// SM2公钥原始字节 -> PEM格式
原始公钥(65字节, 04开头) -> DER编码(加入算法标识) -> Base64编码 -> 加上 "-----BEGIN PUBLIC KEY-----" 头尾
看着简单吧?但坑就在这些“加入”、“加上”的细节里——SM2的OID(对象标识符)是1.2.156.10197.1.301,千万别写错了。
我的习惯:写代码时永远先确认三个常数——私钥长度32字节、公钥非压缩65字节、SM2的OID字符串。这三个对不上,后面全白干。
必须用SVG梳理的知识体系
常见问题与避坑指南
我在这方面踩过的坑,整理一下供你参考:
- 随机数质量:必须用操作系统的安全随机数接口(如Linux的/dev/urandom),不要用rand()
- 公钥验证:收到别人的公钥,必须先验证它在SM2曲线上——不验证等于白给攻击者机会
- 格式混用:不同系统间传密钥,最好统一成PEM格式,附带指明是SM2算法
- 私钥安全:私钥在内存中用完要立即清空,用SecureZeroMemory之类的函数
我曾经在项目中遇到过一个很隐蔽的bug:两个库的私钥格式标准不同,一个把私钥当大端整数,另一个当小端整数。结果两边算出来的公钥完全不同。从那以后,我每次做密钥对相关代码,都先检查字节序、长度、OID,再跑测试向量。
小结
SM2密钥对生成的核心就三点:真随机数、验公钥、格式统一。搞清楚了这些,后面签名、加密等操作才能走得顺。
我自己总结了一个检查清单:生成完密钥对,马上做三件事——检查私钥字节数是不是32、用公钥做一次椭圆曲线点乘验证、用标准测试向量跑一遍签名验签。这步走完了,我才放心把密钥对用在线上。