第一章 · 概念地图:信任链、PCR 与度量
本章目标
这一章没有命令,只讲概念。读完后你应该能回答这些问题:
- 开机时,系统怎么知道自己没被动过手脚?
- TPM 这颗芯片凭什么可信?
- PCR 是什么,为什么说它“只能加不能减“?
- 度量日志和 PCR 是什么关系,为什么要“重放“它?
- 度量启动(Measured Boot)和 Secure Boot 到底有什么区别?
- Seal、远程证明、密钥层级这些名词分别对应什么能力?
这些是后面所有实验的地基。我见过太多教程上来就是 tpm2_createprimary,敲完也不知道自己干了什么。我们反过来:先把概念地图画清楚,再动手。
注意
先澄清几个容易混的名词。TPM 是那颗芯片(或固件/软件实现)本身,行为由 TCG(Trusted Computing Group,可信计算组织)的规范定义;TSS(TPM Software Stack)是操作 TPM 的软件栈,Linux 上的事实标准是
tpm2-tss项目,我们后面用的tpm2_*命令都来自它;“可信计算(Trusted Computing)“则是这整套思想的统称。这个领域早年因为可能被用于 DRM 而备受争议,但如今它在全盘加密、可信启动上的价值已经无可替代。
从一个问题开始
假设你怀疑自己的笔记本被人动过——比如出差时把电脑落在了酒店房间。攻击者只要几分钟,就能换掉你的引导程序,装一个看起来一模一样、但会偷偷记录你磁盘解锁密码的恶意版本(这就是第八章要细讲的 Evil Maid 攻击)。
问题来了:下次开机时,系统怎么知道自己没被动过手脚?
靠操作系统自己检查是不行的。如果攻击者已经控制了引导程序,他同样能控制检查结果,让一切看起来“正常“。你需要一个在攻击者够不着的地方做检查的东西。
这就是 TPM 存在的意义。更准确地说,TPM 给出的回答分两步:第一步,开机过程中的每一环都被算成哈希、存进一个攻击者改不了的寄存器;第二步,把重要的秘密(比如磁盘密钥)锁在“这些寄存器必须是这个样子“的条件上。攻击者动了任何一环,寄存器就变了,秘密就取不出来。这一章剩下的内容,就是把这两步里的每个概念讲清楚。
信任链:每一级先量一量下一级
先建立一个直觉:电脑启动是一级一级移交控制权的接力赛——
固件(UEFI) → 引导程序(Bootloader) → 内核 → initramfs → 操作系统
可信启动的思路是:每一级在把控制权交给下一级之前,先给下一级“量一量“——算出它的哈希值,记下来。 这个动作叫度量(Measure)。固件度量引导程序,引导程序度量内核,一环扣一环,形成信任链(Chain of Trust)。
关键在于,度量结果被记在哪里。如果记在普通内存或磁盘里,攻击者改了系统之后顺手把记录也改了,白搭。所以度量结果必须记在一个攻击者改不了的地方——TPM 芯片里的 PCR。
只要链条的起点(第一段代码)是可信的,而每一级都忠实地度量了下一级,那么链条末端的人只要检查这些度量记录,就能知道整条链上跑过的每一段代码是什么。
这个起点有个专门的名字:核心信任度量根(Core Root of Trust for Measurement, CRTM)。它是固件里第一段执行、负责发起度量的代码,被设计为不可更新(或者更新本身就受硬件保护)——因为它是整个链条里唯一“无条件被信任“的环节,它要是能被改,后面所有的度量就都是演戏。PCR 0 里记录的第一条度量,往往就是固件主体对 CRTM 之外部分的度量。
思考
注意这里的一个微妙之处:TPM 并不能“阻止“恶意代码运行,它只能保证“恶意代码跑过之后一定留下抹不掉的证据“。记录和控制是两回事——这正是后面“度量启动 vs Secure Boot“一节要展开讲的。
信任根:TPM 凭什么可信
信任总得有个起点,这个起点叫信任根(Root of Trust)。TPM 作为信任根,靠三样东西:
物理防篡改。 TPM 是一颗独立的芯片(或者以固件形式运行在 CPU 的受保护区域里),它的内部存储普通软件够不着。攻击者控制了操作系统,也读不到 TPM 里保存的密钥,改不了 PCR 的值。要动它,得物理拆片、用显微镜探针这种级别的手段——成本完全不是一个量级。
密钥不出芯片。 TPM 里生成的私钥可以设置为永远不离开芯片。加密、签名这些操作在芯片内部完成,外界只能拿到结果。即使你的系统被 rootkit 完全控制,攻击者也只能“使用“密钥(而且还得过授权这一关),无法“偷走“密钥。
规范约束。 TPM 的行为由 TCG(Trusted Computing Group,可信计算组织)制定的公开规范严格定义。PCR 怎么更新、密钥怎么派生、授权怎么校验,都是白纸黑字,各家厂商(Intel、AMD、英飞凌、STMicro,以及软件实现 swtpm)遵守同一套规范。本书基于 TPM 2.0 规范。
注意
市面上“TPM“有三种形态:独立芯片(dTPM)、CPU 内置固件实现(Intel PTT / AMD fTPM)、纯软件模拟(swtpm)。本书实验用 swtpm,行为与真实 TPM 2.0 一致——规范保证了这一点。
另外,查资料时你还会碰到 TPM 1.2 这个老版本。它和 2.0 的差别不是“升级“而是“重设计“:1.2 只支持 SHA-1、只有一套固定的密钥结构,安全性和灵活性都已过时。2016 年后的机器基本都是 2.0,Windows 11 更是强制要求。网上 1.2 时代的教程(用的是
tpm_sealdata这类老工具)和本书的命令完全不通用,看到请直接跳过。
PCR:只能加、不能减的寄存器
平台配置寄存器(Platform Configuration Register, PCR)是 TPM 里一组特殊的寄存器,每个存一个哈希值。TPM 2.0 通常提供 24 个 PCR,编号 0 到 23。
PCR 的灵魂在于它的更新方式。你不能往里写新值,唯一的操作叫 extend(扩展):
新值 = Hash(旧值 || 新度量)
|| 是拼接。也就是说,每次度量把“当前的 PCR 值“和“新度量值“拼在一起算哈希,结果覆盖原值。
这个简单到有点简陋的公式,带来三个极其重要的性质:
不可逆。 PCR 的初始值是全零。一旦 extend 过,没有任何办法把它退回去。想回到某个历史值,唯一的方法是重启整机(大部分 PCR 在重启时清零)。软件做不到,root 做不到,攻击者也做不到。
顺序敏感。 Hash(A || B) 和 Hash(B || A) 完全不同。所以 PCR 的值不仅记录了“度量过什么“,还记录了“按什么顺序度量的“。启动链上任何一环被替换、增删、调序,最终的 PCR 值都会变。
历史不可伪造。 想伪造一个“正常“的 PCR 值,攻击者必须找到一段恶意代码,使得 Hash(旧值 || 恶意代码的度量) 恰好等于预期值——这等于对哈希函数做原像攻击,在密码学上不可行。
把这三条连起来,一个只有两次度量的小例子就能看清全过程。假设启动中依次度量了两段代码 M1、M2,PCR 的演化是:
0x000...000 # 开机初始值
extend(M1) → Hash(0x000...000 || M1)
extend(M2) → Hash(Hash(0x000...000 || M1) || M2)
只要攻击者把 M1 换成 M1’,最终值立刻不同。就算他本事大到连 M2 也一起换成某个 M2’,想让两步之后的结果碰巧撞上原值——那等于找到哈希函数的碰撞,SHA-256 目前做不到。第三章的实验 A,我们会用真实命令把这个过程亲手跑一遍。
注意
真实的 TPM 里,PCR 不是“24 个“,而是“24 个 × 若干组“。同一编号的 PCR 按哈希算法分成多个 bank(常见的有 sha1 和 sha256 两组),每次度量通常同时 extend 所有 bank。本书一律使用 sha256 bank,这是当前的事实标准;sha1 bank 只是历史兼容的摆设,不要依赖它。
坑
“PCR 为什么不能重置“是新手最常见的困惑。答案:如果能重置,前面的一切就毫无意义了——攻击者改完系统,把 PCR 重置回正常值即可。“不可重置“不是缺陷,是整个设计的核心。少数 PCR(如 PCR 16)特意允许在运行中重置,那是留给调试和实验用的后门,本书实验 A 正好用它当道具。
PCR 分工:每个编号管什么
24 个 PCR 不是随便用的,规范给它们分了工:
| PCR | 阶段 | 度量内容 |
|---|---|---|
| 0 | 固件 | BIOS/UEFI 固件代码(CRTM,信任链起点) |
| 1 | 固件 | 固件配置与平台数据 |
| 2 | 固件 | 可选 ROM(如显卡、网卡固件) |
| 3 | 固件 | 可选 ROM 配置 |
| 4 | 固件 | 引导管理器(Bootloader)代码 |
| 5 | 固件 | 引导管理器配置、GPT 分区表 |
| 6 | 固件 | 平台厂商保留 |
| 7 | 固件 | Secure Boot 状态与策略(第八章的主角) |
| 8–9 | 操作系统 | 内核、initramfs、内核命令行等 |
| 10 | 操作系统 | IMA 运行时度量(第九章的主角) |
| 11 | 操作系统 | systemd 系的度量:UKI 各组成部分、系统状态等 |
| 12–15 | 操作系统 | 内核命令行、系统扩展镜像等(具体随发行版而异) |
| 16 | 调试 | 调试用途,运行中可重置(实验 A 的道具) |
| 17–22 | 动态 | 动态信任根(DRTM)相关 |
| 23 | 应用 | 应用程序专用,运行中可重置 |
不需要背。现在只要建立两个印象:PCR 0–7 记录固件阶段发生了什么,8 以后记录操作系统阶段发生了什么。每个实验用到哪个 PCR,到时候再细讲。
坑
一个常见的误解是“TPM 在度量启动过程“。不对——TPM 自己什么都不度量,它只是个忠实的记账员:固件、引导程序、操作系统各自算好哈希,调用 extend 写进 PCR。度量是谁做的、度量了什么、写得对不对,TPM 一概不管。这解释了为什么同一块 TPM 芯片,换个发行版 PCR 内容就完全不同——行为是启动链上的软件决定的,不是芯片决定的。
注意
不同发行版、不同引导方式,往 PCR 里度量的内容和顺序差别很大。这也是第五章实验 C 的价值所在——不看文档,直接从你自己机器的度量日志里把真实过程挖出来。
度量日志:信任链的账本
PCR 里存的是哈希链的“最终结果“,但它有一个天然缺陷:你没法从一个 PCR 值反推出“到底度量了些什么“。好比看到考试总分,却不知道每科成绩。
所以系统在度量的同时,还会把每一条度量记录——度量了什么、算出的哈希是多少、进了哪个 PCR——追加到一份日志里,这就是度量日志(Event Log)。Linux 下它暴露在 /sys/kernel/security/tpm0/binary_bios_measurements。
日志存在普通存储里,理论上可以被篡改——但它改不了 PCR。校验方法很妙:把日志从头到尾“重放“一遍,按 extend 公式逐条计算,如果算出的最终值和 PCR 真实值一致,说明日志没有被改动过;不一致,说明日志在撒谎。PCR 是锚,日志是账本,账本的可信度由锚背书。
这份日志是第五章实验 C 的主角:我们会把它解析出来、逐条重放、和真实 PCR 比对。到时候你会看到,启动那几秒钟里,固件和 systemd 到底往 PCR 里记了些什么。
思考
日志本身要不要保护?不用——这正是这套设计漂亮的地方。篡改日志只会让“重放值 ≠ PCR 值“,反而暴露自己。攻击者唯一能做的是连 PCR 一起骗,而我们前面说过,PCR 骗不了。不可信的存储 + 可信的锚 = 可信的账本,这个思路在远程证明里还会再次出现。
度量启动 vs Secure Boot:监控摄像头与门禁
这是 TPM 学习路上最容易混淆的一对概念,必须掰开揉碎。
Secure Boot(安全启动) 是“门禁“:每一级检查下一级的数字签名,签名不对就拒绝执行。它控制的是“允许什么运行“。固件只放行由可信密钥签名的引导程序,引导程序只放行签名正确的内核。攻击者的恶意代码没有合法签名,直接被挡在门外。
度量启动(Measured Boot) 是“监控摄像头“:不管你是谁、签名对不对,只要你跑过,就把你录下来(度量进 PCR)。它不阻止任何事,只保证留下不可抵赖的记录。
两者解决的是不同层面的问题,而且是互补的:
- Secure Boot 的问题在于“信任是静态的“——签名密钥一旦泄露,或者你自己装了带合法签名但有后门的软件,门禁照开。而且很多 Linux 发行版默认不开 Secure Boot。
- 度量启动的问题在于“只记录不干预“——恶意代码照样跑完了才留下证据,单看这一点似乎没用。
但两者结合,化学反应就发生了:Secure Boot 尽量让坏事不发生,度量启动保证漏网之鱼无所遁形。而 PCR 里这些不可抵赖的记录,配合下面要讲的 Seal 和远程证明,就能变成实实在在的安全能力。
补一句 Linux 世界的现状:Secure Boot 在 Windows 上是默认开启的标配,在 Linux 上却长期是“可选的麻烦“——要自己处理签名密钥、和硬件厂商预置密钥的兼容等问题,所以很多发行版装完默认是关的。与之相对,度量启动只要有 TPM 就天然在工作,不需要任何配置。这意味着大多数 Linux 机器处在“摄像头开着、门禁没装“的状态——这既是现状,也是第八章要解决的遗憾。
思考
一台没开 Secure Boot 的 Linux 机器,度量日志照样在忠实记录一切(本书的实验环境正是如此)。这解释了为什么第八章要专门讨论 Secure Boot——度量再完整,如果引导程序本身可以被随意替换而不受任何约束,攻击者仍有可乘之机。个中细节,第八章展开。
Seal / Unseal:把秘密绑在环境状态上
有了 PCR 这个“不可伪造的环境指纹“,一个极其实用的能力出现了:Seal(密封)。
思路很直白:把一个秘密(比如 LUKS 的磁盘解锁密钥)加密存储,解密的条件不是“知道口令“,而是“PCR 处于指定状态“。也就是说:
只有当系统按照预期的方式、用预期的组件启动到这个状态时,秘密才解得开。启动过程里任何一个环节变了——换了内核、改了命令行、动了引导程序——PCR 值就变了,秘密就永远解不开了。
这个过程叫 Seal(加密绑定)和 Unseal(解封)。校验在 TPM 芯片内部完成:解封时 TPM 比较当前 PCR 和策略要求的值,不一致就直接拒绝,密钥连内存都不会进。
Windows 的 BitLocker 就是这个原理:磁盘密钥被 Seal 在“PCR 0/2/4/7 等寄存器的预期值“上。正常开机,PCR 对得上,自动解锁,你感觉不到 TPM 的存在;有人换了引导程序想套取密码,PCR 对不上,BitLocker 立刻转入恢复模式,要求输入 48 位恢复密钥。
第六章的实验 D,就是要在 Linux 上亲手复刻这套机制。
思考
敏锐的读者这里应该已经发现问题了:内核是要升级的。升级之后 PCR 值变了,秘密不就永远解不开了?确实——这是“把秘密绑死在某几个 PCR 的具体值上“的天然缺陷。工程上有几种解法:升级后重新 Seal、改用对未来 PCR 值的签名授权(PolicyAuthorize,第七章的主题)、或者只绑 Secure Boot 状态而不绑具体度量值。这个话题是 TPM 实践中最核心的权衡,我们先记住问题,第七章再解开它。
远程证明:向另一台机器证明“我是我“
Seal 解决的是“本地解锁“,同一套思路延伸到网络世界,就是远程证明(Remote Attestation)。
场景:一台服务器接入内网前,网络策略服务器想确认“这台机器跑的是不是我们认可的固件、内核和配置,没被改过“。做法是 TPM 用专门的证明密钥(Attestation Key, AK)对当前 PCR 值做一份签名声明——这个操作叫 quote。对方拿到 quote,先验证签名确实来自一颗真实 TPM(AK 的证书链可以追溯到厂商预置的背书密钥),再比对 PCR 值是否符合预期,就知道这台机器的启动链干不干净。配合上一节的度量日志,对方甚至能看到每一个启动组件的具体身份,而不只是“合格/不合格“。
本书不深入远程证明的协议细节(那是进阶之后的路),但你要记住:quote 和 Seal 是同一枚硬币的两面——都是“用 PCR 状态作为信任的证据“。第十章会告诉你接下来怎么学。
注意
远程证明有个绕不开的隐私问题:如果每次 quote 都直接用厂商预置的背书密钥(Endorsement Key, EK)签名,验证方就能把“这颗 TPM“跨场合关联起来,等于给机器发了一张全球追踪的身份证。实际部署里用 AK 加隐私 CA 的间接证明来缓解这一点。知道有这回事就够了,细节属于进阶内容。
密钥层级:种子、主密钥与子密钥
最后讲 TPM 怎么管理密钥。新手常有的疑问是:一颗芯片能存多少个密钥?答案是:基本不用存。
TPM 内部有一组出厂或初始化时生成的种子(Seed),几十字节,永不离开芯片。从种子出发,用确定性算法派生出主密钥(Primary Key)——“确定性“意味着同样的种子每次派生出完全相同的主密钥,所以主密钥不需要保存,用的时候重新派生就行。
主密钥再作为父密钥,加密保护任意数量的子密钥(Child Key)。子密钥的私钥部分被父密钥加密后,可以导出成文件随便存放——离开 TPM 也是密文。要用的时候加载回 TPM,在芯片内部解密、使用,私钥全程不以明文出现在芯片外。
这个“种子 → 主密钥 → 子密钥“的三层结构,让一颗只有几十 KB 存储的芯片能管理理论上无限多的密钥,而且保证了“密钥不出芯片“这条铁律。实验 B 里我们会创建主密钥、在树下挂载 Seal 对象,到时你会对这套机制有手感。
注意
TPM 2.0 里其实不止一颗种子,而是按用途分成了几棵独立的“层级(Hierarchy)“:背书层级(Endorsement Hierarchy,厂商相关,远程证明用)、存储层级(Storage Hierarchy,用户日常使用,实验 B 就用它)、平台层级(Platform Hierarchy,固件专用)。新手阶段只需知道:日常创建密钥时选存储层级即可,不同层级的种子互不相干,Clear(清空)一个层级不会动到另一个。
每个密钥和对象还带有一套**授权(Authorization)**设置:谁可以用它、在什么条件下用。最朴素的是口令授权,最常用的是 PCR 策略授权(“PCR 是这个值才允许用”),最灵活的是第七章要讲的 PolicyAuthorize。授权体系是 TPM 的另一半功力,后面会专门展开。
坑
口令授权有个反直觉的保护机制:字典攻击防护(Dictionary Attack, DA)。连续输错几次口令后,TPM 会进入锁定状态,一段时间内拒绝一切授权尝试——哪怕口令是对的。不少人第一次配置时输错口令,之后怎么试都失败,以为 TPM 坏了,其实只是被锁了,等锁定计时器归零(或重启 TPM)即可。实验章节里如果撞上这个坑,别慌。
最后泼点冷水:TPM 不能做什么
概念地图的最后,划一下边界,避免不切实际的期待:
- TPM 不是杀毒软件。它不扫描、不拦截任何恶意代码的运行,只做度量和保管。
- TPM 很慢。签名、解封都是毫秒到百毫秒级的操作,不适合做高频加解密——磁盘全盘加密仍然是 CPU 干的活,TPM 只负责保管那把密钥。
- TPM 保护不了运行时。恶意代码如果已经在跑,照样能读内存里的明文数据。TPM 管的是“启动时放行不放行密钥“,管不了“放行之后的事“(第九章的 IMA 把这条边界往外推了一步)。
- TPM 防不了拿到你恢复密钥的人。BitLocker 的 48 位恢复密钥一旦泄露,TPM 形同虚设。安全链条的强度永远由最弱一环决定。
- TPM 不替你做决策。PCR 里记了什么、哪些值算“正常“、解封条件怎么设,全是配置问题。配置错了,TPM 会忠实地执行错误的策略。
记住这些边界,后面的实验里你会反复体会到它们。
小结:全书地图
这一章信息量不小,我们回收一下。核心其实就一条主线:TPM 用 PCR 把启动过程变成不可伪造的证据,再用 Seal 和 quote 把这份证据变成“满足条件才给密钥“的实际能力。 其余的——信任链、密钥层级、授权——都是围绕这条主线的支撑结构。
这些概念会在后面的章节里逐个变成你亲手跑过的命令:
| 概念 | 对应章节 | 你将亲手做的事 |
|---|---|---|
| PCR 哈希链 | 第三章 · 实验 A | 手动 extend PCR,验证顺序敏感和不可逆 |
| Seal / Unseal | 第四章 · 实验 B | 把秘密绑到 PCR,模拟篡改后解封失败 |
| 信任链的度量记录 | 第五章 · 实验 C | 解析固件度量日志,重放并与真实 PCR 比对 |
| Seal 的实际应用 | 第六章 · 实验 D | LUKS 密钥绑 TPM,开机自动解锁 |
| PCR Policy / 授权 | 第七章 | 为什么“绑死 PCR“不够用,PolicyAuthorize 怎么解 |
| Secure Boot 的意义 | 第八章 | Evil Maid 攻击:PCR 7 的盲区与对策 |
| IMA 运行时度量 | 第九章 | PCR 10:度量延伸到文件执行层面 |
下一章我们先把实验环境搭起来——一台带软件 TPM 的虚拟机,玩坏了快照回滚。之后就可以放心大胆地动手了。