4、硬件基础:eFuse与OTP原理、如何利用eFuse实现版本锁定

讲防回滚,绕不开两个硬件基础:eFuse 和 OTP。说白了,它们就是芯片上的一次性可编程存储单元。你想想看,如果攻击者能随便改写固件版本号,那防回滚就是个笑话。所以,我们需要一种「烧进去就改不了」的机制。

我个人习惯把 eFuse 和 OTP 比作芯片上的「熔断式保险丝」。烧断一根,就再也接不回去了。这个特性,恰好可以用来锁定版本号。

4.1 eFuse 的工作原理

eFuse 这个词,其实是「电子保险丝」的缩写。它最早是 IBM 搞出来的技术。原理很简单:

  • 芯片内部有一小段金属连线(通常是铜或硅化物的导线)
  • 正常状态下,这段导线是导通的,代表逻辑「1」
  • 通过施加一个较大的电流(比如 10-30mA),导线会熔断
  • 熔断后,该位变成开路状态,代表逻辑「0」

嗯,这里要注意:熔断是不可逆的。你不可能把断掉的导线再接回去。这就是 eFuse 实现「一次编程」的物理基础。

关键点: eFuse 的编程是物理层面的破坏。不是像 Flash 那样通过电子隧穿擦写。所以,任何试图恢复 eFuse 状态的操作,都需要在显微镜下做芯片级逆向工程——成本极高,成功率极低。

我在项目中遇到过一件事:有个客户想用 eFuse 存储产品序列号,结果发现编程电流没控制好,把相邻的位也熔断了。后来我们加了一个电流限制电路,才解决这个问题。所以,eFuse 编程的时序和电流参数,一定要严格按照芯片手册来。

4.2 OTP 存储器的原理

OTP 是 One-Time Programmable 的缩写。它和 eFuse 本质上是同一类东西,但实现方式更多样:

类型 编程方式 典型容量 常见用途
eFuse 电流熔断金属线 1-64 Kb 密钥存储、芯片ID、版本锁定
反熔丝 OTP 高压击穿栅氧化层 1-256 Kb 配置存储、校准参数
一次性 EPROM 紫外/电编程 1-512 Kb 早期 MCU 代码存储
一次性 Flash 特殊工艺禁止擦除 1-2 Mb 低成本代码存储

目前嵌入式设备里,最常用的是 eFuse 和反熔丝 OTP。它们都集成在 SoC 内部,不需要外部存储芯片。

我的经验: 选型时别只看容量。eFuse 的编程电压通常需要外部提供(比如 1.8V/3.3V 之外的专用编程引脚),而反熔丝 OTP 往往可以用芯片内部电荷泵升压。如果 PCB 空间紧张,后者更方便。

4.3 如何用 eFuse 实现版本锁定

好了,原理讲完了。现在说说实战——怎么用 eFuse 锁住固件版本号。

基本思路是这样的:

  1. 在芯片的 eFuse 区域,划出若干位作为「版本号寄存器」
  2. 每次固件升级时,Bootloader 检查新版本号是否大于当前版本号
  3. 如果合法,Bootloader 将新版本号写入 eFuse
  4. 一旦写入,该版本号就永久生效,无法回退

举个例子。假设我们用 8 位 eFuse 来表示版本号:

// 伪代码:eFuse 版本锁定流程
#define EFUSE_VERSION_ADDR  0x1A000000
#define CURRENT_VERSION     (*((volatile uint8_t*)EFUSE_VERSION_ADDR))

int check_and_update_firmware(uint8_t new_version) {
    // 1. 读取当前 eFuse 中的版本号
    uint8_t current_ver = CURRENT_VERSION;
    
    // 2. 检查新版本是否合法
    if (new_version <= current_ver) {
        return ERROR_ROLLBACK_DETECTED;  // 回滚攻击!
    }
    
    // 3. 验证新固件的签名(省略具体实现)
    if (!verify_firmware_signature()) {
        return ERROR_SIGNATURE_INVALID;
    }
    
    // 4. 写入新版本号到 eFuse
    write_efuse_byte(EFUSE_VERSION_ADDR, new_version);
    
    // 5. 写入后立即回读验证
    if (CURRENT_VERSION != new_version) {
        return ERROR_EFUSE_WRITE_FAILED;
    }
    
    return SUCCESS;
}

注意: eFuse 编程通常是一次性写入多个位。你不能只改其中一位而不影响其他位。所以,版本号字段要单独分配一个 eFuse 区域,不要和其他配置混在一起。我曾经见过一个产品,把版本号和密钥放在同一个 eFuse 字里,结果升级时把密钥也冲掉了——那叫一个惨。

4.4 版本锁定的安全边界

用 eFuse 锁版本,听起来很完美。但实际部署时,有几个坑要避开:

  • 版本号位数要留够:8 位最多支持 256 个版本。如果产品生命周期长,建议用 16 位或 32 位。
  • 不要用 0x00 或 0xFF 作为初始值:未编程的 eFuse 位可能是全 0 或全 1(取决于工艺)。如果初始版本号恰好是 0x00,那第一个版本就写不进去了。
  • 考虑 eFuse 的可靠性:eFuse 编程有成功率问题。写入后一定要回读验证,必要时可以写冗余位。

我建议: 在量产阶段,把 eFuse 版本号的初始值设为 0x01。这样,第一个正式固件版本从 0x02 开始。留出 0x01 作为工厂测试版本,方便产线调试。

4.5 eFuse 与版本锁定的整体流程

下面这张图,把整个流程串起来了。从芯片出厂到固件升级,每一步都离不开 eFuse 的参与。

eFuse 版本锁定流程图 eFuse 版本锁定流程图 芯片出厂 eFuse 初始状态(全 0/全 1) 工厂烧录初始版本号(0x01) Bootloader 检查版本号 新版本 > 当前版本? 是 写入新版本到 eFuse 否 拒绝升级(回滚检测) 说明:eFuse 写入后不可逆。即使攻击者物理拆解芯片,也无法将已熔断的 eFuse 恢复。 因此,版本号一旦锁定,固件只能升级,不能降级。

这张图展示了从芯片出厂到固件升级的完整链路。你注意看那个菱形判断框——这是防回滚的核心决策点。只有新版本号大于当前 eFuse 中存储的版本号,升级才能继续。

4.6 实际部署中的注意事项

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

  • eFuse 编程次数有限:虽然叫一次性编程,但有些芯片支持按位编程(比如一次写 1 位,分多次写完)。不过,频繁编程会降低 eFuse 的可靠性。我建议每个版本只写一次。
  • 考虑 eFuse 的寿命:eFuse 熔断后,金属残留物可能会在高温下迁移,导致短路。虽然概率很低,但在高可靠性产品(比如汽车电子)中,建议使用冗余设计。
  • 不要依赖单一防护:eFuse 版本锁定只是防回滚的一环。还要配合签名验证、安全启动、JTAG 锁定等措施。单一防护点被攻破,整个防线就垮了。

警告: 有些芯片的 eFuse 区域可以通过特殊调试接口(比如 JTAG、SWD)读取或改写。量产时一定要禁用这些接口。否则,攻击者可以绕过 Bootloader,直接修改 eFuse 内容。我见过一个案例,就是因为 JTAG 没锁,导致整个防回滚机制形同虚设。

好了,eFuse 和 OTP 的原理、以及如何用它们实现版本锁定,就讲到这里。记住一句话:硬件锁死,软件验证,两者缺一不可。