4. 密钥管理操作:增、启停、轮换、删除

好了,咱们直接进入密钥的“一生”。

我个人习惯把密钥管理看成对一个非常敏感的服务实例的操作。你不仅要创建它,还得盯着它的状态,到时间了让它换密码,最后还得干净地销毁。这四步,一步都不能马虎。

4.1 创建密钥 — 第一行“出生证明”

创建密钥,说白了就是调用一个API,告诉KMS:“嘿,给我生成一个密钥,我要用。”

但这里头有几个关键参数,我建议你在设计SDK的时候,把它封装得清晰一点。

参数 说明 我踩过的坑
KeyUsage 用途:ENCRYPT_DECRYPT 还是 SIGN_VERIFY 别直接写死,要支持扩展
KeySpec 算法规格:AES_256, RSA_2048, SM4 等 我记得有回忘了验国密,线上直接报错
Origin 来源:KMS生成还是外部导入(EXTERNAL) 外部导入需要额外处理密钥材料
Tags 标签,用于资源管理和筛选 建议必传,不然运维想哭
我曾经犯过这个错: 在 SDK 里把密钥别名(Alias)和密钥 ID 混在一起返回。导致下游用户拿着别名去调用解密接口,结果因为 Alias 是动态的,引发了一串故障。记住:创建时返回的是固定的 KeyId。

来看一个最简单的创建代码片段。我比较喜欢用的是 Builder 模式,参数一看就明白。

CreateKeyRequest request = CreateKeyRequest.builder()
    .keySpec(KeySpec.AES_256)
    .keyUsage(KeyUsage.ENCRYPT_DECRYPT)
    .origin(Origin.KMS)
    .description("生产环境订单加密密钥")
    .tag("env", "prod")
    .tag("service", "order")
    .build();

CreateKeyResponse response = kmsClient.createKey(request);
System.out.println("新密钥 ID: " + response.keyId());

嗯,这里要注意:密钥创建之后,默认就是“已启用”状态。 不需要额外调启用接口。

4.2 启用与禁用 — 遥控器上的暂停键

你想想看,生产环境中突然发现某个密钥被泄露了,你要怎么办?直接删除?别急。先禁用,切断使用,等找到根因再说。

enableKey 和 disableKey 是两个非常轻量的操作。它们不修改密钥材料,只修改状态机里的一个布尔值。

个人经验: 我建议在 SDK 里把这俩操作封装成“原子操作+幂等”。也就是说,一个已经禁用的密钥,你再调一次 disableKey,它不会报错,只会返回同样的结果。这样上层业务重试时不会翻车。

禁用后,所有需要用到这个密钥的加密、解密、签名请求都会失败。常见错误码:DisabledKeyException 或类似的 KMSInvalidStateException。

什么时候用启用/禁用?

  • 应急止损: 疑似泄漏,先禁再说
  • 版本回退: 灰度发现新密钥有问题,切回旧密钥
  • 合规轮换: 旧密钥不再用于新加密,但需要留着给解密用

我记得有一次,一个同事直接删了还在用的密钥,导致大量历史数据解密失败。后来我们的流程里加了一条:删之前必须禁用观察 7 天。

4.3 密钥轮换 — 别让你的“密码”用到老

轮换,说白了就是定期生成一个新的密钥版本来替代旧的。KMS 里主要分成两种:自动轮换和手动轮换。

类型 机制 我推荐的策略
自动轮换 KMS 按配置的间隔(比如每年)自动生成新版本 适合常规加密,省心
手动轮换 你主动调用 rotateKey,触发一次版本升级 适合有特殊合规要求的场景

设计 SDK 的时候,我建议把轮换理解成创建新版本+切换主版本的过程。不会让你单独去调“删除旧版本”的接口,KMS 自动保留旧版本用于解密历史数据。

// 开启自动轮换(每年一次)
kmsClient.enableKeyRotation(
    EnableKeyRotationRequest.builder()
        .keyId("your-key-id")
        .rotationPeriodInDays(365)
        .build()
);
一个容易被忽略的点: 轮换只影响后续加密,不影响已经加密的数据。你之前用旧版本加密的密文,只有用对应的旧版本密钥才能解开。所以 SDK 中 decrypt 接口底层会自动根据密文中的 KeyVersionId 去找版本,不需要用户操心。

4.4 删除密钥 — 盖棺定论,别急着埋

删除是一个不可逆操作。一旦删了,之前用这个密钥加密的所有数据,都等于丢了。这就是为什么 AWS 和大多数云厂商都要求设置一个等待期(PendingDeletionWindowInDays),从 7 到 30 天不等。

我建议在设计 SDK 时,把 deleteKey 接口做成两步:

  1. 计划删除(scheduleKeyDeletion):密钥进入“待删除”状态,不可用于加密解密,但还能取消。
  2. 取消删除(cancelKeyDeletion):在等待期内恢复密钥,回到“已禁用”或“已启用”状态。
// 计划删除,7天后真正销毁
kmsClient.scheduleKeyDeletion(
    ScheduleKeyDeletionRequest.builder()
        .keyId("your-key-id")
        .pendingWindowInDays(7)
        .build()
);

// 如果发现删错了,赶紧取消
kmsClient.cancelKeyDeletion(
    CancelKeyDeletionRequest.builder()
        .keyId("your-key-id")
        .build()
);

💡 为什么要有等待期?

我曾经在一个大型项目中,因为运维脚本的 bug,误删了一批测试环境的密钥。幸好有 7 天等待期,我们及时取消操作,才没酿成大祸。从那以后,我在所有 SDK 设计里都强制要求:删除密钥必须经过等待期,且 SDK 层面不能提供跳过等待期的参数。

4.5 整体生命周期管理 — 一张图说清楚

下面这张图,基本概括了密钥从生到死的完整流程。我建议你在做 SDK 设计文档时,把这张图放进去,比大段文字解释管用得多。

密钥生命周期管理——核心状态流转 1. 创建密钥 2. 已启用 3. 已禁用 4. 轮换(版本+1) 5. 待删除 6. 已删除 说明 - 实线:正向操作 - 虚线:回退/恢复操作

好,这四种操作,我已经全部讲完了。

说实话,密钥管理不是一个高频操作,但一旦出错,代价极高。我在设计 SDK 时,宁愿在接口上多做一些“保护性”设计,比如强制等待期、幂等校验、清晰的错误码,也不愿意等到线上出事故了再补漏洞。

嗯,今天就到这里。把这四种操作吃透,你的 KMS SDK 设计就已经及格了。