4. TPM 2.0 证明实战:PCR、AIK 与 TPM Quote
好,咱们进入真正的动手环节了。前面几章讲了远程证明的理论框架和 TPM 的基本概念,现在该让键盘说话了。这一章我会带你走一遍 TPM 2.0 证明的核心流程:读取 PCR、生成 AIK、最后签发 Quote。整个过程都用 tpm2-tools 来完成,这是 Linux 下操作 TPM 2.0 的事实标准工具集。
本章实战目标:在一台具备 TPM 2.0 的机器上,用命令行完成一次完整的平台身份证明数据采集。
4.1 环境准备与工具安装
先确认你的环境。我建议用 Ubuntu 22.04 或更新的版本,内核 5.15 以上对 TPM 2.0 支持比较完善。如果你用的是虚拟机,记得在 vSphere 或 VirtualBox 里启用虚拟 TPM(vTPM)。
# 安装 tpm2-tools 和对应的资源管理器
sudo apt update
sudo apt install tpm2-tools tpm2-abrmd
# 启动资源管理器(tpm2-abrmd 负责管理 TPM 资源的并发访问)
sudo systemctl start tpm2-abrmd
sudo systemctl enable tpm2-abrmd
# 验证安装
tpm2_getcap --capability="properties-fixed" | grep "TPM2_PT_MANUFACTURER"
如果输出里能看到厂商 ID(比如 0x49424d00 代表 IBM 模拟器,0x53544d20 代表 STMicro),说明 TPM 已经正常响应了。我曾经在一台旧服务器上折腾了半天,结果发现 BIOS 里把 TPM 给禁用了……嗯,这种低级错误犯一次就够了。
小提示:如果你没有物理 TPM,可以用 swtpm 软件模拟器做练习。安装 swtpm 和 swtpm-tools,然后启动一个模拟实例,效果和硬件 TPM 基本一致。
4.2 读取平台配置寄存器(PCR)值
PCR 是 TPM 里最核心的度量存储区域。每个 PCR 存放一个 SHA-256(或 SHA-1)哈希值,记录从开机到现在的关键度量事件。说白了,PCR 就是平台的「健康档案」。
先看看有哪些 PCR 可用:
# 列出所有 PCR 的当前值(bank 表示哈希算法)
tpm2_pcrread
# 只读取 PCR 0-7(通常用于 BIOS/UEFI 和引导加载程序)
tpm2_pcrread sha256:0,1,2,3,4,5,6,7
# 读取 PCR 8(通常用于 GRUB 和内核)
tpm2_pcrread sha256:8
输出大概长这样:
sha256:
0 : 0x3A4F...(64 位十六进制字符串)
1 : 0xB2C1...
2 : 0xF8E6...
...
每个 PCR 的值都是 32 字节的哈希。如果两次启动之间硬件或固件没变,PCR 0-7 的值应该完全一致。我在做可信启动验证时,就靠对比 PCR 值来判断固件是否被篡改过。
注意:PCR 是「只扩展不重置」的。你不能直接写入一个值,只能通过 TPM2_PCR_Extend 操作把新哈希和旧值拼接后再哈希。所以 PCR 的最终值反映了所有度量事件的完整链条。
4.3 生成 AIK(Attestation Identity Key)
AIK 是 TPM 证明的「身份证」。它是一把非对称密钥(通常是 RSA-2048 或 ECC-256),专门用来签名 Quote 数据。AIK 有两个关键特性:
- 不可迁移:私钥永远锁在 TPM 硬件里,导不出来。
- 可认证:AIK 的公钥可以通过 TPM 的背书密钥(EK)来验证合法性。
生成 AIK 的步骤:
# 第一步:创建 AIK 的上下文文件(包含公钥和私钥的句柄)
tpm2_createprimary -C e -c primary.ctx
# 参数说明:
# -C e 使用背书层次(Endorsement Hierarchy)
# -c 输出主密钥上下文文件
# 第二步:基于主密钥创建 AIK
tpm2_create -C primary.ctx -G rsa2048 -a "fixedtpm|fixedparent|sensitivedataorigin|userwithauth|sign" -u aik.pub -r aik.priv
# 参数说明:
# -G rsa2048 密钥类型为 RSA 2048 位
# -a 密钥属性:固定 TPM、不可迁移、签名用途
# 第三步:加载 AIK 到 TPM 并获取句柄
tpm2_load -C primary.ctx -u aik.pub -r aik.priv -c aik.ctx
# 第四步:持久化 AIK(可选,但推荐)
tpm2_evictcontrol -C o -c aik.ctx 0x81000001
持久化之后,AIK 的句柄 0x81000001 会一直保留在 TPM 里,直到你显式删除它。我个人习惯把 AIK 持久化,这样后续 Quote 操作不需要每次都重新加载密钥文件。
关键点:AIK 的公钥(aik.pub)需要安全地传递给验证方。通常的做法是连同 EK 证书一起发送,验证方通过 EK 证书链来确认 AIK 确实来自一个真实的 TPM。
4.4 生成 TPM Quote
Quote 是 TPM 证明的核心产物。它本质上是一个签名后的数据结构,包含:
- 一组 PCR 的当前值
- 一个由验证方提供的随机数(nonce),防止重放攻击
- 签名算法标识和签名值
生成 Quote 的命令:
# 生成一个 16 字节的随机数(nonce),实际场景中由验证方提供
openssl rand -hex 16 > nonce.bin
# 使用 AIK 对 PCR 8 和 PCR 9 做 Quote
tpm2_quote -c aik.ctx -l sha256:8,9 -q nonce.bin -m quote.msg -s quote.sig -p quote.pcr
# 参数说明:
# -c AIK 上下文文件或持久化句柄
# -l 要引用的 PCR 列表(算法:索引)
# -q nonce 文件
# -m 输出 Quote 消息(明文)
# -s 输出签名值
# -p 输出 PCR 值(可选,方便验证)
执行成功后,你会得到三个文件:
| 文件 | 内容 | 用途 |
|---|---|---|
quote.msg |
Quote 的明文部分(PCR 值 + nonce + 元数据) | 验证方用公钥验证签名 |
quote.sig |
AIK 对 quote.msg 的签名 | 证明数据确实来自该 TPM |
quote.pcr |
被引用的 PCR 原始值 | 验证方比对预期值 |
实战经验:我曾经遇到一个坑——nonce 长度必须是 16 字节(32 个十六进制字符),否则 tpm2_quote 会报参数错误。另外,Quote 的 PCR 选择要谨慎,别把包含敏感信息的 PCR(比如 PCR 14 用于 UEFI 安全启动策略)暴露给不可信的验证方。
4.5 核心流程总览
下面这张图把整个流程串起来了。从读取 PCR 开始,到生成 AIK,再到签发 Quote,每一步都依赖上一步的输出。
4.6 验证 Quote 的完整性
Quote 生成之后,验证方需要做三件事:
- 提取公钥:从 AIK 上下文或持久化句柄中导出公钥。
- 验证签名:用公钥验证
quote.sig是否匹配quote.msg。 - 比对 PCR:将
quote.pcr中的值与预期值(比如已知的黄金镜像 PCR)做对比。
# 提取 AIK 公钥(如果之前持久化了)
tpm2_readpublic -c 0x81000001 -o aik.pub
# 验证签名(使用 openssl)
openssl dgst -verify aik.pub -keyform der -sha256 -signature quote.sig quote.msg
# 如果输出 "Verified OK",说明 Quote 确实由该 AIK 签名
重要提醒:验证签名只是第一步。即使签名通过,如果 PCR 值不符合预期(比如 PCR 8 的哈希和已知的干净内核不一致),那这台机器的可信状态依然存疑。签名保证「是谁说的」,PCR 保证「说的是什么」。
4.7 避坑指南
这些年我踩过的坑,列几个典型的:
- PCR 选择错误:有些 PCR 在每次启动都会变化(比如 PCR 9 记录 GRUB 配置),如果你用固定值去比对,永远对不上。我建议先做一次基线采集,记录「干净状态」下的 PCR 值。
- Nonce 重用:同一个 nonce 不能用于两次不同的 Quote,否则攻击者可以重放旧的 Quote 来欺骗验证方。每次 Quote 都必须用新的随机 nonce。
- AIK 持久化冲突:TPM 的持久化句柄数量有限(通常只有几个)。如果你频繁创建和删除 AIK,记得清理不再使用的句柄,否则会报
TPM_RC_NV_DEFINED错误。 - 资源管理器未启动:tpm2-abrmd 没启动时,所有 tpm2-tools 命令都会报「设备忙」或「连接失败」。检查
sudo systemctl status tpm2-abrmd确保它在运行。
我的习惯:每次做完 Quote 后,我会把 quote.msg、quote.sig、quote.pcr 和 aik.pub 打包成一个 tar 文件,加上时间戳命名。这样后续审计时能快速定位到某次证明的完整数据包。