2、安全启动原理:信任链模型、根信任建立、签名验证流程、硬件不可变信任根
安全启动,说白了就是解决一个核心问题:你怎么知道你启动的代码是可信的?
我早年做第一代TEE方案时,客户问了个很尖锐的问题:“我把手机放桌上喝杯咖啡,回来你怎么保证系统没被人动过?” 当时我支支吾吾答不上来。后来才明白,安全启动不是靠某个单一技术,而是靠一套环环相扣的信任传递机制。
这一章,我们就来拆解这套机制。你理解了它,后面做性能调优才有方向感。
2.1 信任链模型:从“根”到“叶”的逐级验证
信任链模型,你可以想象成一场接力赛。
第一棒是硬件,它天生可信。第二棒是BootROM,它由硬件验证。第三棒是Bootloader,它由BootROM验证。第四棒是OS内核,它由Bootloader验证。每一棒都检查下一棒的签名,确认无误后才把控制权交出去。
这个链条一旦断了,系统就拒绝启动。嗯,就是这么简单粗暴。
核心原则:信任不能凭空产生,只能传递。每一级只信任它的上一级,而上一级必须通过签名验证来证明下一级的合法性。
我见过不少团队在移植TEE时,为了省时间跳过某个环节的验证。结果呢?测试环境跑得欢,一上生产环境就被攻破。后来他们花了两倍的时间回来补这个坑。
典型的信任链层级如下:
| 层级 | 组件 | 存储位置 | 验证者 |
|---|---|---|---|
| 0 | 硬件不可变信任根 (RoT) | 芯片内部熔丝/ROM | 物理不可篡改 |
| 1 | BootROM | 芯片内部ROM | 硬件RoT |
| 2 | 一级Bootloader (SPL) | 片上SRAM | BootROM |
| 3 | 二级Bootloader (U-Boot) | DDR | SPL |
| 4 | TEE OS / 内核 | DDR | U-Boot |
你想想看,如果攻击者想篡改OS内核,他得先过Bootloader这一关。想过Bootloader?得先过BootROM。想过BootROM?对不起,BootROM在芯片出厂时就写死了,改不了。这就是信任链的威力。
2.2 根信任建立:硬件不可变信任根
信任链的起点,就是硬件不可变信任根。它通常由两部分组成:
- 不可变代码:BootROM,固化在芯片内部ROM中,出厂后无法修改。
- 不可变密钥:根公钥,存储在芯片的熔丝(eFuse)或一次性可编程(OTP)存储器中。
我个人习惯把根信任比作“芯片的出生证明”。它是在芯片制造时写进去的,谁也没法改。攻击者就算把整个Flash都换了,只要芯片一上电,BootROM还是那个BootROM,根公钥还是那个根公钥。
注意:根公钥的存储是安全启动的命门。如果攻击者能物理篡改熔丝值,整个信任链就崩塌了。所以,高端芯片会采用主动防护层(Active Shield)来检测物理探针攻击。我在项目中遇到过客户要求做激光攻击测试,结果熔丝区域被激光照了10秒就报警自毁——虽然极端,但确实有效。
根信任的建立过程,其实就三步:
- 芯片上电:CPU从固定地址取指,执行BootROM。
- 读取根公钥:BootROM从OTP中读取根公钥。
- 准备验证:BootROM用根公钥去验证下一级镜像(通常是SPL或直接是Bootloader)的签名。
这里有个细节:BootROM本身不需要被验证,因为它不可变。它天生就是可信的。这就是“硬件不可变”的真正含义。
2.3 签名验证流程:从镜像到签名的完整校验
签名验证,是整个安全启动中最耗时的环节。也是我们做性能调优的主战场。
一个标准的签名验证流程,包含以下步骤:
- 加载镜像:从Flash中读取待验证的镜像(如Bootloader、TEE OS)。
- 加载签名:读取镜像对应的签名数据(通常附加在镜像尾部或单独存储)。
- 计算哈希:对镜像内容计算哈希值(如SHA-256)。
- 验签:用上一级提供的公钥,对签名进行非对称解密(如RSA-2048),得到解密后的哈希值。
- 比对:将计算出的哈希值与解密后的哈希值进行比对。一致则通过,不一致则拒绝启动。
代码层面,验签的核心逻辑大致如下:
// 伪代码:签名验证流程
int verify_image(uint8_t *image, uint32_t image_len,
uint8_t *signature, uint32_t sig_len,
uint8_t *pubkey, uint32_t key_len) {
uint8_t hash[SHA256_DIGEST_SIZE];
// 1. 计算镜像哈希
sha256(image, image_len, hash);
// 2. RSA验签:用公钥解密签名,得到原始哈希
uint8_t decrypted_hash[SHA256_DIGEST_SIZE];
rsa_verify(pubkey, key_len, signature, sig_len, decrypted_hash);
// 3. 比对哈希
if (memcmp(hash, decrypted_hash, SHA256_DIGEST_SIZE) == 0) {
return SUCCESS; // 验证通过
} else {
return FAILURE; // 验证失败
}
}
我曾经在一个项目中,发现验签耗时占了整个启动时间的40%。原因很简单:RSA-2048验签在软件实现下太慢了。后来我们换成了硬件加速器,直接把验签时间从200ms降到了5ms。嗯,这就是性能调优的典型场景。
调优提示:验签的瓶颈通常不在哈希计算(SHA-256硬件加速很容易做到微秒级),而在非对称解密(RSA/ECC)。如果条件允许,优先使用硬件密码引擎。如果只能用软件,考虑使用ECC(椭圆曲线)替代RSA,同等安全强度下ECC的验签速度更快。
2.4 信任链的完整流程图
下面这张图,把整个信任链的启动流程串起来了。你仔细看,每个箭头都代表一次签名验证。
2.5 避坑指南:信任链常见问题
做安全启动这么多年,我踩过不少坑。挑几个典型的说说:
- 签名密钥泄露:我曾经见过一个团队把签名私钥直接放在代码仓库里。结果被内部员工泄露,整个产品线的安全启动形同虚设。记住:私钥必须离线存储,专人管理。
- 哈希算法过时:有些老方案还在用SHA-1。SHA-1已经被证明存在碰撞攻击。我建议至少用SHA-256,有条件上SHA-384。
- 验签顺序颠倒:有个项目把验签放在了镜像加载之后、执行之前。但镜像加载过程中没有做完整性校验,攻击者可以在加载时篡改数据。正确的做法是:加载完成后立即验签,中间不能有任何可被利用的时间窗口。
- 忽略回滚攻击:攻击者可能不篡改镜像,而是把镜像替换成有漏洞的旧版本。所以,安全启动还需要配合版本号检查(anti-rollback)。
重要提醒:安全启动不是银弹。它只能保证启动时的镜像完整性,无法防御运行时攻击。启动完成后,TEE还需要依赖内存隔离、安全中断等机制来保护自身。别以为安全启动搞定了就万事大吉。