4. 密钥生成:使用HSM生成对称与非对称密钥

密钥生成是整个生命周期真正的起点。说白了一件事——密钥如果在一开始就不安全,后面所有保护都是白搭。我个人习惯把密钥生成看作整条链路上最关键的一环,因为后续的存储、使用、轮换都建立在它的质量之上。

HSM在这里扮演的角色其实很纯粹:提供一个可信的、硬件的、抗篡改的环境来产生密钥材料。它不是普通软件库,它的随机数发生器是真随机,它的私钥一旦生成就永远以密文形式待在硬件里。嗯,这就是HSM的价值所在。

HSM密钥生成核心流程 密钥生成请求 身份验证与权限检查 密钥类型决策 对称密钥生成 (AES) 非对称密钥生成 (RSA/ECC) 密钥安全存储至HSM 返回密钥句柄 / 引用 ⚠ 关键原则 密钥材料永不 以明文离开HSM 熵源:硬件真随机 数发生器 (TRNG)

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 内部会做几件事:

  1. TRNG 生成种子(seed)
  2. 硬件加速器执行素数检测或曲线点乘运算
  3. 私钥直接写入 HSM 安全存储区
  4. 公钥以明文形式返回给调用方

这里面有一个容易被忽略的点:公钥虽然可以导出,但一定要验证其完整性。我建议在导出公钥时计算一个哈希值保存下来,后续使用时先验签,防止公钥被替换。

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 时就不会手忙脚乱。

嗯,这一章先到这儿。密钥生成只是起点,接下来它会被使用、轮换、最终销毁——每一步都有坑,后面我们一个一个聊。