3. 安全启动概述

各位同学,咱们今天聊聊安全启动。说实话,这个主题在TEE领域里,属于那种「听着简单,做起来全是坑」的东西。我当年刚接触TEE时,觉得安全启动不就是校验一下签名嘛,有什么难的?结果第一次在ARM平台上做实际部署,就被硬件OTP(一次性可编程)存储给坑了一把——嗯,这个后面细说。

3.1 安全启动到底在解决什么问题?

先问大家一个问题:你的设备上电后,CPU执行的第一条指令,你怎么保证它是可信的?

你可能会说:「代码是我自己写的,当然可信。」但现实是,攻击者可以物理篡改Flash,或者通过JTAG/SWD接口注入恶意代码。一旦Boot阶段被攻破,后续的TEE、OS、应用全部沦陷。

所以,安全启动的核心目标就三个:

  • 完整性验证:确保每一阶段加载的镜像没有被篡改
  • 来源认证:确保镜像来自合法的发布者(通常是OEM或芯片厂商)
  • 防回滚:防止攻击者加载有漏洞的旧版本镜像

我个人习惯把安全启动比作「接力赛」——每一棒(启动阶段)都要验证下一棒的身份,任何一棒出问题,整个比赛就输了。

核心要点:安全启动不是单一的技术,而是一条完整的信任链。链上的每个环节都必须被保护,否则就是「木桶效应」——最短的那块板决定了整体安全性。

3.2 信任链的建立:从硬件到OS的层层校验

信任链(Chain of Trust)是怎么建立的呢?我画了一张图,大家一看就明白:

安全启动信任链建立流程 硬件RoT BootROM(不可篡改) 验证 Bootloader 校验签名 & 哈希 验证 TEE / Rich OS 加载 & 运行 运行 App 信任链:每一级验证下一级的完整性和来源 ⚠ 防回滚机制 每个镜像携带版本号,Bootloader检查版本号 ≥ 当前已记录的最小版本 信任根(RoT)是整个链条的基石 BootROM中的公钥哈希是硬件固化的,无法通过软件修改

这张图展示了典型的信任链流程。从BootROM开始,到Bootloader,再到TEE/OS,最后到应用。每一级都负责验证下一级的签名和完整性。

我在项目中遇到过一种情况:某厂商为了省成本,把Bootloader的签名校验做成了「可选」——结果被攻击者直接跳过了校验,加载了恶意镜像。你想想看,这跟没锁门有什么区别?

实战建议:信任链的每一级校验都必须是强制的,不能有「调试模式」或「跳过校验」的后门。如果开发阶段需要调试,请使用独立的调试证书,并在量产固件中彻底移除调试入口。

3.3 Root of Trust(RoT):信任链的基石

信任根(RoT)是什么?说白了,就是整个安全启动体系里「第一个被信任的实体」。它必须是不可篡改的、不可绕过的。

常见的RoT实现方式有几种:

RoT类型 实现方式 优缺点
硬件RoT BootROM固化公钥哈希
(如ARM TrustZone、Intel Boot Guard)
✅ 安全性最高,无法篡改
❌ 灵活性差,更新需换芯片
软件RoT 使用eFuse/OTP存储公钥哈希
(一次性可编程)
✅ 可在线烧录,灵活性好
❌ 需要物理安全保护
混合RoT 硬件RoT + 软件RoT组合
(如Google Pixel的Titan芯片)
✅ 兼顾安全与灵活性
❌ 成本较高

我个人比较推荐混合RoT方案。为什么呢?因为纯硬件RoT虽然安全,但一旦公钥需要更新(比如密钥泄露),你就得召回所有设备——这成本谁受得了?而混合方案可以在硬件保护下,通过安全通道更新软件RoT。

⚠ 避坑指南:我曾经在一个IoT项目中,看到团队把RoT的公钥直接编译在Bootloader里。结果呢?攻击者通过漏洞读取了公钥,然后用自己的私钥签名了恶意镜像。记住:RoT里存的应该是公钥的哈希,而不是公钥本身!哈希值无法反推出公钥,攻击者拿到也没用。

3.4 安全启动的典型实现流程

咱们来看一个实际的安全启动代码流程(伪代码):

// 安全启动核心流程(伪代码)
void secure_boot() {
    // 1. 从BootROM开始(硬件RoT)
    hash_t expected = read_otp_hash();  // 从OTP读取期望的公钥哈希
    
    // 2. 加载Bootloader镜像
    bootloader_img_t bl = load_from_flash(BOOTLOADER_ADDR);
    
    // 3. 验证Bootloader签名
    if (!verify_signature(bl.pub_key, bl.signature, bl.data)) {
        panic("Bootloader签名验证失败!");
    }
    
    // 4. 验证公钥哈希是否匹配RoT
    hash_t actual = sha256(bl.pub_key);
    if (memcmp(expected, actual, HASH_SIZE) != 0) {
        panic("公钥哈希不匹配!可能被篡改");
    }
    
    // 5. 检查版本号(防回滚)
    if (bl.version < read_min_version()) {
        panic("版本过低,拒绝启动");
    }
    
    // 6. 跳转到Bootloader
    jump_to(bl.entry_point);
}

这段代码看起来简单,但实际部署时有很多细节要注意。比如:

  • OTP读取失败处理:如果OTP没烧录过,应该直接拒绝启动,而不是用默认值
  • 签名算法选择:建议使用ECDSA(椭圆曲线签名),RSA-2048虽然也行,但性能差一些
  • 哈希碰撞防护:使用SHA-256或更高,别用MD5/SHA-1——这些已经被证明不安全了

关键提醒:安全启动的「安全」二字,依赖于整个链条中最弱的一环。如果你的BootROM有漏洞(比如允许跳过校验),那后面做得再好也没用。所以,选择芯片时一定要确认其BootROM是否经过安全审计。

3.5 实际部署中的常见问题

最后,分享几个我在项目中踩过的坑:

  1. 密钥管理混乱:开发密钥、测试密钥、生产密钥混用。结果测试密钥泄露,攻击者直接签了恶意镜像。建议:严格区分密钥环境,生产密钥必须存储在HSM(硬件安全模块)中。
  2. 回滚保护没做好:只校验了签名,没检查版本号。攻击者可以刷回有漏洞的旧版本。建议:在OTP或安全存储中记录最小允许版本号。
  3. 调试接口没关闭:量产设备还留着JTAG/SWD接口。攻击者直接通过调试接口读取了RoT密钥。建议:量产前熔断调试接口的保险丝。

嗯,这些坑说起来都是泪。但反过来想,踩过坑之后,你对安全启动的理解会更深刻。