4. 密钥生成:使用HSM生成对称与非对称密钥
密钥生成是整个生命周期真正的起点。说白了一件事——密钥如果在一开始就不安全,后面所有保护都是白搭。我个人习惯把密钥生成看作整条链路上最关键的一环,因为后续的存储、使用、轮换都建立在它的质量之上。
HSM在这里扮演的角色其实很纯粹:提供一个可信的、硬件的、抗篡改的环境来产生密钥材料。它不是普通软件库,它的随机数发生器是真随机,它的私钥一旦生成就永远以密文形式待在硬件里。嗯,这就是HSM的价值所在。
4.1 为什么非要在HSM内部生成?
你可以在软件里调用 OpenSSL 生成密钥对,然后再导入 HSM。这听起来似乎也行。但我在项目里见过太多次——有人图省事在应用服务器上用软件生成私钥,再通过网络传给 HSM,结果密钥在传输过程中被截获,整个安全体系瞬间崩塌。
HSM 内部生成的核心优势有三个:
- 真随机熵源:HSM 内置硬件真随机数发生器 (TRNG),不是软件伪随机。随机数质量差,密钥就容易被人猜出来。
- 私钥永不离开硬件:私钥一旦生成就留在 HSM 内部,外部拿不到明文。API 返回的只是一个句柄(handle),不是密钥本身。
- 操作审计与权限隔离:每一次生成操作都会有审计记录,而且可以绑定具体的用户角色,谁在什么时候生成过什么密钥,一清二楚。
4.2 对称密钥生成:AES 为例
对称密钥,说白了就是加密和解密用同一个密钥。最常见的当然是 AES。HSM 里生成 AES 密钥,你只需要指定密钥长度和用途标签。
关键参数说明
| 参数 | 说明 | 建议值 |
|---|---|---|
| CKA_CLASS | 密钥类型 | CKO_SECRET_KEY |
| CKA_KEY_TYPE | 算法类型 | CKK_AES |
| CKA_VALUE_LEN | 密钥长度(位) | 256(推荐) |
| CKA_ENCRYPT / CKA_DECRYPT | 使用用途 | CK_TRUE |
| CKA_ | 是否存储在HSM中 | CK_TRUE |
我个人建议对称密钥至少用 256 位。128 位目前虽然还安全,但考虑到系统的生命周期——你想想看,这套系统可能要跑 5 到 10 年——直接上 256 位省心。
生成时还有一个容易踩坑的地方:用途标签(CKA_ENCRYPT、CKA_DECRYPT 等)一定要精确设置。我在项目中遇到过,有人图方便把所有用途标签全设为 CK_TRUE,结果审计时发现密钥被用于非授权操作,合规过不了。嗯,这个后面讲到密钥用途约束时还会细说。
4.3 非对称密钥生成:RSA 与 ECC
非对称密钥生成比对称复杂一些,因为它涉及密钥对——公钥和私钥。私钥留在 HSM 里,公钥可以导出。
RSA 密钥生成:核心是找到两个大素数,这非常消耗计算资源。HSM 有专门的硬件加速器,所以比普通 CPU 快得多。我记得有一次用软件生成 4096 位 RSA 密钥对,等了将近半分钟,HSM 里几秒就完成了。
ECC 密钥生成:椭圆曲线密钥生成速度比 RSA 快,而且同样安全强度下密钥长度短很多。但要注意曲线选择——P-256、P-384、P-521 是主流,Curve25519 也越来越常用。
💡 选型建议
新系统我强烈建议优先考虑 ECC。同样的安全强度,ECC 256 位等价于 RSA 3072 位,而且生成速度快、签名效率高。除非你有历史兼容性要求,否则别再开新项目用 RSA 2048 了。
生成非对称密钥对时,HSM 内部会做几件事:
- TRNG 生成种子(seed)
- 硬件加速器执行素数检测或曲线点乘运算
- 私钥直接写入 HSM 安全存储区
- 公钥以明文形式返回给调用方
这里面有一个容易被忽略的点:公钥虽然可以导出,但一定要验证其完整性。我建议在导出公钥时计算一个哈希值保存下来,后续使用时先验签,防止公钥被替换。
4.4 用 PKCS#11 调用 HSM 生成密钥
大多数 HSM 都支持 PKCS#11 接口。下面是一个生成 AES-256 密钥的示例:
// 生成 AES-256 对称密钥(PKCS#11 C 示例)
CK_SESSION_HANDLE hSession;
CK_OBJECT_HANDLE hKey;
CK_MECHANISM mechanism = {CKM_AES_KEY_GEN, NULL_PTR, 0};
// 定义密钥模板
CK_ATTRIBUTE template[] = {
{CKA_CLASS, &class, sizeof(class)},
{CKA_KEY_TYPE, &keyType, sizeof(keyType)},
{CKA_VALUE_LEN, &keyLen, sizeof(keyLen)},
{CKA_ENCRYPT, &trueVal, sizeof(trueVal)},
{CKA_DECRYPT, &trueVal, sizeof(trueVal)},
{CKA_, &trueVal, sizeof(trueVal)}
};
CK_RV rv = C_GenerateKey(hSession, &mechanism,
template, 6, &hKey);
if (rv == CKR_OK) {
// hKey 就是密钥句柄,后续加解密都用它
}
生成 RSA 密钥对类似,只是机制换成 CKM_RSA_PKCS_KEY_PAIR_GEN,模板里要分别定义公钥属性和私钥属性。
⚠ 重要警告
生成密钥对时,不要设置 CKA_EXTRACTABLE 为 CK_TRUE,除非你明确知道自己在做什么。允许私钥可导出,就等于打开了攻击面。我曾经在一个客户现场发现,有人为了方便备份,把所有 HSM 密钥都设成了可导出,结果运维脚本泄露导致私钥被批量窃取——血的教训。
4.5 避坑指南:我踩过的几个坑
这么多年做 HSM 对接,有些坑说起来都是泪。分享几个典型的:
- 随机数耗尽:高并发场景下,HSM 的 TRNG 可能有速率限制。我曾经遇到一个支付系统,每秒请求数千次密钥生成,直接把 HSM 的随机数池抽干了,后续生成请求全部超时。解决方案是加本地预生成缓冲池。
- 会话管理不当:每次生成密钥都打开一个新会话,用完不关闭,最后 HSM 会话数耗尽。这其实是基础问题,但我见过好几家都犯了。
- 密钥用途与算法不匹配:生成 RSA 密钥时指定了 CKA_ENCRYPT,但后续却拿它做签名——PKCS#11 会报错 CKR_KEY_TYPE_INCONSISTENT。一开始就规划好用途,后面省很多麻烦。
4.6 小结一张表:对称 vs 非对称生成对比
| 对比维度 | 对称密钥(AES) | 非对称密钥(RSA/ECC) |
|---|---|---|
| 生成速度 | 快(微秒级) | 慢(毫秒~秒级,RSA 尤慢) |
| 密钥长度 | 128 / 192 / 256 位 | RSA 2048+ / ECC 224+ |
| 密钥数量 | 1 个密钥 | 1 对(公钥 + 私钥) |
| 私钥是否可导出 | 一般不导出 | 私钥永不导出,公钥可导出 |
| 典型用途 | 批量数据加密 | 密钥交换、数字签名 |
| HSM 资源消耗 | 低 | 高(尤其 RSA 素数生成) |
密钥生成这个环节,看起来就是调一个 API 的事,但背后涉及随机数质量、权限管控、用途规划、资源管理好几个层面。你在设计系统时,一定要把这些前置条件想清楚。我个人习惯是在项目初期就列一张密钥清单,标明每个密钥的算法、长度、用途、生命周期,这样后面对接 HSM 时就不会手忙脚乱。
嗯,这一章先到这儿。密钥生成只是起点,接下来它会被使用、轮换、最终销毁——每一步都有坑,后面我们一个一个聊。