2、硬件信任根(RoT):基于eFuse/OTP的根密钥存储,物理不可克隆函数(PUF)原理

好,咱们接着聊。上一章我们把安全启动的整个链路讲清楚了,这一章要深入到底层——硬件信任根。说白了,就是你的系统凭什么相信自己是“干净”的?这个信任的起点,就在硬件里。

我经常跟团队里的小朋友说:软件层面的安全,最终都要依赖硬件来兜底。你想想看,如果攻击者能直接读出你的私钥,那加密算法再强也没用。所以,硬件信任根就是整个安全体系的“地基”。

2.1 什么是硬件信任根?

硬件信任根,英文叫 Root of Trust,简称 RoT。它是一个绝对可信的、不可篡改的硬件实体。系统启动时,最先执行的就是 RoT 里的代码,它负责验证下一级固件的签名。只要 RoT 是安全的,整个信任链就能建立起来。

我在一个消防报警项目里就吃过亏。当时用的是一颗通用 MCU,没有硬件 RoT,全靠软件做校验。结果呢?攻击者通过调试接口直接把 bootloader 给替换了,整机变成了“肉鸡”。从那以后,我选型的第一条铁律就是:必须有硬件信任根

核心要点:硬件信任根必须满足三个条件——

  • 不可篡改:出厂后无法修改
  • 不可读取:密钥不能被软件读出
  • 不可绕过:系统启动必须经过它

2.2 eFuse/OTP:一次性可编程存储

实现 RoT 最常用的技术就是 eFuse 和 OTP。这两个东西原理差不多,都是一次性可编程的存储单元。出厂时所有位都是“0”,你烧写一次后,某些位变成“1”,就再也改不回来了。

eFuse 这个名字挺形象的——就像电路里的一根保险丝。烧写时通过大电流把“熔丝”熔断,形成开路。OTP 则是用浮栅晶体管,击穿氧化层后电荷就 trapped 在里面了。两种方式各有优劣,但目的都一样:写入即锁定

2.2.1 存储什么?

eFuse/OTP 里一般存这些东西:

  • 根密钥:用于验签的公钥哈希,或者对称加密的根密钥
  • 芯片唯一ID:每颗芯片独一无二的序列号
  • 安全配置:比如调试接口是否锁定、安全启动是否使能
  • 生命周期状态:芯片处于开发阶段还是量产阶段

我习惯把公钥的哈希值存在 OTP 里。为什么不是直接存公钥?因为公钥太长,OTP 空间很宝贵。存哈希值,然后让 bootloader 去校验公钥的完整性,这样更灵活。

2.2.2 实际项目中的坑

我曾经踩过一个坑:某次量产时,发现一批芯片的 OTP 烧写失败了。查了半天,原来是烧写时序没留够余量。OTP 烧写对电压和温度很敏感,量产时一定要做回读校验。烧完立刻读出来比对,不对就报废,别想着“也许能用”。

另外,OTP 的容量通常很小,一般就 1Kb 到 16Kb。所以设计时要精打细算,别什么都往里塞。我见过有人把整个 bootloader 都存 OTP 里,结果空间不够,最后不得不重新流片……

2.3 物理不可克隆函数(PUF)

讲完 eFuse,咱们聊聊更高级的东西——PUF。这玩意儿我第一次接触时觉得挺玄乎的,后来做深了才发现,它其实很“物理”。

PUF 的全称是 Physical Unclonable Function。它的核心思想是:利用芯片制造过程中不可避免的工艺偏差,生成一个独一无二的“指纹”。每颗芯片的硅晶体结构都有微小的差异,这些差异会导致电路行为略有不同。PUF 就是把这些差异提取出来,变成一个密钥。

你想想看,eFuse 存的密钥是固定的,如果攻击者用电子显微镜去读,理论上能读出来。但 PUF 的密钥是“活”的——它只在需要时生成,用完后就不存在了。攻击者就算拿到芯片,也找不到密钥存在哪里。

2.3.1 PUF 的几种实现方式

类型 原理 优点 缺点
SRAM PUF 利用 SRAM 上电时随机初始状态 无需额外电路,成本低 受温度电压影响大,需要纠错
环形振荡器 PUF 利用振荡器频率的工艺偏差 稳定性较好 面积较大,功耗高
仲裁器 PUF 利用路径延迟差异 响应速度快 易受建模攻击
蝴蝶型 PUF 利用交叉耦合门的不稳定态 可靠性高 设计复杂

在消防报警系统里,我推荐用 SRAM PUF。为什么?因为很多 MCU 内部本来就有 SRAM,不需要额外硬件。你只要在 bootloader 里加一段代码,上电时读取 SRAM 的初始值,就能生成一个 128 位的密钥。

个人经验:SRAM PUF 有个问题——它生成的密钥不是 100% 稳定的。温度从 -40°C 变到 85°C,可能会有几位翻转。所以一定要配合模糊提取器(Fuzzy Extractor)来做纠错。我一般用 BCH 码,能纠 8 位错误就够用了。

2.3.2 PUF 的注册与重构

PUF 的使用分两步:

  1. 注册(Enrollment):在安全环境下,读取 PUF 的原始响应,生成辅助数据(Helper Data)。这个辅助数据可以公开存储,但不要存在 OTP 里——浪费空间。
  2. 重构(Reconstruction):每次上电时,重新读取 PUF 响应,结合辅助数据,恢复出原始密钥。

代码示例(伪代码,实际项目里用 C 实现):

// 注册阶段
uint8_t puf_response[16];   // 128位原始响应
uint8_t helper_data[16];    // 辅助数据
uint8_t secret_key[16];     // 最终密钥

puf_read(puf_response);                     // 读取PUF
fuzzy_extractor_gen(puf_response, helper_data, secret_key);
store_to_flash(helper_data);                // 存到Flash

// 重构阶段
uint8_t puf_response_new[16];
uint8_t secret_key_recovered[16];

puf_read(puf_response_new);                 // 再次读取
fuzzy_extractor_rep(puf_response_new, helper_data, secret_key_recovered);
// 现在 secret_key_recovered 应该等于 secret_key

2.4 eFuse vs PUF:怎么选?

这个问题我经常被问到。我的建议是:

  • 成本敏感、对安全要求中等:用 eFuse/OTP。简单可靠,技术成熟。
  • 高安全要求、防物理攻击:用 PUF。密钥不留痕迹,抗逆向工程。
  • 最稳妥的方案:两者结合。用 eFuse 存根公钥,用 PUF 生成设备私钥。

我在一个高端消防主机项目里就是这么干的。eFuse 里存了 CA 的公钥哈希,用来验签固件。PUF 生成设备自己的私钥,用来和服务器做双向认证。这样即使攻击者拆了芯片,也拿不到私钥——因为私钥根本不存在于任何非易失存储里。

总结一下:

  • 硬件信任根是安全启动的基石
  • eFuse/OTP 适合存固定密钥,但怕物理攻击
  • PUF 利用工艺偏差生成密钥,抗物理提取
  • 实际项目中,建议 eFuse + PUF 组合使用

嗯,这一章就到这里。下一章我们讲安全启动的具体实现——从 bootrom 到 bootloader 的签名验证流程。到时候我会拿一个实际的消防报警主板来拆解,你们会看到代码是怎么跑起来的。