ESP-IDF 中 flash 加密可以在 bootloader 阶段自动启用,但是这需要设备自加密后重启一次,为了节省这次重启的步骤,你可以选择通过一些脚本工具在外部启用 flash 加密。
本篇文档用于介绍 ESP32-C3 手动启用 Secure Boot V2
与 Flash 加密
的操作流程。其中,Flash 加密使用发布模式(Release Mode),并使用主机生成的密钥对数据进行加密。Secure Boot
方案所需的公钥的 SHA-256 哈希摘要和和 Flash 加密
方案需要的 Flash 加密秘钥均为外部生成(bootloader 阶段自动启用的方式为设备内自动计算并写入相关的 efuse)。其他相关的 efuse 也通过脚本工具进行烧录,更多信息请参考乐鑫官方文档 基于主机的安全工作流程。本篇文档可以看做官方文档的一次实践,仅做参考。
1 测试环境
- 硬件:ESP32-C3
- IDF 版本:esp-idf v5.0.2
- 测试例程:examples/system/ota/simple_ota_example
2 通过 menuconfig 使能
2.1 调整分区表偏移量
使能 Flash 加密以及 Secure Boot V2
后,生成的 bootloader bin
会变大,可以通过调整分区表偏移量增大 bootloader 分区大小,本例中将分区表偏移量从默认的 0x8000
调整到 0xF000
,这样 bootloader 分区的大小为 0xF000 - 0x1000 = 0xE000
(0x0 为 bootloader bin 烧录地址),可以参考引导加载程序大小。
需要注意的是,开启 Flash 加密会默认使能 NVS 加密,需要在分区表中定义 NVS Key 分区,否则运行会出现错误。参考 NVS Key Partition。如果不需要 NVS 加密,可以通过 menuconfig 禁用该功能,配置路径:
(Top) -> Component config -> NVS -> [ ] Enable NVS encryption
,这样无需在分区表中定义 NVS Key 分区。
2.2 安全特征配置
Flash 加密 release 模式默认的 UART ROM download mode
为 Permanently switch to Secure mode (recommended)
,在开发过程中,为了避出现意外无法使设备恢复正常,暂且使能 UART ROM 下载模式,后面可以通过烧录 ENABLE_SECURITY_DOWNLOAD
使用安全下载模式。
3 测试流程
3.1 生成 Secure Boot V2 签名秘钥并烧录到对应 efuse
- 可使用如下指令生成签名秘钥:
python $IDF_PATH/components/esptool_py/esptool/espsecure.py generate_signing_key --version 2 secure_boot_signing_key.pem
- 生成
secure_boot_digest.bin
,该文件为secure boot
公钥的SHA-256
摘要:
python $IDF_PATH/components/esptool_py/esptool/espsecure.py digest_sbv2_public_key --keyfile secure_boot_signing_key.pem --output secure_boot_digest.bin
- 烧录
secure_boot_digest.bin
:
python $IDF_PATH/components/esptool_py/esptool/espefuse.py burn_key BLOCK_KEY0 secure_boot_digest.bin SECURE_BOOT_DIGEST0
- 烧录
SECURE_BOOT_EN
:
python $IDF_PATH/components/esptool_py/esptool/espefuse.py burn_efuse SECURE_BOOT_EN 1
3.2 Flash 加密密钥生成与烧录
python $IDF_PATH/components/esptool_py/esptool/espsecure.py generate_flash_encryption_key my_flash_encryption_key.bin
python $IDF_PATH/components/esptool_py/esptool/espefuse.py burn_key BLOCK_KEY1 my_flash_encryption_key.bin XTS_AES_128_KEY
- Flash 加密相关安全 eFuses:
DIS_DOWNLOAD_ICACHE
: Disable UART cacheDIS_DIRECT_BOOT
: Disable direct boot (legacy SPI boot mode)DIS_USB_JTAG
: Disable USB switch to JTAGDIS_PAD_JTAG
: Disable JTAG permanentlyDIS_DOWNLOAD_MANUAL_ENCRYPT
: Disable UART bootloader encryption access
此处先不烧录DIS_DOWNLOAD_MANUAL_ENCRYPT
,如果不禁用这个 efuse,那么仍可通过idf.py encrypted-app-flash/idf.py encrypted-flash
(参考 Re-flashing Updated Partitions)或者esptool.py write_flash --encrypt
这两种方式边写边加密明文固件。
python $IDF_PATH/components/esptool_py/esptool/espefuse.py burn_efuse DIS_DOWNLOAD_ICACHE 1 DIS_PAD_JTAG 1 DIS_USB_JTAG 1 DIS_DIRECT_BOOT 1
- Flash 加密模式下
SPI_BOOT_CRYPT_CNT
需要设置为7
,此时 Flash 加密无法关闭。
python $IDF_PATH/components/esptool_py/esptool/espefuse.py burn_efuse SPI_BOOT_CRYPT_CNT 7
3.3 编译 bootloader
idf.py bootloader
3.4 编译 app/partition bin
idf.py build
Project build complete. To flash, run this command:
/home/mali/.espressif/python_env/idf5.0_py3.8_env/bin/python ../../../../components/esptool_py/esptool/esptool.py -p (PORT) -b 460800 --before default_reset --after no_reset --chip esp32c3 --no-stub write_flash --flash_mode dio --flash_size keep --flash_freq 80m 0xf000 build/partition_table/partition-table.bin 0x14000 build/ota_data_initial.bin 0x20000 build/simple_ota.bin
or run 'idf.py -p (PORT) flash'
由于使能了 Sign binaries during build
, 会在构建过程中对 bootloader
和 app bin
进行签名,此时,生成的 bootloader.bin
和 app.bin
为签过名的明文固件。
- 获取分区表信息:
$ idf.py partition-table
Executing action: partition-table
Running ninja in directory /home/mali/esp/esp32/rel_50/examples/system/ota/simple_ota_example/build
Executing "ninja partition-table"...
[1/2] cd /home/mali/esp/esp32/rel_50/examples/system/ota/simple_ota_example/build/esp-idf/partition_table && /home/mali/.e...sif/tools/cmake/3.24.0/bin/cmake -E echo "*******************************************************************************"Partition table binary generated. Contents:
*******************************************************************************
# ESP-IDF Partition Table
# Name, Type, SubType, Offset, Size, Flags
nvs,data,nvs,0x10000,16K,
otadata,data,ota,0x14000,8K,
phy_init,data,phy,0x16000,4K,
ota_0,app,ota_0,0x20000,1536K,
ota_1,app,ota_1,0x1a0000,1536K,
nvs_key,data,nvs_keys,0x320000,4K,encrypted
*******************************************************************************
[2/2] Running utility command for partition-tablePartition table built:
Partition Table build complete. To flash, run this command:
/home/mali/.espressif/python_env/idf5.0_py3.8_env/bin/python ../../../../components/esptool_py/esptool/esptool.py -p (PORT) -b 460800 --before default_reset --after no_reset --chip esp32c3 --no-stub write_flash 0xf000 build/partition_table/partition-table.bin
or run 'idf.py -p (PORT) partition-table-flash'
3.5 使用 Flash 加密密钥对签名后的 bin 文件进行加密
使用 Flash 加密密钥对 bin 文件加密时,需要指定 bin 文件的地址,这个地址可从 idf.py build
结束后输出的烧录指令中获取,在本例中,共有四个 bin 文件需要被加密。
0x0 build/bootloader/bootloader.bin (ESP32-C3 的 bootloader bin 的烧录地址是一个确定值,为 0x0)
0xf000 build/partition_table/partition-table.bin
0x14000 build/ota_data_initial.bin
0x20000 build/simple_ota.bin
python $IDF_PATH/components/esptool_py/esptool/espsecure.py encrypt_flash_data --aes_xts --keyfile my_flash_encryption_key.bin --address 0x20000 --output simple_ota_enc.bin build/simple_ota.bin
python $IDF_PATH/components/esptool_py/esptool/espsecure.py encrypt_flash_data --aes_xts --keyfile my_flash_encryption_key.bin --address 0xf000 --output partition-table_enc.bin build/partition_table/partition-table.bin
python $IDF_PATH/components/esptool_py/esptool/espsecure.py encrypt_flash_data --aes_xts --keyfile my_flash_encryption_key.bin --address 0x0 --output bootloader_enc.bin build/bootloader/bootloader.bin
python $IDF_PATH/components/esptool_py/esptool/espsecure.py encrypt_flash_data --aes_xts --keyfile my_flash_encryption_key.bin --address 0x14000 --output ota_data_initial_enc.bin build/ota_data_initial.bin
3.6 烧录加密后的 bin 文件
- 烧录前建议擦除 Flash 一次。
python $IDF_PATH/components/esptool_py/esptool/esptool.py -b 921600 --before default_reset --after no_reset --chip esp32c3 --no-stub write_flash --force --flash_mode dio --flash_size keep --flash_freq 80m 0x0 bootloader_enc.bin 0xf000 partition-table_enc.bin 0x14000 ota_data_initial_enc.bin 0x20000 simple_ota_enc.bin
- 烧录完成后,检查设备是否可以正常启动。如果可以正常启动,从 log 中应该可以看到类似下面的日志:
E (1171) flash_encrypt: Flash encryption settings error: app is configured for RELEASE but efuses are set for DEVELOPMENT
E (1181) flash_encrypt: Mismatch found in security options in bootloader menuconfig and efuse settings. Device is not secure.
flash_encrypt
报错的的原因是当前烧录的 efuse 和 Flash 加密 release 模式不匹配,这是因为前面没有烧录 DIS_DOWNLOAD_MANUAL_ENCRYPT
,release 模式下需要保证这个 efuse 烧录,此时,无法再可通过 idf.py encrypted-app-flash/idf.py encrypted-flash
(参考 Re-flashing Updated Partitions)或者 esptool.py write_flash --encrypt
这两种方式边写边加密明文固件,需要先使用 Flash 加密秘钥对明文固件进行加密,然后直接烧录密文固件。
espefuse.py burn_efuse DIS_DOWNLOAD_MANUAL_ENCRYPT
- 前面在 menuconfig 中使能 UART ROM 下载模式,Flash release 模式建议使用安全下载模式,该模式下,仍可烧录固件但是无法读取固件。
espefuse.py burn_efuse ENABLE_SECURITY_DOWNLOAD
4 补充内容
UART ROM 三种下载模式的比较
- 下面是对于 UART ROM 三种下载模式的大致比较,更多信息请见 UART ROM download mode。
- SECURE_DISABLE_ROM_DL_MODE:禁用下载模式
- SECURE_ENABLE_SECURE_ROM_DL_MODE:安全下载模式
- SECURE_INSECURE_ALLOW_DL_MODE:使能下载模式
禁用下载模式 | 安全下载模式 | 使能下载模式 | |
---|---|---|---|
是否可以下载固件 | 否 | 是 | 是 |
是否可以读取固件 | 否 | 否 | 是(esptool.py --no-stub read_flash ) |
是否可以获取 efuse 信息 | 否 | 是(部分,esptool.py --no-stub get_security_info | 是(全部,espefuse.py summary ) |
通过烧录 efuse 使能 | espefuse.py burn_efuse DIS_DOWNLOAD_MODE | espefuse.py burn_efuse ENABLE_SECURITY_DOWNLOAD | 无需烧录 efuse |
- 如果要对已经使能 Flash 加密和 secure boot v2 的设备进行 OTA 升级,那么 ota bin 文件需要先进行签名再放到服务器上,对于通用的 ota 例程,放在服务器上的 ota bin 应为签名后的明文固件,在写入到 ota 分区时会自动进行加密。
espsecure.py sign_data --version 2 --keyfile PRIVATE_SIGNING_KEY --output SIGNED_BINARY_FILE BINARY_FILE
-
此处的 PRIVATE_SIGNING_KEY 为前面生成的
Secure Boot
签名密钥secure_boot_signing_key.pem
,更多信息可见 Signing using espsecure.py。 -
如果担心服务器上的明文 ota.bin 被他人窃取(例如: non-TLS).,可以对 ota.bin 进行预加密,参考例程 pre_encrypted_ota。