2. SM2密钥生成:密钥对生成原理、随机数质量与存储格式

密钥生成,是SM2算法落地的第一道关卡。很多人觉得这步简单——不就是算个点吗?
嗯,我早年也这么想,直到在项目里吃过亏,才明白这里面的门道有多深。

今天咱们就掰开揉碎,把密钥生成的原理、随机数的坑、还有存储格式的讲究,一次性说清楚。

2.1 密钥对生成原理

SM2的密钥对,本质上是一对椭圆曲线上的点与标量的关系。

  • 私钥:一个随机的大整数 \( d \),取值范围在 \([1, n-2]\),其中 \( n \) 是SM2曲线的阶。
  • 公钥:\( P = d \cdot G \),其中 \( G \) 是SM2曲线的基点。

说白了,私钥就是一个随机数,公钥就是拿这个随机数去乘基点。从公钥反推私钥,就是椭圆曲线离散对数问题——目前无有效解法。

核心公式:\( P = d \cdot G \)

私钥长度:256位(32字节)
公钥长度:未压缩时65字节(1字节标识 + 32字节x坐标 + 32字节y坐标)

生成流程其实很直接:

  1. 生成一个合格的随机数 \( d \)(256位)
  2. 检查 \( d \) 是否在合法范围内(1 ≤ d ≤ n-2)
  3. 计算 \( P = d \cdot G \)(椭圆曲线标量乘法)
  4. 输出私钥 \( d \) 和公钥 \( P \)

我在项目中遇到过一个问题:有人直接用系统自带的 rand() 生成私钥,结果每次重启设备,密钥都一样。你想想看,这跟没锁门有什么区别?

2.2 随机数质量对安全性的影响

随机数,是密钥生成的命门。私钥的随机性直接决定了整个系统的安全性。

为什么这么说?

  • 随机数可预测:攻击者如果能预测私钥,就能计算出对应的公钥,整个加密体系形同虚设。
  • 随机数重复:如果两个不同的设备生成了相同的私钥,那么它们的公钥也相同。这在区块链场景下,意味着资产可以被任意一方控制。
  • 随机数熵不足:嵌入式设备上,如果随机数生成器没有足够的熵源,生成的私钥可能集中在某个小范围内,攻击者可以暴力枚举。

⚠️ 我曾经踩过的坑:在某款IoT设备上,我们用了芯片内置的硬件随机数生成器。结果发现,设备刚上电时,熵池还没初始化,前100次调用返回的都是同一个值。嗯,从那以后,我坚持在密钥生成前,先做一次熵源健康检查。

那么,什么样的随机数才算合格?

指标 要求 说明
熵源 ≥ 256位 至少需要256位不可预测的熵
均匀性 通过NIST SP 800-22测试 不能有明显的偏斜或模式
独立性 前后不相关 不能通过前一个值预测后一个值
抗攻击 抵抗侧信道攻击 不能通过功耗、时序泄露信息

在嵌入式环境里,我建议的做法是:

  • 优先使用硬件TRNG(真随机数发生器)
  • 如果硬件TRNG不可靠,用软件方式混合多个熵源(ADC噪声、时钟抖动、传感器数据)
  • 生成后做一次简单的自检(比如检查是否全0或全1)

2.3 密钥存储格式

密钥生成完了,怎么存?这问题看似简单,但格式选不对,后面互操作性全是坑。

SM2密钥存储,常见的有三种格式:

格式 私钥长度 公钥长度 特点
原始二进制 32字节 65字节(未压缩) 最紧凑,但无结构信息
PEM/DER 可变(含ASN.1头) 可变(含ASN.1头) 标准互操作,但开销大
自定义结构体 可变 可变 灵活,可附加元数据

我个人习惯,在嵌入式系统里用原始二进制格式,因为省空间、解析快。但要注意:

  • 私钥必须加密存储(比如用AES-GCM加密后存Flash)
  • 公钥可以明文存,但最好带CRC校验
  • 如果涉及跨平台,建议用PEM格式,兼容性好

💡 一个小技巧:存储公钥时,可以用压缩格式(33字节,只存x坐标和y的奇偶性)。这样能省一半空间,但计算时需要恢复y坐标,多一次模平方运算。在资源紧张的MCU上,这个取舍值得权衡。

下面是一个典型的密钥存储结构体示例:

typedef struct {
    uint8_t version;        // 版本号,用于兼容
    uint8_t algorithm;      // 算法标识(0x01 = SM2)
    uint8_t key_type;       // 0x01 = 私钥, 0x02 = 公钥
    uint8_t format;         // 0x00 = 原始, 0x01 = 压缩
    uint8_t key_data[64];   // 私钥32字节 / 公钥x+y
    uint32_t crc32;         // 完整性校验
} sm2_key_store_t;

嗯,这里要注意:CRC32只能防随机错误,不能防篡改。如果担心被恶意修改,建议用HMAC代替。

2.4 密钥生成流程图

我把整个密钥生成的流程画成了SVG图,方便你对照理解:

SM2密钥生成流程图 SM2密钥生成流程图 熵源采集 生成256位随机数 d 检查 d ∈ [1, n-2] 不合格,重新生成 计算 P = d · G 输出私钥 d + 公钥 P ⚠️ 注意 随机数质量决定安全性 建议使用硬件TRNG + 软件混合 生成后做自检

2.5 避坑指南

最后,分享几个我亲身踩过的坑:

  • 别用伪随机数生成器(PRNG)直接生成私钥——尤其是没做种子更新的那种。我曾经见过一个设备,每次上电都用同一个种子,结果所有设备的私钥都一样。
  • 注意私钥的零值检查——SM2标准里,私钥不能为0,也不能为n-1。虽然概率极低,但检查一下不费事。
  • 公钥的合法性验证——生成公钥后,建议验证它是否在曲线上。虽然理论上 \( d \cdot G \) 一定在曲线上,但硬件故障或内存错误可能导致异常。
  • 密钥存储的访问控制——私钥存储区,一定要做读写权限隔离。我在一个项目里见过,私钥存在全局可读的Flash区域,结果被恶意固件直接读走了。

总结一句话:密钥生成不是简单的“随机数 + 点乘”,它涉及随机数质量、格式选择、存储安全等多个维度。任何一个环节出问题,整个密码体系就形同虚设。