3. AWS KMS 核心概念:密钥类型、密钥层级与 CMK

迁移前的第一件事,就是把 AWS KMS 里的“家底”盘清楚。

密钥类型、密钥层级、CMK 和数据密钥……这些概念如果不搞明白,到了 Azure 那边你会发现自己连门都摸不到。说白了,KMS 里的密钥就是个分层体系,每一层有自己的职责。

3.1 密钥类型:对称 vs 非对称

AWS KMS 支持两种密钥类型:对称密钥和非对称密钥。这俩的区别,不仅仅是数学原理不同,在实际使用场景上差别也很大。

对称密钥:加密和解密用同一把密钥。速度快,适合大量数据加密。

非对称密钥:公钥加密,私钥解密。适合签名验证、密钥交换,性能相对慢一些。

我个人习惯,只要不是必须用非对称的场景(比如数字签名),一律优先选对称密钥。为什么?因为对称密钥快啊,而且 AWS 的对称 CMK 是托管服务,你根本看不到明文密钥,安全性天然有保障。

我在项目中遇到过这么个事:有个团队在 API 网关做请求签名验证,死活要用非对称密钥。结果发现每次调用 KMS 做签名操作,延迟都比预期高。后来改成本地缓存公钥,用本地 OpenSSL 验证签名,好多了。所以你看,非对称密钥虽好,但它的场景要选对。

迁移需要注意什么?

Azure Key Vault 同样支持对称和非对称密钥。但有个差异得记住:AWS KMS 的非对称密钥默认支持 RSA 和 ECC 两种算法簇,而 Azure Key Vault 对 ECC 的支持在某些区域和 SKU 上有差异。迁移前,先确认你的非对称密钥算法类型,再查一下 Azure 那边支不支持。

3.2 密钥层级:从上到下的三层结构

AWS KMS 的密钥体系,说白了就是个分层模型。从上到下分三层:

  1. 根密钥(CMK):在 KMS 中创建或导入的顶级密钥,永远不离开 KMS 硬件安全模块(HSM)。
  2. 密钥加密密钥(KEK):CMK 本身就是一种 KEK,它专门用来加密其他密钥。
  3. 数据密钥(Data Key):由 CMK 生成,用于实际加密你的业务数据。可以拿到本地使用,用完建议立刻丢弃。

你想想看,如果每一份业务数据都直接调 KMS API 加密,那 KMS 的调用量和费用直接起飞。所以 AWS 设计了这套分层机制:用 CMK 生成数据密钥,用数据密钥去加密你的文件或数据库记录。数据密钥用完就丢掉,下次用再重新生成。

我的个人经验:在 S3 服务器端加密(SSE-KMS)场景中,AWS 自动帮你做这个分层。但在自建加密(比如客户端加密 S3 文件)时,你需要手动管理数据密钥的生成和缓存。我建议把数据密钥的明文使用周期控制在 5 分钟内,超过就重新生成,别省那点 KMS 调用费。

层级 名称 是否可导出明文 典型场景
第1层 CMK(根密钥) 否(永远在 HSM 内) 加密其他密钥、授权管理
第2层 KEK(加密密钥的密钥) 否(同 CMK) 保护下级数据密钥
第3层 数据密钥 是(可导出明文/密文对) 加密业务数据

3.3 CMK 与数据密钥:一对“夫妻”

CMK(Customer Master Key)和数据密钥(Data Key)是 KMS 里最核心的一对概念。很多人刚开始容易搞混,其实记住一句话就行:CMK 是管钥匙的,数据密钥是开锁的。

CMK 的特点:

  • 由 AWS KMS 托管,不能直接使用加解密功能操作业务数据。
  • 每个 CMK 有唯一的 ARN 和 Key ID。
  • 支持自动轮换(每年一次)和手动轮换。
  • 可以配置别名、密钥策略、授权。

数据密钥的特点:

  • 由 CMK 通过 GenerateDataKey API 生成。
  • 返回一对密钥:明文数据密钥(用于本地加密)和密文数据密钥(用于安全存储)。
  • 密文数据密钥只能由同一个 CMK 解密。
  • 数据密钥本身不存储,每次用的时候重新生成或从密文恢复。

我曾经踩过的一个坑:在迁移时,有个同事直接把 CMK 的 Key Material 拷贝到 Azure Key Vault 里,想“一对一对等”迁移。结果发现 Azure 那边用的是不同的密钥包装格式,根本解密不了 AWS 生成的数据密钥密文。正确的做法是:在 Azure Key Vault 里新建密钥,然后用旧 CMK 重新加密数据,或者用包装解锁(unwrap key)的方式过渡。

实际代码示例:用 CMK 生成数据密钥加密文件

以下是一个典型的客户端加密流程,用 AWS SDK for Python (boto3) 实现:

import boto3
from cryptography.fernet import Fernet

kms = boto3.client('kms')

# 1. 用 CMK 生成数据密钥
response = kms.generate_data_key(
    KeyId='alias/my-key',
    KeySpec='AES_256'
)

plaintext_key = response['Plaintext']      # 用于加密
ciphertext_key = response['CiphertextBlob'] # 用于存储

# 2. 加密文件
fernet = Fernet(plaintext_key)
with open('data.csv', 'rb') as f:
    encrypted_data = fernet.encrypt(f.read())

# 3. 保存加密数据 + 密文数据密钥
with open('data.encrypted', 'wb') as f:
    f.write(ciphertext_key + b'\n' + encrypted_data)

print("加密完成。数据密钥的密文已随文件保存。")

解密时,先读取密文数据密钥,用 kms.decrypt 解密得到明文密钥,再用 Fernet 解密文件内容。注意,不要保存明文数据密钥,用完立刻从内存中清理。

3.4 一张图看懂本章核心逻辑

下面这张 SVG 图把密钥类型、层级和 CMK/数据密钥的关系串起来了。你把它打印出来贴在工位上,迁移时随时对照。

AWS KMS 密钥体系核心结构 CMK(根密钥) 托管在 HSM,不可导出,终身不离开 KMS GenerateDataKey 数据密钥 (Data Key) 对 明文密钥 → 加密业务数据(用完即丢) 密文密钥 → 由 CMK 加密后安全存储 明文密钥 密文密钥 加密业务数据 S3 文件 / RDS 记录 / EBS 卷 安全存储密文密钥 与应用数据一起保存 密钥类型补充 CMK 可以是对称或非对称;数据密钥通常是对称的 (AES-256);密文数据密钥只能由原 CMK 解密解除。 业务数据 安全存储

从这张图可以看到,CMK 在最顶层,只做两件事:生成数据密钥、解密数据密钥。真正的业务加密,全部由数据密钥在本地完成。这种设计不仅避免了频繁调用 KMS 的延迟和费用,还让密钥管理职责更清晰。

迁移要点总结:

  • 对称密钥迁移到 Azure Key Vault,直接创建对应的 RSA/AES 密钥即可。
  • 非对称密钥要注意算法兼容性,尤其是 ECC 和 RSA-OAEP 填充方式。
  • CMK 不能直接迁移,需要在 Azure 重建,并用旧 CMK 解密数据密钥后再用新 CMK 重新加密。
  • 数据密钥本身不迁移,迁移的是“用数据密钥加密过的业务数据”和“用 CMK 加密过的密文数据密钥”。