2. HSM密钥生命周期管理

密钥从生到死,中间经历哪些阶段?每个阶段能不能导出?这是整个课程最基础的一块。我个人习惯把密钥生命周期切分成五个阶段:创建→激活→使用→归档→销毁。

为什么这样设计?你想想看,密钥不能一直处于可用状态。日常业务密钥、长期存档密钥、废弃密钥,它们的安全策略完全不同。我在项目中遇到过一家客户,所有密钥都标记为"永久可用",结果想替换旧算法时,连密钥都找不全了——因为从来没归档过。

下面这张图概括了完整流程:

HSM密钥生命周期与导出限制状态机 创建 未初始化 激活 可导出? 看策略 使用 内部操作 归档 仅备份用途 销毁 不可恢复 ⚠ 导出控制策略在每个阶段差异很大 创建时通常不允许导出,激活/使用阶段可按策略导出,归档阶段仅限加密导出,销毁后彻底不能导出

2.1 密钥创建阶段

密钥第一次被生成出来的时候,它叫"未初始化"状态。说白了,就是密钥材料已经存在,但还不能用于加解密操作。

在这个阶段,HSM默认禁止导出。为什么这样设计?嗯,你想想,一个刚出生的密钥,连它的属性、权限、生命周期策略都没绑定,直接导出纯粹是给自己找麻烦。我曾经见过一个开发团队,直接在创建时就把密钥导出到文件里——结果密钥材料还在内存里,被日志系统不小心打了出来。

关键规则

创建阶段导出策略:不允许导出。必须设定密钥用途、算法、可导出标记(CKA_EXTRACTABLE)之后,密钥才能进入激活阶段。

2.2 密钥激活阶段

激活阶段是密钥生命周期中最关键的节点。HSM会检查密钥的元数据策略:

  • CKA_EXTRACTABLE = True:允许将来导出(以加密形式)。
  • CKA_EXTRACTABLE = False:永远无法导出,只能留在HSM内部使用。
  • CKA_ALWAYS_EXTRACTABLE = True:可导出标记不能撤销,一旦设置为可导出,之后不能改成不可导出。

我在项目中遇到过一种坑:客户生成了密钥对,把私钥标记为不可导出。后来需要做密钥迁移,发现私钥拿不出来,只能重新生成。这不是HSM的问题,是设计时没想清楚私钥是否需要跨HSM迁移。

注意

CKA_ALWAYS_EXTRACTABLE一旦设置为True,后续不能改为False。如果你不确定密钥将来是否需要导出,建议预设为True,但只在确定需要导出时执行导出操作。

2.3 密钥使用阶段

密钥激活后进入使用阶段。这时候HSM主要处理:

  • 签名/验签、加密/解密、密钥协商
  • 密钥旋转:定期或事件触发,生成新密钥替换旧密钥

导出控制方面,使用阶段的关键点是:

允许加密导出。但要满足:

  1. 必须使用HSM内部的其他密钥(KEK)来加密。
  2. 导出的密钥材料是密文,明文永远不会离开HSM的边界。
  3. 只有在CKA_EXTRACTABLE = True时才能执行。

个人习惯

我建议在使用阶段始终通过HSM内部操作完成所有加解密。尽量不要触发导出,除非你有明确的备份或迁移需求。导出越多,暴露面越大。

2.4 密钥归档阶段

密钥不再用于日常业务,进入归档状态。这个阶段密钥理论上只用于历史数据解密或合规审计。

归档阶段的导出策略:

  • 只能加密导出,且目标必须是受控的长期归档介质。
  • 归档后的密钥不可重新激活为使用状态(部分HSM支持,取决于厂商实现)。
  • 导出时需双人授权(Split Knowledge)才能执行。

我曾经协助一家金融机构做合规审查,他们归档了5年的交易密钥。但归档时没设置正确的导出策略,三年后需要解密老数据时,密钥还在HSM里,但导出策略被锁定为不可导出。最后只能找厂商打补丁——费时费力。

2.5 密钥销毁阶段

密钥的生命终点,就是销毁。HSM清除所有密钥材料,并记录审计日志。

销毁阶段的导出规则:彻底禁止。密钥一旦销毁,没有任何办法恢复。

这里有个重要的点:安全删除。HSM不会只删个指针,它会:

  1. 覆盖密钥内存区域多次
  2. 清除所有备份副本
  3. 更新密钥状态标记为"销毁"

我曾经踩过的坑

有一次做密钥销毁测试,我忘了确认密钥是否还被其他服务引用。结果销毁后,依赖该密钥的加密通道全部断了。从那天起,我每次销毁前都会跑一遍依赖关系检查。

2.6 生命周期状态对比表

生命周期阶段 导出是否允许 导出形式 典型操作
创建 禁止 — 设置属性、生成密钥
激活 按策略 加密导出 分配权限,启用使用
使用 允许(有条件) 加密导出 加解密、签名、密钥旋转
归档 仅限加密导出 加密导出 备份、长期存储
销毁 彻底禁止 — 安全删除,清除审计

2.7 SDK适配的关键思路

搞清楚了生命周期,SDK怎么适配?核心就两点:

  • 状态感知:SDK要能查询密钥当前处于哪个生命周期阶段,根据阶段决定是否允许导出。
  • 策略映射:把HSM的原生属性(CKA_EXTRACTABLE等)翻译成你的业务策略。

举个例子,下面是我常用的SDK生命周期检查代码片段:

// 伪代码:检查密钥是否可导出
function canExportKey(keyHandle) {
    let state = hsm.getKeyState(keyHandle);
    
    if (state === "CREATED") {
        return false;  // 创建阶段,不允许导出
    }
    
    if (state === "ACTIVE" || state === "USING") {
        let extractable = hsm.getAttribute(keyHandle, "CKA_EXTRACTABLE");
        return extractable === true;
    }
    
    if (state === "ARCHIVED") {
        return true;  // 归档阶段允许加密导出
    }
    
    // 销毁或未定义
    return false;
}

为什么这样设计? 因为不同HSM厂商的生命周期命名可能不同,但背后的逻辑是一样的。SDK的作用就是屏蔽这些差异,提供统一的接口。