4. 密钥层级与信任根建立

固件加密的强度并不取决于最外层密钥有多长,而取决于整条密钥链的起点是否可信、每一级派生是否可验证、每一份密钥是否被隔离存放。如果设备把“加密固件用的 AES 密钥”直接烧写在 Flash 的某个固定偏移,那么攻击者只要读出这一块,整条防线就同时失效。因此工程上采用密钥层级(Key Hierarchy):由硬件信任根出发,逐级派生、逐级隔离,任何一级泄露都不会直接暴露其他用途的密钥。

一、信任根:不可变的起点

信任根(Root of Trust, RoT)是整条密钥链的锚点,其核心属性是“不可篡改、不可读出、可被硬件验证”。在典型 MCU/SoC 上,它由三部分构成:

  • 不可变 ROM 引导代码(BootROM):芯片出厂固化,负责上电后第一段执行逻辑,验证下一级引导的签名。
  • 硬件唯一密钥(HUK / Device Secret):由芯片物理不可克隆函数(PUF)或一次性可编程熔丝(OTP/eFuse)生成,只存在于密钥总线内部,CPU 无法通过普通读指令获取。
  • 安全启动公钥哈希:烧写在 OTP 中的根公钥摘要,用于验证一级引导签名,防止替换引导镜像。

信任根不直接加密固件,它只做两件事:验证“下一级代码是否可信”,以及作为密钥派生函数(KDF)的输入秘密。这样即使攻击者拿到固件密文,也无法在没有 HUK 的情况下复现密钥。

二、层级派生:HKDF 如何把根秘密展开成多把密钥

从 HUK 到最终固件加密密钥,不能简单做一次哈希,而应使用标准 KDF 实现“一密多用、用途隔离”。HKDF(RFC 5869)由 Extract 和 Expand 两阶段组成,正好满足这一需求:

// 阶段一:Extract,把 HUK 与盐混合,得到固定长度的伪随机密钥 PRK
PRK = HMAC-SHA256(salt = device_id || "fw-root-v1", IKM = HUK)

// 阶段二:Expand,按 info 标签派生出不同用途的密钥
K_fw_enc  = HKDF-Expand(PRK, info = "firmware-encryption", L = 32)
K_fw_sign = HKDF-Expand(PRK, info = "firmware-signing",    L = 32)
K_ota     = HKDF-Expand(PRK, info = "ota-session",         L = 32)
K_store   = HKDF-Expand(PRK, info = "secure-storage",      L = 32)

关键点在于 info 标签:它把同一份 PRK 映射成互不相关的多把密钥。即使 K_ota 在会话中被泄露,攻击者也无法反推 K_fw_enc,因为 HMAC 的单向性保证了不同 info 的输出在计算上不可关联。盐值中加入 device_id 则实现“一机一密”,同一固件镜像在不同设备上派生出不同密钥,防止批量复制攻击。

派生层级通常设计为三层:根层(HUK)→ 主密钥层(PRK / Master Key)→ 用途密钥层(Firmware Key、OTA Key、Storage Key)。层级不宜过深,否则每级都引入一次 HMAC 开销和一次密钥暴露面;三层已能覆盖固件加密、签名验证、安全存储和会话密钥四类典型用途。

密钥层级派生体系(HKDF-SHA256) 硬件信任根 RoT HUK (PUF/eFuse) + BootROM + OTP 公钥哈希 不可读出 · 不可篡改 · 仅密钥总线可见 HKDF-Extract salt = device_id ‖ "fw-root-v1" 主密钥 PRK(Master Key) HMAC-SHA256(HUK, salt) → 32 Byte 仅存在于安全 RAM / 密钥槽,不落盘 HKDF-Expand info 标签区分用途 固件加密密钥 K_fw_enc info="firmware-encryption" 固件签名密钥 K_fw_sign info="firmware-signing" OTA 会话密钥 K_ota info="ota-session" 安全存储密钥 K_store info="secure-storage" 隔离存储与使用边界 AES 引擎密钥槽 仅解密引擎可读 ECDSA 签名单元 私钥不出安全域 会话 RAM 临时区 用完即清零 加密 NVM 分区 密文落盘

三、隔离存储:密钥不能“住在一起”

派生完成后,密钥的存放位置决定了攻击面。工程上遵循三条隔离原则:

  1. 根密钥不出安全域:HUK 和 PRK 只存在于芯片内部密钥总线或安全 RAM,CPU 通过普通 load 指令读不到;需要使用时由硬件加密引擎直接引用密钥句柄,而非把密钥搬到通用内存。
  2. 用途密钥按需加载:K_fw_enc 只在解密固件镜像时被加载到 AES 引擎密钥槽,解密完成后立即清除;K_ota 只在 OTA 会话期间驻留,会话结束即销毁。
  3. 持久化密钥必须二次保护:如果某些密钥需要跨重启保存(如设备身份密钥),应使用 HUK 派生的 K_store 对其做 AES-GCM 封装后再写入 NVM,且每次写入使用不同 nonce,防止重放。
隔离的本质是“最小权限 + 最短生命周期”:每把密钥只暴露给真正需要它的硬件单元,且只在必要的时间窗口内存在。这样即使某个软件模块被攻破,攻击者能拿到的也只是当前会话的临时密钥,无法横向移动到其他用途。

四、一个可落地的派生与存储示例

假设某 Cortex-M33 设备使用 TrustZone 作为安全域边界,HUK 由 eFuse 提供 256 bit 随机值,设备唯一 ID 为 128 bit。上电后安全启动流程如下:

// 1. BootROM 读取 eFuse HUK 与 device_id
// 2. 计算 PRK
salt = device_id || "fw-root-v1"
PRK  = HMAC-SHA256(key = HUK, data = salt)

// 3. 派生固件加密密钥(AES-256-GCM)
K_fw_enc = HKDF-Expand(PRK, info = "firmware-encryption", L = 32)

// 4. 用 K_fw_enc 解密固件镜像
//    镜像头部携带 nonce(12B) 与 tag(16B)
plaintext = AES_GCM_Decrypt(K_fw_enc, nonce, ciphertext, tag)

// 5. 解密完成后清除密钥槽
secure_zero(K_fw_enc)
secure_zero(PRK)

验证时,可在安全域内读取一个“密钥派生自检”结果:用相同 HUK 和 device_id 重新派生 K_fw_enc,对固定测试向量加密,比对密文是否一致。若一致,说明派生路径和盐值拼接顺序正确;若不一致,优先检查 device_id 的字节序和 info 字符串是否被截断。注意,自检过程本身不应把密钥输出到非安全域,只输出“通过/失败”标志。

五、常见误区与判断依据

误区后果正确做法
用 HUK 直接加密固件HUK 暴露面扩大,无法轮换HUK 只做 KDF 输入,不直接参与加解密
所有用途共用一个派生密钥一处泄露,全线失守用不同 info 标签隔离用途
盐值固定且不含设备 ID同固件跨设备密钥相同,可批量复制盐值绑定 device_id,实现一机一密
派生密钥写入 Flash 缓存可被读出,层级形同虚设密钥只驻留安全 RAM / 硬件密钥槽

判断一套密钥层级是否合格,可以问三个问题:根密钥是否物理不可读出?每把用途密钥是否有独立的 info 标签和生命周期?密钥使用后是否被可靠清除?三个答案都为“是”,才说明从信任根到固件加密密钥的链路真正闭合。