3、安全启动与密钥管理:基于TrustZone的硬件信任根、安全启动链、密钥派生与存储策略
安全启动和密钥管理,说白了就是给设备办一张「出生证明」和「身份证」。没有这两样东西,你的国密算法再强,也架不住别人从根上把你替换掉。我在做金融终端安全方案时,见过太多「算法很强、密钥裸奔」的案例——SM4加密做得滴水不漏,结果根密钥直接明文躺在Flash里。嗯,这就像给保险柜装了顶级锁,却把钥匙挂在门上。
TrustZone的价值就在这里:它能把信任根和密钥管理锁进安全世界,让普通世界连碰都碰不到。今天我们就来拆解这套机制。
3.1 硬件信任根:一切信任的起点
信任根(Root of Trust, RoT)是安全启动的基石。它必须是一个不可篡改、物理隔离的实体。在TrustZone架构中,这个角色通常由以下几部分承担:
- BootROM:芯片出厂时固化在只读存储器中的代码,是信任链的第一环。它不可更改,物理上无法被覆盖。
- OTP(一次性可编程存储器):用于存储芯片唯一身份、根密钥哈希、安全配置等。写入后永久锁定。
- 安全世界的中断向量表:确保安全世界代码入口不被普通世界劫持。
核心原则:信任根必须满足「三不」——不可读、不可写、不可绕过。BootROM是硬件固化的,OTP是一次性写入的,安全世界入口是Monitor模式强制路由的。这三者缺一不可。
我个人习惯把信任根比作「芯片的DNA」。它从出厂那一刻就决定了这个设备是否可信。我在一个IoT门锁项目里遇到过这样的坑:某厂商为了省成本,把根密钥存在外部SPI Flash里,结果被物理攻击直接读出。后来我们改用OTP存储根密钥哈希,配合TrustZone的隔离,才彻底堵住这个漏洞。
3.2 安全启动链:逐级验证,层层递进
安全启动不是一步到位的,而是一个链式验证过程。每一级在加载下一级之前,都要先验证它的签名或哈希。一旦某级验证失败,启动立即终止。
典型的TrustZone安全启动链如下:
- BootROM:上电后最先执行。它读取OTP中的根公钥哈希,验证下一级Bootloader(通常是BL1)的签名。
- BL1(一级Bootloader):运行在安全世界SRAM中。它负责初始化DRAM,验证BL2的镜像。
- BL2(二级Bootloader):运行在安全世界DRAM中。它加载并验证安全世界固件(如OP-TEE)和普通世界Bootloader(如U-Boot)。
- 安全世界固件(OP-TEE/Trusted Firmware):提供安全服务,管理安全存储,执行国密运算。
- 普通世界Bootloader & OS:启动Linux或其它富操作系统。
为什么会这样设计?你想想看,如果BootROM直接加载整个OS,那BootROM的代码量会非常庞大,漏洞面也大。逐级验证的好处是:每一级只做最少的事,代码量小,容易审计,也容易做形式化验证。
避坑指南:我曾经在一个项目中,BL1和BL2共用同一套签名密钥。后来做安全审计时发现,如果BL1被逆向,攻击者就能用同一密钥签名恶意BL2。正确的做法是:每一级使用独立的密钥对,并且密钥的用途在OTP中明确限定。
下面这张图展示了完整的信任链传递过程:
3.3 密钥派生:从根密钥到业务密钥
有了信任根和安全启动,接下来就是密钥管理了。直接使用根密钥加密业务数据是极其危险的做法——一旦根密钥泄露,整个设备的安全体系就崩塌了。正确的做法是:通过密钥派生,生成不同用途的次级密钥。
在TrustZone安全世界中,我常用的密钥派生策略如下:
| 密钥层级 | 存储位置 | 派生方式 | 用途 |
|---|---|---|---|
| 根密钥(RK) | OTP / eFuse | 芯片出厂随机生成,不可导出 | 信任根,仅用于派生 |
| 设备密钥(DK) | 安全存储(RPMB/安全文件) | HKDF-SM3(RK, 设备ID, 上下文) | 设备身份认证 |
| 加密密钥(EK) | 安全存储(会话级) | HKDF-SM3(DK, 应用ID, 随机数) | 业务数据加密(SM4) |
| 签名密钥(SK) | 安全存储(持久化) | HKDF-SM3(RK, 用途标签, 计数器) | 固件签名、消息认证(SM2) |
重要提醒:密钥派生必须使用国密标准的KDF算法。我推荐使用HKDF-SM3,它基于HMAC-SM3构建,符合GM/T 0005-2021标准。千万不要自己拼接哈希值,那很容易引入侧信道攻击。
这里有一个我在实际项目中用过的派生函数示例:
// 基于SM3的密钥派生函数(HKDF-SM3)
// 输入:根密钥root_key, 盐值salt, 上下文信息info, 输出长度len
// 输出:派生密钥derived_key
int derive_key_sm3(const uint8_t *root_key, size_t root_len,
const uint8_t *salt, size_t salt_len,
const uint8_t *info, size_t info_len,
uint8_t *derived_key, size_t len) {
// 第1步:提取阶段(Extract)
// 使用HMAC-SM3生成伪随机密钥PRK
uint8_t prk[32]; // SM3输出32字节
hmac_sm3(salt, salt_len, root_key, root_len, prk);
// 第2步:扩展阶段(Expand)
// 基于PRK生成所需长度的密钥
uint8_t t[32];
uint8_t counter = 1;
size_t offset = 0;
while (offset < len) {
// T(i) = HMAC-SM3(PRK, T(i-1) || info || counter)
hmac_sm3_update(prk, 32, t, offset);
hmac_sm3_update(prk, 32, info, info_len);
hmac_sm3_update(prk, 32, &counter, 1);
hmac_sm3_final(prk, 32, t);
size_t copy_len = (len - offset) > 32 ? 32 : (len - offset);
memcpy(derived_key + offset, t, copy_len);
offset += copy_len;
counter++;
}
return 0;
}
3.4 密钥存储策略:安全世界的「保险柜」
密钥派生出来了,存在哪里?普通世界的文件系统肯定不行。TrustZone提供了几种安全存储方案:
- RPMB(Replay Protected Memory Block):eMMC/UFS中的安全分区,具备重放保护。适合存储设备密钥和证书。
- 安全文件系统(如OP-TEE的Secure Storage):基于RPMB或安全Flash分区,提供文件级加密存储。密钥在存储前会用设备密钥加密。
- 安全SRAM:掉电易失,适合存放会话密钥。攻击者即使物理拆机,断电后数据即消失。
我的经验:对于高安全场景(如金融支付),我建议采用「三级存储」策略:
- 根密钥放在OTP,物理不可读。
- 设备密钥放在RPMB,由安全世界固件管理。
- 会话密钥放在安全SRAM,用完即焚。
这样即使攻击者拿到Flash镜像,也得不到任何有效密钥。
另外,密钥的生命周期管理也很重要。我见过不少设备,密钥从出厂到报废从不更换。正确的做法是:
- 生产阶段:在安全环境中注入根密钥,锁定OTP。
- 运行阶段:定期派生新会话密钥,旧密钥安全擦除。
- 退役阶段:触发安全擦除流程,清除所有安全存储区。
嗯,这里要注意:安全擦除不是简单地删除文件。对于Flash存储,要执行全块擦除并写入随机数,防止数据残留被恢复。
3.5 国密算法在安全启动中的具体应用
讲完了框架,我们来看看国密算法具体怎么嵌入到安全启动链中。
在标准的TrustZone安全启动中,签名验证通常使用RSA或ECDSA。替换为国密SM2后,流程如下:
- 镜像签名:在编译阶段,使用SM2私钥对镜像文件计算签名(SM2签名算法)。
- 镜像哈希:同时计算镜像的SM3哈希值,用于完整性校验。
- 启动验证:BootROM读取OTP中的SM2公钥,验证BL1镜像的SM2签名。验证通过后,计算BL1的SM3哈希,与签名中的哈希比对。
- 逐级传递:每一级都重复上述过程,直到整个系统启动完成。
关键点:SM2签名验证的计算量比RSA-2048大约30%,但安全性更高(等效于RSA-3072)。在嵌入式设备上,我建议使用硬件SM2加速器,否则启动时间可能增加数百毫秒。
我曾经在一个项目中,因为没启用硬件SM2加速,导致安全启动耗时从200ms飙升到1.2s。后来在TrustZone安全世界中配置了SM2协处理器,才把时间压回300ms以内。所以,硬件加速不是可选项,而是必选项。
最后,总结一下本章的核心要点:
- 信任根必须硬件固化,不可篡改。
- 安全启动是链式验证,每一级独立签名。
- 密钥派生使用HKDF-SM3,分层管理。
- 密钥存储利用TrustZone安全存储机制,分级保护。
- 国密SM2/SM3可无缝替换国际算法,但需注意硬件加速。
这些内容,说白了就是一句话:从芯片上电的第一条指令开始,到业务密钥的每一次使用,都要确保「可信」和「可控」。TrustZone给了我们隔离的土壤,国密算法给了我们自主的武器,剩下的就是如何把它们组合成一套坚固的防御体系。