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 的使用分两步:
- 注册(Enrollment):在安全环境下,读取 PUF 的原始响应,生成辅助数据(Helper Data)。这个辅助数据可以公开存储,但不要存在 OTP 里——浪费空间。
- 重构(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 的签名验证流程。到时候我会拿一个实际的消防报警主板来拆解,你们会看到代码是怎么跑起来的。