密钥生成策略:随机数质量、密钥生成算法、生成环境安全
密钥生成是整个SM2生命周期里最容易被“轻视”的环节。很多人在审计时只看证书链对不对、签名验签通不通,却忽略了这把锁是怎么打出来的。说白了,私钥就是个大随机数,公钥是它乘上基点算出来的。你要是随机数不靠谱、算法走了偏门、生成环境被盯上了——后边再多合规审计都是白搭。
我接过不少密钥泄露的案子,刨根问底,有超过一半的根源能追溯到生成阶段。所以这一章我把密钥生成拆成三个核心维度来聊:随机数质量、生成算法、环境安全。你不是在审计的时候一条一条过就行,更要理解每个坑在哪儿。
随机数质量:密钥安全的基石
SM2私钥d是从[1, n-2]这个区间里均匀取出来的。d本身的熵多大,密钥就有多强。我记得有个项目用C语言标准库的rand()生成私钥,结果线上出了两把一模一样的私钥——因为没播种,启动时默认种子是1。你想想看,两个用户的私钥竟然相同,这审计报告写出来谁信?
随机数质量必须从三个角度去卡:
- 熵源——底层噪声是否足够?CPU指令集(如RDRAND)、专用硬件随机数芯片、还是纯软件算法模拟?
- 均匀性——生成大量样本后做统计检验,不能有偏。NIST SP 800-22的15项测试至少跑一遍。
- 不可预测性——攻击者即便知道前1000个随机数,也不能推断出第1001个是什么。
| 类型 | 典型实现 | 熵质量 | 性能 | 审计时看什么 |
|---|---|---|---|---|
| 硬件随机数生成器(HRNG) | HSM内置芯片、Intel RDRAND | 近真随机,符合Entropy Source要求 | 较慢,适合批量生成 | 是否通过AIS-31 / FIPS 140-2认证? |
| 内核熵池(/dev/random) | Linux系统调用 | 依赖系统中断收集,充足时很高 | 可能阻塞 | 熵估计值、是否降级到伪随机? |
| 伪随机数生成器(DRBG) | NIST SP 800-90A的HMAC_DRBG / CTR_DRBG | 强种子下可达到密码学安全 | 快 | 种子来源、状态保护、是否每次重新播种? |
| (危险)软件内置rand() | C/srand/rand, Java.util.Random | 极低,可预测 | 极快 | 直接判违规,拒绝 |
个人习惯:我偏向用硬件随机数生成器+DRBG组合。HSM提供种子,HMAC_DRBG做扩展,既保证熵质量又兼顾吞吐。你可以在审计清单里加一条:“检查随机数生成器是否使用了独立熵源 + 经过验证的DRBG算法。”
密钥生成算法:从随机数到密钥对
随机数拿到手,下一步就是生成密钥对。SM2的流程很简洁:d ∈ [1, n-2],然后P = d * G。但就这短短两步,我在项目中遇到过好几种坑。
举个例子,有一次审计某开源SM2库,发现它生成私钥之后没有检查d是否等于0或1。你想想看,如果d=1,那么公钥就是G,等于全世界都知道你的公钥就是标准基点,攻击者直接用G就能伪造签名。标准里写得明明白白d ∈ [1, n-2],可就是有人漏了上限检查。
另一个容易踩的坑是椭圆曲线参数的合法性验证。SM2用的是p=2^256 - 2^224 - 2^96 + 2^64 - 1 的素域,基点和阶n都是固定的。但有些实现允许用户自定义曲线参数,这在国密标准里是不允许的。审计时一定要确认代码里硬编码的是GM/T 0003-2012规定的标准参数,不能从外部加载。
// 标准SM2密钥生成核心伪代码(仅示意)
function generateSM2KeyPair():
d = randomInteger(1, n - 2) // 必须包含上下界检查
if d == 0 or d == 1: // 安全冗余检查
d = randomInteger(1, n - 2)
P = d * G // 使用标准基点G
if not P.isOnCurve(): // 公钥必须在曲线上
throw "公钥坐标不在SM2曲线上"
return (d, P)
我曾经遇到过一个设备厂商,密钥生成时用了Hessian算法但不是SM2标准曲线,而是把坐标硬编码成一个自定义椭圆曲线。结果密钥对和标准SM2完全不兼容,验签时总是失败。审计时一定要抓到这层:不仅算法流程要对,曲线参数也必须与国标一致。
生成环境安全:没有人旁观你的私钥
随机数质量再好,算法实现再标准,如果生成环境被人“监听”了,那密钥就等于白给。我记得以前做过一次金融系统的安全审计,密钥生成直接在应用服务器的Java堆里做,用BigInteger对象存私钥。虚拟机的堆是共享的,如果内存被dump,私钥瞬间泄露。那之后我就养成了一个习惯:把密钥生成和业务逻辑完全隔离。
环境安全我总结为四点:
- 物理隔离——最好在离线的HSM或安全宿主机上生成,不接入可能被攻破的网络。
- 内存保护——私钥在内存中的生命周期要尽可能短,且存放区域不能被swap、core dump或debugger读取。推荐使用mlock()锁定物理内存,用完立即memset置零。
- 侧信道防护——SM2的点乘运算容易受时间攻击和功耗分析。必须使用固定时间算法(如Montgomery ladder)或者用HSM硬件自带的防护。
- 日志清理——生成过程不该把中间变量(临时私钥、临时随机数)写进日志。我在审计日志时经常看到格式化输出里泄露了
d的前半截。
| 检查项 | 通过标准 | 常见失败场景 |
|---|---|---|
| 生成环境是否隔离 | 独享分区/容器/HSM,无其它进程干扰 | 在通用服务器上多进程共享生成 |
| 私钥内存是否锁定 | mlock() + 退出前清零 | 未锁定,被swap到磁盘 |
| 点乘是否固定时间 | 使用Montgomery ladder或等长处理 | 用double-and-add简单实现 |
| 是否有日志泄露 | 日志中不出现私钥、中间变量 | debug日志打印d值 |
我建议生成环境安全可以跟着“最少特权”原则走:只有生成程序本身能访问私钥内存,其它进程、用户,甚至root都不能通过ptrace或/proc来读到。Linux下可以设置prctl(PR_SET_DUMPABLE, 0)防止core dump。
综合审计视角:密钥生成三维模型
这三块不是孤立的。随机数质量不过关,算法再标准也没用;环境不安全,随机数可能被中途调包;算法走捷径,环境再安全也防不住内部bug。审计的时候,你应该把这三个维度放在一起看,而不是零散地查。
下面这张图总结了我做密钥生成审计时的思维框架:
你看,从顶部的“密钥生成”向下分三条主线,每条线下都有具体的审计指标。我每次做审查都会在脑子里或者纸上画这样一个模型,对照检查,不容易遗漏。特别是你可能只关注了其中一两个方面,但对称地,随机数质量、算法、环境三者互相支撑,任何一块都能成为木桶的短板。
总结一下审计重点:你不需要每次都自己写密钥生成代码,但必须理解它。重点审查随机数源、DRBG配置、d的合法范围校验、曲线参数硬编码、生成内存隔离和日志无泄露。把这几个点钉死,密钥生成环节就出不了大乱子。