密钥生成审计:私钥随机性检测、公钥点合法性验证、密钥对存储安全

密钥生成,是SM2整个安全体系的起点。起点歪了,后面全白搭。我见过不少项目,算法实现没问题,但密钥生成环节出了纰漏,结果整个系统形同虚设。今天咱们就掰开揉碎,把密钥生成审计的三个核心点讲透。

私钥随机性检测:别让“随机”变成“伪随机”

SM2的私钥是一个256位的随机数。听起来简单,对吧?但“随机”这两个字,恰恰是最容易出问题的地方。

我个人习惯,审计私钥随机性时,先问三个问题:

  • 随机数源是什么? 是硬件真随机数发生器,还是软件伪随机数生成器?
  • 种子熵够不够? 有没有混入足够多的不可预测信息?
  • 有没有后处理? 原始随机比特是否经过密码学安全的去偏和提取?

我在项目中遇到过一家厂商,他们用C语言的rand()函数生成私钥。你想想看,rand()的周期才2^31,对于256位的私钥来说,这跟没锁门有什么区别?

⚠️ 审计红线: 私钥必须使用密码学安全的随机数生成器(CSPRNG)。在国密合规场景下,优先使用硬件真随机数发生器,或至少使用操作系统提供的/dev/urandom(Linux)、CryptGenRandom(Windows)等接口。

具体检测方法,我一般做这几步:

  1. 源码审计:检查随机数生成器的调用链,确认没有使用rand()、random()、Math.random()等非密码学安全函数。
  2. 熵源检查:确认种子是否包含系统时间、进程ID、鼠标移动、网络延迟等多源信息。
  3. 统计测试:对生成的私钥样本做NIST SP 800-22随机性测试,至少跑1000个样本。
  4. 重复性检测:检查私钥库中是否存在重复值。我曾经审计过一个系统,10万把私钥里居然有3把完全一样——随机数发生器初始化时种子没变。
💡 小技巧: 审计时可以用ent工具对私钥文件做熵值估算。正常256位私钥的熵值应该在7.9-8.0 bits/byte之间。低于7.5就要高度警惕。

公钥点合法性验证:别让无效点混进来

私钥生成后,SM2通过标量乘法计算出公钥:P = d * G。但公钥不是算出来就完事了——它必须是一个合法的椭圆曲线点。

为什么?因为如果公钥不在曲线上,或者是一个无穷远点,后续的加密、签名验证都会出问题。更严重的是,攻击者可能利用无效点发起攻击。

公钥点合法性验证,我总结为“四步检查法”:

检查项 验证内容 审计方法
非无穷远点 公钥不能是无穷远点O 检查坐标是否为(0,0)或特殊标记
坐标范围 x, y 必须在[1, p-1]范围内 比较坐标值与素数p的大小
曲线方程 y² ≡ x³ + ax + b (mod p) 代入SM2曲线参数验证等式
阶的验证 n * P = O(n为基点阶) 验证公钥的阶是否整除n

嗯,这里要注意:很多实现只做了前两步,忽略了“阶的验证”。但SM2标准(GM/T 0003-2012)明确要求验证n * P = O。我曾在一次审计中发现,某个实现跳过了这一步,结果攻击者可以用小阶点攻击来降低安全性。

审计要点: 公钥验证必须在公钥导入、公钥计算、公钥存储三个环节都执行。不能只在生成时验证一次,后面就不管了。

代码层面,我建议这样实现公钥验证:

// SM2公钥点合法性验证(伪代码)
bool validatePublicKey(ECPoint P) {
    // 1. 检查无穷远点
    if (P.isInfinity()) return false;
    
    // 2. 检查坐标范围
    if (P.x <= 0 || P.x >= p) return false;
    if (P.y <= 0 || P.y >= p) return false;
    
    // 3. 检查曲线方程
    BigInt left = (P.y * P.y) % p;
    BigInt right = (P.x * P.x * P.x + a * P.x + b) % p;
    if (left != right) return false;
    
    // 4. 检查阶(关键步骤)
    ECPoint result = scalarMultiply(n, P);
    if (!result.isInfinity()) return false;
    
    return true;
}

密钥对存储安全:存不好,等于白干

密钥生成完了,验证通过了,然后呢?存起来。但怎么存,存哪里,谁有权访问——这些才是真正的安全分水岭。

我见过最离谱的案例:某系统把SM2私钥以明文形式存在数据库的private_key字段里,连个加密都没有。审计时我直接查到了所有用户的私钥。你想想看,这跟把家门钥匙挂在门外有什么区别?

密钥对存储安全,我建议从三个维度审计:

  • 存储形态:私钥必须加密存储,推荐使用硬件安全模块(HSM)或可信执行环境(TEE)。软件方案至少要用SM4对私钥进行加密保护。
  • 访问控制:谁可以读取私钥?谁可以导出私钥?必须有严格的权限分级和审计日志。
  • 生命周期:密钥生成、分发、使用、备份、销毁,每个阶段都要有记录。
⚠️ 常见坑: 很多系统把私钥和公钥放在同一个文件里,或者把私钥的加密密钥硬编码在代码中。这两种做法在审计中直接判为不合格。

我个人习惯,审计密钥存储时,会画一张数据流图,追踪私钥从生成到销毁的完整路径。每个环节都要问:私钥在这里是明文还是密文?谁有权限接触?有没有日志记录?

下面这张图,是我做审计时常用的密钥生成与存储安全框架:

SM2密钥生成与存储安全审计框架 随机数源 硬件TRNG / CSPRNG 私钥生成 256位随机数 d 公钥计算 P = d * G 私钥随机性检测 熵源检查 · 统计测试 重复性检测 公钥点合法性验证 非无穷远 · 坐标范围 曲线方程 · 阶的验证 密钥对存储安全 加密存储 访问控制 · 生命周期 加密存储 SM4加密 / HSM / TEE 访问控制 权限分级 · 审计日志 生命周期管理 生成 · 使用 · 备份 · 销毁 审计结论:三项全通过 → 密钥生成安全 任何一项不通过 → 判定为高风险,需整改

说白了,密钥生成审计就是三个字:真、准、牢。私钥要真随机,公钥要准合法,存储要牢不可破。任何一个环节出问题,整个密码体系就形同虚设。

我曾经审计过一个金融系统,他们SM2密钥生成全部合规,但私钥存储时用了弱密码加密。结果内部员工用暴力破解拿到了管理员私钥,伪造了交易签名。你看,前面99步都走对了,最后一步没走好,全盘皆输。

审计清单速查:

  • ✅ 私钥随机数源是否为CSPRNG或硬件TRNG?
  • ✅ 私钥是否通过NIST随机性测试?
  • ✅ 公钥是否完成四步合法性验证(含阶的验证)?
  • ✅ 私钥是否加密存储?加密密钥是否独立管理?
  • ✅ 密钥访问是否有权限控制和审计日志?
  • ✅ 密钥生命周期是否有完整记录?

记住,密钥生成审计不是走过场。它是整个密码安全的第一道防线。这道防线守住了,后面的加密、签名、密钥交换才有意义。