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 密钥体系的层级关系

这四类密钥不是平级的。它们形成一个严格的层级信任链:

  1. PK 验证 KEK 的更新
  2. KEK 验证 db 和 dbx 的更新
  3. db 验证启动程序的签名
  4. dbx 覆盖 db 的信任,禁止特定签名

换句话说,PK是「信任的根」,KEK是「信任的传递者」,db和dbx是「信任的执行者」。这个链条一旦建立,任何一环的破坏都会导致整个安全启动失效。

核心原则:信任只能向下传递,不能反向。KEK不能修改PK,db不能修改KEK。这是安全启动防篡改的基础。

安全启动密钥体系层级关系 平台密钥 (PK) 信任的根 密钥交换密钥 (KEK) 信任的传递者 签名数据库 (db) 白名单 禁止签名数据库 (dbx) 黑名单 启动程序 (Boot Loader) 验证更新 验证更新 验证更新 允许加载 禁止加载 说明:箭头方向表示信任验证方向。PK验证KEK,KEK验证db/dbx,db/dbx验证启动程序。

4.6 实际部署中的注意事项

在实际项目中,配置这四类密钥有几个容易踩的坑:

  • PK不能为空:安全启动开启后,PK必须存在。如果PK被清空,安全启动会自动进入「设置模式」,此时任何人都可以修改KEK和db。这通常是攻击者的突破口。
  • KEK不要只放一个:我建议至少放两个KEK,一个主用,一个备用。主用KEK用于日常更新,备用KEK放在安全的地方,防止主用证书意外吊销或过期。
  • dbx更新要谨慎:一旦把某个签名加入dbx,所有使用该签名的程序都无法启动。我曾经见过有人误把Microsoft的根证书加入dbx,结果整个Windows都启动不了……嗯,那台设备只能返厂重刷固件。
  • 签名数据库大小有限:NVRAM空间是有限的。db和dbx不能无限添加条目。我建议定期清理无效或过期的签名,避免空间耗尽。

个人经验:在量产前,一定要做一次「PK丢失模拟测试」。把PK私钥故意销毁,然后尝试更新KEK——如果还能更新,说明你的安全启动实现有问题。这个测试我每次做项目都会跑一遍,能发现不少隐藏的漏洞。