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梳理的知识体系

SM2密钥对生成与格式体系 SM2密钥对生成与格式体系 SM2密钥对 私钥(256位随机数) 公钥(椭圆曲线点) 直接生成 压缩格式(33B) 种子派生 非压缩格式(65B) 原始字节格式 DER编码格式 PEM文本格式 注意:不同格式间可转换,但必须确保私钥随机数合格、公钥在椭圆曲线上

常见问题与避坑指南

我在这方面踩过的坑,整理一下供你参考:

  • 随机数质量:必须用操作系统的安全随机数接口(如Linux的/dev/urandom),不要用rand()
  • 公钥验证:收到别人的公钥,必须先验证它在SM2曲线上——不验证等于白给攻击者机会
  • 格式混用:不同系统间传密钥,最好统一成PEM格式,附带指明是SM2算法
  • 私钥安全:私钥在内存中用完要立即清空,用SecureZeroMemory之类的函数

我曾经在项目中遇到过一个很隐蔽的bug:两个库的私钥格式标准不同,一个把私钥当大端整数,另一个当小端整数。结果两边算出来的公钥完全不同。从那以后,我每次做密钥对相关代码,都先检查字节序、长度、OID,再跑测试向量。

小结

SM2密钥对生成的核心就三点:真随机数、验公钥、格式统一。搞清楚了这些,后面签名、加密等操作才能走得顺。

我自己总结了一个检查清单:生成完密钥对,马上做三件事——检查私钥字节数是不是32、用公钥做一次椭圆曲线点乘验证、用标准测试向量跑一遍签名验签。这步走完了,我才放心把密钥对用在线上。