4、可信平台模块(TPM)实战:TPM规范与功能、TPM密钥层次结构、使用TPM生成和存储根密钥

聊到根密钥的物理存储,TPM 是个绕不开的话题。我最早接触 TPM 是在做一款金融安全终端的时候,当时厂商给的文档厚得像本字典,读完后最大的感受是——这东西真能藏东西,但用起来也真讲究。今天咱们就把它拆开看看。

4.1 TPM 规范与核心功能

TPM,全称 Trusted Platform Module,说白了就是一个焊在主板上的专用安全芯片。它有自己的处理器、非易失性存储、甚至独立的随机数发生器。你想想看,密钥放在操作系统里,再强的加密也怕内存 dump;但放在 TPM 里,连操作系统都读不到它的内部状态。

TPM 的规范由 TCG(Trusted Computing Group)维护,目前主流的是 TPM 2.0。相比 1.2 版本,2.0 最大的变化是算法灵活性——不再死磕 SHA-1 和 RSA,而是支持了 ECC、AES、SM3/SM4 等更多选择。

TPM 的核心能力就三件事:

  • 密钥生成与存储——在芯片内部生成密钥,私钥永远不离开 TPM
  • 平台完整性度量——通过 PCR(平台配置寄存器)记录系统启动状态
  • 密封与证明——将数据绑定到特定软硬件状态,只有状态匹配才能解封

我个人习惯把 TPM 理解成一个「保险箱」:你把密钥放进去,它给你一个句柄(handle),以后你拿着句柄去操作密钥,但永远看不到密钥本身。嗯,这里要注意——TPM 不是用来做高速运算的,它的加解密速度远不如 CPU,但它胜在安全隔离。

4.2 TPM 密钥层次结构

这是 TPM 最核心的设计,也是很多人容易搞混的地方。TPM 的密钥不是平铺的,而是分层的树状结构。我画了一张图,你看完就明白了。

TPM 密钥层次结构图 TPM 密钥层次结构 主密钥 (SRK) 存储密钥 (Storage Keys) 身份密钥 (AIK) 绑定密钥 (Binding Keys) 签名密钥 (Signing Keys) 密封密钥 (Sealed Keys) 密钥保护关系 • 子密钥的私钥由父密钥加密后存储在外部 • SRK 的私钥永远在 TPM 内部,不可导出 • 加载子密钥时需要逐层解密,形成信任链 • 根密钥就是 SRK,它是整个层次的安全锚点

这张图里最关键的是最顶层的 SRK(Storage Root Key,存储根密钥)。SRK 在 TPM 出厂时或首次初始化时生成,私钥永远锁死在芯片内部。它下面的所有密钥,私钥都是被 SRK 加密后存在硬盘上的——这叫「密钥封装」。你每次要用某个子密钥,TPM 会先用 SRK 解密,加载到芯片内部使用,用完再封存回去。

我的经验: 在项目中,我建议把 SRK 的密码(authValue)单独保存在离线密码保险柜里。一旦丢失,所有子密钥都无法加载——我曾经见过一个团队因为把 SRK 密码写在代码注释里,结果代码泄露后整个密钥体系报废。

4.3 使用 TPM 生成和存储根密钥

好了,理论讲完,咱们上手实操。下面我用 tpm2-tools 演示如何在 TPM 中生成根密钥并存储。这套工具是 Linux 下操作 TPM 2.0 的事实标准。

4.3.1 检查 TPM 可用性

先确认你的机器有没有 TPM,以及驱动是否正常加载。

# 检查 TPM 设备是否存在
ls /dev/tpm*  # 应该看到 /dev/tpm0 或 /dev/tpmrm0

# 查看 TPM 版本和厂商信息
tpm2_getcap properties-fixed

# 检查是否已存在 SRK
tpm2_getcap handles-persistent

如果输出为空,说明 TPM 还没初始化。我第一次在服务器上跑这个命令时,发现 TPM 被 BIOS 禁用了——折腾了半天才发现是主板设置的问题。所以,先检查 BIOS 里 TPM 有没有开启。

4.3.2 创建存储根密钥(SRK)

SRK 的创建是一次性的。TPM 2.0 支持多种算法,我个人推荐用 ECC,性能更好,密钥更短。

# 创建 ECC 类型的 SRK,持久化到句柄 0x81000001
tpm2_createprimary -C o -G ecc -g sha256 \
  -a "fixedtpm|fixedparent|sensitivedataorigin|userwithauth|restricted|decrypt" \
  -p "your_srk_password" \
  -c srk.ctx

# 将 SRK 持久化到 TPM 的 NVRAM 中
tpm2_evictcontrol -C o -c srk.ctx 0x81000001

这里解释几个关键参数:

  • -C o:使用 TPM 所有者授权(platform hierarchy)
  • -G ecc:密钥算法为 ECC(NIST P-256)
  • -a "...":密钥属性,决定了密钥的用途和限制
  • -p:SRK 的授权密码,务必妥善保管

警告: 持久化句柄 0x81000001 是 SRK 的常用约定句柄。但不同厂商的 TPM 可能占用不同范围,建议先查一下可用句柄范围(tpm2_getcap handles-persistent)。我曾经在 Intel 的 TPM 上用了 0x81000000,结果和 BIOS 预留的冲突,导致系统启动时 TPM 报错。

4.3.3 在 SRK 下创建子密钥

有了 SRK,我们就可以创建受它保护的子密钥了。下面创建一个用于数据加密的存储密钥。

# 加载 SRK 到 TPM 会话
tpm2_load -C 0x81000001 -u srk.pub -r srk.priv -c srk_load.ctx

# 在 SRK 下创建子密钥(存储密钥)
tpm2_create -C srk_load.ctx -G aes -g sha256 \
  -a "fixedtpm|fixedparent|sensitivedataorigin|userwithauth|decrypt" \
  -p "child_key_password" \
  -u child.pub -r child.priv

# 加载子密钥到 TPM
tpm2_load -C srk_load.ctx -u child.pub -r child.priv -c child.ctx

注意看,子密钥的私钥文件 child.priv 是加密后存在磁盘上的。没有 SRK 和正确的密码,谁也解不开。这就是 TPM 的「密钥层次」在实战中的体现。

4.3.4 使用子密钥加密数据

密钥生成好了,怎么用?下面演示用子密钥加密一段敏感数据。

# 准备要加密的数据
echo "This is my root key seed material" > secret.txt

# 使用子密钥加密(对称加密)
tpm2_encryptdecrypt -c child.ctx -o secret.enc secret.txt

# 解密验证
tpm2_encryptdecrypt -c child.ctx -d -o secret.dec secret.enc
cat secret.dec

整个过程,AES 密钥只在 TPM 内部使用,外部永远看不到明文密钥。你想想看,就算攻击者拿到了 child.priv 和 secret.enc,没有 SRK 和密码,他什么都做不了。

4.4 实战中的避坑指南

做 TPM 开发这几年,我踩过不少坑。下面列几个最常见的,你遇到了能少走弯路。

问题场景 原因分析 解决方案
TPM 返回 TPM_RC_NV_DEFINED NVRAM 索引已被占用 换一个索引,或先释放旧的
加载子密钥时密码错误 authValue 不匹配 创建时用 -p 设置,加载时用 -P 传入
SRK 持久化失败 句柄冲突或权限不足 检查 TPM 所有者授权是否启用
加密数据无法在其他机器解密 子密钥绑定到特定 TPM 这是设计特性,需迁移密钥时用 tpm2_duplicate

我的习惯: 每次创建密钥后,立即用 tpm2_getcap handles-persistent 确认持久化成功,并把句柄和用途记录到密钥清单中。项目大了之后,密钥一多,光靠脑子记肯定乱套。

4.5 小结

TPM 的密钥层次结构,说白了就是「一级保一级」的信任链。SRK 是锚点,它锁在芯片里;子密钥由 SRK 保护,存在磁盘上。这种设计的好处是:即使硬盘被拔走,密钥依然是安全的。坏处是:一旦 SRK 损坏或密码丢失,所有子密钥都跟着报废。

在实际项目中,我建议把 TPM 生成的根密钥作为「密钥加密密钥」(KEK),真正的业务密钥再由 KEK 派生。这样即使业务密钥需要轮换,根密钥可以保持不变。嗯,这个思路在后面的章节里还会深入展开。