4. 安全启动的密钥体系:PK、KEK、db、dbx
安全启动的核心,说白了就是一套「谁可以信任」的规则。而规则本身,是靠四类密钥来执行的。我刚开始接触UEFI时,总觉得这四类密钥绕来绕去,分不清谁管谁。后来在项目里亲手配了一遍,才真正理解它们的关系。
这四类密钥分别是:平台密钥(PK)、密钥交换密钥(KEK)、签名数据库(db)、禁止签名数据库(dbx)。它们存储在固件的NVRAM变量里,形成一个层级信任链。
一句话总结:PK是最高权限,KEK负责管理签名数据库,db放白名单,dbx放黑名单。启动时,固件只允许运行db里签过名、且不在dbx里的程序。
4.1 平台密钥(PK)—— 整个体系的根
PK是整个安全启动的信任锚点。它由平台所有者(通常是OEM或最终用户)持有私钥,公钥被烧录到固件中。只有用PK私钥签名的更新,才能修改PK本身或KEK。
我个人习惯把PK比作「保险柜的总钥匙」。丢了它,整个安全启动体系就失控了。我在一个量产项目里遇到过,某厂商把PK私钥存在一个不加密的共享文件夹里……嗯,后来他们紧急召回了所有设备。
| 属性 | 说明 |
|---|---|
| 存储位置 | UEFI NVRAM 变量 PK |
| 签名算法 | RSA-2048 或更强(通常搭配 SHA-256) |
| 可修改者 | 仅当前 PK 私钥持有者 |
| 作用范围 | 验证 KEK 更新、PK 自身更新 |
警告:PK一旦被设置,安全启动就进入「用户模式」。如果PK私钥丢失,你将永远无法修改KEK或db。我曾经帮一个客户恢复过这种状态的设备——唯一的办法是物理擦除SPI Flash,重新烧录固件。
4.2 密钥交换密钥(KEK)—— 数据库的管理员
KEK的作用是验证db和dbx的更新。你可以有多个KEK,每个KEK对应一个授权实体。比如,Microsoft有一个KEK,OEM自己也可以有一个。
为什么需要KEK?你想想看,如果每次更新db都要用PK签名,那PK私钥就得频繁使用,风险太高。KEK相当于一个「中间代理」——PK只用来信任KEK,KEK再去管理具体的签名数据库。
我记得在给一家服务器厂商做方案时,他们要求同时支持Microsoft的补丁和自研的驱动签名。解决方案就是:在KEK列表里同时放入Microsoft KEK和厂商自己的KEK。这样两边都能独立更新db,互不干扰。
小技巧:生产环境中,建议至少保留两个KEK——一个用于日常更新,另一个作为备份。我曾经见过只放一个KEK的设备,结果那个KEK证书过期后,整个安全启动策略就锁死了。
4.3 签名数据库(db)—— 白名单
db里存放的是被信任的签名信息。这些签名可以是:
- 证书(Certificate)—— 信任该CA签发的所有程序
- 文件哈希(SHA-256 Hash)—— 只信任特定版本的特定文件
- 签名列表(Signature List)—— 混合上述两种
启动时,固件会检查每个启动项(boot loader、OS loader)的签名是否匹配db中的某个条目。匹配则放行,不匹配则拒绝启动。
这里有个坑:db条目是有顺序的。固件会按顺序匹配,匹配到第一个就停止。我遇到过一个问题:某驱动签名明明在db里,但启动时总报错。查了半天发现,db里先放了一个过期证书,固件匹配到它就停了,根本没往后看。
// db 条目示例(伪代码)
EFI_SIGNATURE_LIST {
SignatureType = EFI_CERT_X509_GUID // 证书类型
SignatureListSize = 2048
SignatureHeaderSize = 0
SignatureSize = 1024
// 第一个签名:Microsoft Windows Production PCA 2011
// 第二个签名:OEM 自签名证书
// 第三个签名:特定 bootloader 的 SHA-256 哈希
}
4.4 禁止签名数据库(dbx)—— 黑名单
dbx和db正好相反。它存放的是被明确禁止的签名。如果一个程序的签名在dbx里,即使它也在db里,固件也会拒绝加载。
dbx的优先级高于db。这个设计很关键——当发现某个证书或哈希有安全漏洞时,可以立即把它加入dbx,而不需要修改db。比如,某个CA被攻破了,你只需要把它的证书加入dbx,所有由它签发的恶意程序就都无法启动了。
我曾经处理过一个案例:某款UEFI固件被植入Bootkit,攻击者利用一个过期的db条目签发了恶意驱动。修复方案就是:把那个过期证书的哈希加入dbx,然后发布固件更新。这样即使攻击者持有那个证书,也无法再通过安全启动。
| 对比项 | db | dbx |
|---|---|---|
| 作用 | 允许加载 | 禁止加载 |
| 匹配逻辑 | 匹配任一即放行 | 匹配任一即阻止 |
| 优先级 | 低(dbx优先检查) | 高(先于db检查) |
| 更新频率 | 较高(添加新驱动/OS) | 较低(仅安全事件时) |
4.5 密钥体系的层级关系
这四类密钥不是平级的。它们形成一个严格的层级信任链:
- PK 验证 KEK 的更新
- KEK 验证 db 和 dbx 的更新
- db 验证启动程序的签名
- dbx 覆盖 db 的信任,禁止特定签名
换句话说,PK是「信任的根」,KEK是「信任的传递者」,db和dbx是「信任的执行者」。这个链条一旦建立,任何一环的破坏都会导致整个安全启动失效。
核心原则:信任只能向下传递,不能反向。KEK不能修改PK,db不能修改KEK。这是安全启动防篡改的基础。
4.6 实际部署中的注意事项
在实际项目中,配置这四类密钥有几个容易踩的坑:
- PK不能为空:安全启动开启后,PK必须存在。如果PK被清空,安全启动会自动进入「设置模式」,此时任何人都可以修改KEK和db。这通常是攻击者的突破口。
- KEK不要只放一个:我建议至少放两个KEK,一个主用,一个备用。主用KEK用于日常更新,备用KEK放在安全的地方,防止主用证书意外吊销或过期。
- dbx更新要谨慎:一旦把某个签名加入dbx,所有使用该签名的程序都无法启动。我曾经见过有人误把Microsoft的根证书加入dbx,结果整个Windows都启动不了……嗯,那台设备只能返厂重刷固件。
- 签名数据库大小有限:NVRAM空间是有限的。db和dbx不能无限添加条目。我建议定期清理无效或过期的签名,避免空间耗尽。
个人经验:在量产前,一定要做一次「PK丢失模拟测试」。把PK私钥故意销毁,然后尝试更新KEK——如果还能更新,说明你的安全启动实现有问题。这个测试我每次做项目都会跑一遍,能发现不少隐藏的漏洞。