嵌入式开发 物联网设备安全加固:固件加密与安全启动实战
你的 IoT 设备裸奔了吗?
很多开发者把 ESP32 程序烧进去就跑,完全没想过一个问题:别人把你的板子拆下来,用编程器读一下 Flash,整个固件就到手了。里面可能有 WiFi 密码、API 密钥、私有协议逻辑,甚至商业算法。今天咱们就来聊聊怎么给 IoT 设备穿上”防弹衣”——安全启动(Secure Boot)+ Flash 加密。
为什么要做固件安全?
先说个真实场景:你做了一个智能门锁,ESP32 负责蓝牙通信和电机控制。某天竞争对手买了你的产品,拆开外壳,用 SPI 编程器把 Flash 芯片里的数据全读出来。反编译一看,蓝牙配对密钥、开锁协议、甚至云端 API 的 secret key 全部明文躺着。接下来他们就能克隆你的产品,或者直接破解开锁流程。
这就是不做固件安全的后果。安全启动 + Flash 加密就是为了解决这两个核心问题:
-
$1
-
$1
ESP32(v3.0 及以上芯片版本)支持的是 Secure Boot v2,基于 RSA-PSS 签名方案,比 v1 更安全也更灵活。下面咱们一步步实操。
硬件清单
| 组件 | 型号/规格 | 说明 |
|---|---|---|
| 开发板 | ESP32-WROOM-32 / ESP32-S3 | 芯片版本需 v3.0 以上 |
| USB 线 | Micro-USB / USB-C | 用于烧录和串口调试 |
| 电脑 | Windows / macOS / Linux | 安装 ESP-IDF 开发环境 |
| ESP-IDF | v5.0+ | 乐鑫官方开发框架 |
**
注意**:老版本的 ESP32(芯片 revision < v3.0)只支持 Secure Boot v1,本文以 v2 为准。你可以在终端运行 esptool chip_id 查看芯片版本信息。
核心概念:安全启动是怎么工作的?
安全启动的核心思路是信任链(Chain of Trust):
ROM 代码(不可修改) → 验证二级 bootloader 签名 → 验证 App 固件签名 → 运行应用
-
$1
-
$1
-
$1
整个过程的关键在于:私钥永远不存储在设备上,只存在你的开发电脑或更安全的离线环境中。设备上只有公钥摘要(存在 eFuse 里),攻击者即使拿到板子也无法伪造签名。
第一步:生成签名密钥
在项目目录下执行:
idf.py secure-generate-signing-key signing_key.pem
这会生成一个 RSA-3072 的 PEM 格式密钥文件 signing_key.pem。这个文件就是你的命根子,务必妥善保管,建议:
-
备份到离线 U 盘
-
不要上传到 Git
-
生产环境下用 HSM(硬件安全模块)或离线电脑生成
如果你担心 idf.py 生成的随机数不够安全,可以用 OpenSSL 手动生成:
openssl genrsa -out signing_key.pem 3072
第二步:配置 menuconfig
运行 idf.py menuconfig,进入以下配置项:
启用安全启动 v2
Security features →
[✓] Enable hardware Secure Boot in bootloader
Secure Boot Version → Secure Boot V2 (RSA-PSS)
设置芯片最低版本
Component config → ESP32-specific →
Minimum Supported ESP32 Revision → v3.0
指定签名密钥路径
Security features →
(/path/to/signing_key.pem) Signing key file path
(可选)禁用 UART 下载模式
Security features →
[ ] Allow UART ROM download mode
**
警告**:禁用 UART 下载模式后,你将无法再通过串口重新烧录固件。如果后续需要 OTA 升级,确保 OTA 功能已正确配置。开发阶段建议保持开启,量产前再关闭。
启用 Flash 加密(可选但推荐)
Security features →
[✓] Enable flash encryption on boot (RELEASE_ON_BOOT)
Flash encryption mode → DEVELOPMENT (开发阶段) / RELEASE (生产阶段)
-
DEVELOPMENT 模式:每次烧录时自动重新加密,方便调试。
-
RELEASE 模式:只加密一次,之后 Flash 内容永久加密,无法再明文读取或修改。
第三步:构建并烧录
构建 bootloader
idf.py bootloader
构建完成后,终端会提示你烧录 bootloader 的命令,类似:
esptool.py --port /dev/ttyUSB0 write_flash 0x0 build/bootloader/bootloader.bin
手动执行这条命令,将签名后的 bootloader 烧录到地址 0x0。
构建并烧录 App
idf.py flash
**
注意**:启用安全启动后,idf.py flash 不会烧录 bootloader,只会烧录分区表和 App 固件。这是为了防止意外覆盖已签名的 bootloader。
首次启动
复位 ESP32,观察串口输出。你应该看到类似以下内容:
I (xxx) boot: Secure boot V2 enabled
I (xxx) boot: Validating app image...
I (xxx) boot: App image verified successfully
如果看到 Secure boot verification failed 或启动卡住,说明签名验证失败。常见原因:
-
密钥不匹配(用了不同的密钥签名)
-
eFuse 未正确写入公钥摘要
-
Flash 加密配置冲突
第四步:验证安全效果
测试 1:尝试烧录未签名固件
用另一个未启用安全启动的项目构建固件,尝试烧录:
# 在未启用安全启动的项目中
idf.py flash
复位后,设备会拒绝启动,串口输出类似:
E (xxx) boot: Secure boot verification failed! Halting...
测试 2:读取 Flash 内容
用 esptool 读取 Flash:
esptool.py --port /dev/ttyUSB0 read_flash 0x0 0x400000 flash_dump.bin
如果启用了 Flash 加密(RELEASE 模式),flash_dump.bin 里的内容是加密的,直接用 strings 或反编译工具看不到任何有意义的信息。
常见问题排查
Q1:启用安全启动后无法再烧录怎么办?
A:如果你禁用了 UART 下载模式且没有配置 OTA,那就真的”砖”了。解决方案:
-
开发阶段不要禁用 UART 下载模式
-
配置 OTA 功能,通过无线方式更新固件
-
使用 JTAG 调试接口烧录(需要额外硬件)
Q2:Flash 加密后调试变得困难?
A:开发阶段使用 DEVELOPMENT 模式,每次 idf.py flash 会自动重新加密,不影响调试。切换到 RELEASE 模式后再做最终测试。
Q3:OTA 升级时如何处理签名?
A:OTA 固件必须用同一个私钥签名。在 OTA 服务器端,用以下命令签名新固件:
idf.py secure-sign-app build/app.bin signed_app.bin
然后推送 signed_app.bin 到设备。ESP32 的 OTA 机制会自动验证签名,失败则回滚到旧版本。
Q4:eFuse 写错能恢复吗?
A:不能。eFuse 是一次性可编程存储器(OTP),一旦写入就无法撤销。这也是为什么建议在开发阶段充分测试,确认配置无误后再烧录 eFuse。
Q5:性能影响大吗?
A:安全启动的签名验证只在启动时执行一次,增加约 100-200ms 启动时间,对大多数 IoT 应用可以忽略。Flash 加密/解密由硬件加速,运行时几乎没有性能损耗。
生产环境最佳实践
-
$1
-
$1
-
$1
-
$1
-
$1
小结
给 IoT 设备做安全加固,核心就两件事:
-
安全启动:确保只有你签名的固件能跑
-
Flash 加密:确保固件内容不会被物理读取
ESP32 的 Secure Boot v2 + Flash 加密方案已经非常成熟,配置起来也不复杂。关键是养成习惯——从第一个项目开始就把安全考虑进去,而不是等到产品上市后被破解才补救。
如果你的设备涉及用户隐私、支付信息或商业机密,这套方案几乎是必选项。毕竟,没人想成为下一个”智能门锁被批量克隆”的新闻主角吧?
参考资料:ESP-IDF 安全启动 v2 官方文档