由于部分用户在 nvs 中存储了一些安全性需求较高的数据,比如 cloud 对应的秘钥。希望在 flash 加密时同时开启 nvs 加密。此篇文章着重描述 nvs 加密需要进行的流程,大体分为以下三步:
- 编写需要生成的自定义 nvs 键值对
- 生成 nvs.bin 的 key 与加密后的 nvs.bin 进行加密
- 烧录加密后的 nvs.bin 和 nvs key 到 flash
注意:nvs 加密同时需要修改分区表,具体会在后文说明。
nvs 加密对应的指令可以简要概括如下:
-
使用
nvs_partition_gen.py
生成nvs_key.bin
用于加密 nvs 区域:
python nvs_partition_gen.py generate-key --keyfile nvs_key.bin
-
使用
nvs_key.bin
加密nvs_test.csv
,生成nvs_encrpytion.bin
:
python nvs_partition_gen.py encrypt nvs_test.csv nvs_encrpytion.bin 0x10000 --inputkey ./keys/nvs_key.bin
-
加密烧录
MAC_nvs_key.bin
:
esptool_3.0.exe -p COM157 write_flash --encrypt 0xff000 ./nvs_key.bin
-
烧录
nvs_encrpytion.bin
:
esptool_3.0.exe -p COM157 write_flash 0x13000 nvs_encrpytion.bin
注: 上述 bin 的名称和对应的分区表 offset 需要根据用户实际分区表来修改,仅供参考。
1. 编写需要生成的自定义 nvs 键值对
ESP-IDF 里有 nvs.bin 生成工具,可以参考 这里,此文中会直接使用此工具来进行 nvs.bin 生成与加密操作。
例如我们想要定义以下两个键值对:
- uint_8 类型的数据,值为 127
- string 类型的数据,值为 0A:0B:0C:0D:0E:0F
此时我们可以创建一个 .csv 文件,比如 test.csv,编写上述两个键值对,如下:
对应的文本为:
key,type,encoding,value
test,namespace,,
testKey,data,u8,127
testStringKey,data,string,0A:0B:0C:0D:0E:0F
2. 生成 nvs.bin 的 key 与加密后的 nvs.bin 进行加密
进入上述 nvs.bin 生成工具 对应的终端,首先使用以下指令生成 nvs_key:
python nvs_partition_gen.py generate-key --keyfile nvs_key.bin
然后结合 nvs_key 生成被 nvs_key 加密后的 nvs_test_encrpytion.bin。指令如下:
python nvs_partition_gen.py encrypt test.csv nvs_test_encrpytion.bin 0x10000 --inputkey ./keys/nvs_key.bin
此时即生成了后续需要的 nvs_key.bin 以及 nvs_test_encrpytion.bin。
3. 烧录加密后的 nvs_key.bin 和 nvs_test_encrpytion.bin 到 flash
然后需要进行 secure boot v2 和 flash 加密的配置,由于仅仅是测试,这里就用简单的生成随机 flash 加密秘钥的形式来进行加密。
3.1 Secure boot v2 配置(如果仅仅需要 nvs 加密,则跳过此小节,直接查看 3.2 小节)
在 配置了 ESP-IDF 环境 后,终端跳转至 console example 下,输入 idf.py menuconfig
来使能 secure boot v2 选项,如下:
-
首先需要配置 ESP32 芯片最低版本为 ECO3,因为低于 ESP32 ECO3 的芯片版本不支持 secure boot v2
-
然后将分区表的 offset 向后调整 ,因为 secure boot v2 会导致 bootloader.bin 的大小变大,原有的空间可能会因容纳不了 secure boot v2 签名后的 bootloader,bin 而导致 overlap。这里可以调整分区表 offset 至 0xf000。
-
最后使能 secure boot ,注意这里设置的签名秘钥名称要和上一步生成的签名秘钥名称一致,在这里是
secure_boot_signing_key.pem
注:此处勾选使能了
Sign binaries during build
,所以无需手动做给固件签名的操作,如果需要使用秘钥手动给固件签名,可参考 这里。
修改此示例文件下的分区表文件 partitions_example.csv,可以修改为如下:
# Name, Type, SubType, Offset, Size, Flags
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
nvs, data, nvs, , 0x10000,
otadata, data, ota, , 0x2000,
phy_init, data, phy, , 0x1000,
factory, app, factory, , 768K,
ota_0, app, ota_0, , 768K,
ota_1, app, ota_1, , 768K,
storage, data, fat, , 768K,
nvs_key, data, nvs_keys,0x390000,0x1000,
接下来输入 idf.py build
来编译。可以看到以下错误 log 来提示需要生成 secure boot v2 秘钥 :
Secure Boot Signing Key secure_boot_signing_key.pem does not exist. Generate using:
espsecure.py generate_signing_key --version 2 secure_boot_signing_key.pem
CMake Error at /home/zhengzhong/github/esp-idf/rel4.3/esp-idf/tools/cmake/scripts/fail.cmake:3 (message):
输入以下指令来生成 secure boot v2 秘钥:
espsecure.py generate_signing_key --version 2 secure_boot_signing_key.pem
运行 idf.py bootloader
,生成 对应签名后的 bootloader 后输入
esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size detect 0x1000 build/bootloader/bootloader.bin
运行 idf.py flash
生成并刷新分区表和刚刚构建的应用程序映像。将使用刚生成的签名密钥对应用程序图像进行签名。
注:如此时出现 secure Boot 失败的情况,此时可使用
esptool.py -p /dev/ttyUSB0 -b 460800 --before default_reset --after no_reset --chip esp32 write_flash --flash_mode dio --flash_size keep --flash_freq 40m 0xf000 build/partition_table/partition-table.bin 0x20000 build/ota_data_initial.bin 0x30000 build/console.bin 0x1000 build/bootloader/bootloader.bin
代替烧录。
完成后查看 log, 可以发现 secure boot v2 已经成功开启。如下:
ets Jul 29 2019 12:21:46
rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:2
load:0x3fff0030,len:8956
ho 0 tail 12 room 4
load:0x40078000,len:19436
load:0x40080400,len:3416
0x40080400: _init at ??:?
entry 0x40080638
W (587) secure_boot_v2: Not disabling ROM Download mode - SECURITY COMPROMISED
I (598) cpu_start: Pro cpu up.
I (598) cpu_start: Starting app cpu, entry point is 0x40081428
0x40081428: call_start_cpu1 at /home/zhengzhong/github/esp-idf/rel4.3/esp-idf/components/esp_system/port/cpu_start.c:150
I (0) cpu_start: App cpu up.
I (613) cpu_start: Pro cpu start user code
I (613) cpu_start: cpu freq: 160000000
I (613) cpu_start: Application information:
I (617) cpu_start: Project name: console
I (622) cpu_start: App version: v4.3.2-dirty
I (627) cpu_start: Compile time: Jan 29 2022 20:53:36
I (634) cpu_start: ELF file SHA256: 2a2cf3b0756b8806...
I (640) cpu_start: ESP-IDF: v4.3.2-dirty
I (645) heap_init: Initializing. RAM available for dynamic allocation:
I (652) heap_init: At 3FFAE6E0 len 00001920 (6 KiB): DRAM
I (658) heap_init: At 3FFB7E38 len 000281C8 (160 KiB): DRAM
I (664) heap_init: At 3FFE0440 len 00003AE0 (14 KiB): D/IRAM
I (671) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
I (677) heap_init: At 40096B50 len 000094B0 (37 KiB): IRAM
I (684) spi_flash: detected chip: gd
I (688) spi_flash: flash io: dio
W (692) spi_flash: Detected size(16384k) larger than the size in the binary image header(4096k). Using the size in the binary image header.
I (706) cpu_start: Starting scheduler on PRO CPU.
I (0) cpu_start: Starting scheduler on APP CPU.
W (899) vfs_fat_spiflash: f_mount failed (13)
I (899) vfs_fat_spiflash: Formatting FATFS partition, allocation unit size=4096
I (1099) vfs_fat_spiflash: Mounting again
I (1099) example: Command history enabled
This is an example of ESP-IDF console component.
Type 'help' to get the list of commands.
Use UP/DOWN arrows to navigate through command history.
Press TAB when typing command name to auto-complete.
Press Enter or Ctrl+C will terminate the console environment.
esp32>
3.2 flash encryption 配置 & nvs encryption 配置
先修改此示例文件下的分区表文件 partitions_example.csv,可以修改为如下:
# ESP-IDF Partition Table
# Name, Type, SubType, Offset, Size, Flags
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
nvs, data, nvs, , 0x10000,
otadata, data, ota, , 0x2000, encrypted
phy_init, data, phy, , 0x1000, encrypted
factory, app, factory, , 768K, encrypted
ota_0, app, ota_0, , 768K, encrypted
ota_1, app, ota_1, , 768K, encrypted
storage, data, fat, , 768K, encrypted
nvs_key, data, nvs_keys,0x390000,0x1000, encrypted
需要注意的是需要在部分分区后添加 encrypted
flag。详情可以参考 partition-tables。
输入 idf.py menuconfig
来使能 flash 加密,如下:
由于是进行测试,选择默认的 Development 模式即可。在开启 flash 加密后,nvs 加密选项会默认被勾选,如下:
注:第一次进行 flash 加密时无需手动做给固件进行加密,如果需要使用秘钥手动给固件加密,可参考 这里。
在 flash 加密前,将 nvs_key.bin 和 nvs_test_encrpytion.bin 烧录到指定的 offset:
esptool.py -p /dev/ttyUSB0 -b 460800 --before default_reset --after no_reset --chip esp32 write_flash --flash_mode dio --flash_size keep --flash_freq 40m 0x10000 nvs_test_encrpytion.bin 0x390000 nvs_key.bin
然后执行
idf.py flash monitor
然后发现已经成功进行 flash 加密,secure boot v2 加密以及 nvs 加密,log 如下:
ets Jul 29 2019 12:21:46
rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:2, clock div:2
secure boot v2 enabled
secure boot verification succeeded
load:0x3fff0030 len:0x27bc
load:0x40078000 len:0x50ec
load:0x40080400 len:0xd58
0x40080400: _init at ??:?
entry 0x40080638
I (370) cpu_start: Pro cpu up.
I (370) cpu_start: Starting app cpu, entry point is 0x40081428
0x40081428: call_start_cpu1 at /home/zhengzhong/github/esp-idf/rel4.3/esp-idf/components/esp_system/port/cpu_start.c:150
I (0) cpu_start: App cpu up.
I (384) cpu_start: Pro cpu start user code
I (384) cpu_start: cpu freq: 160000000
I (384) cpu_start: Application information:
I (388) cpu_start: Project name: console
I (393) cpu_start: App version: v4.3.2-dirty
I (399) cpu_start: Compile time: Jan 29 2022 21:17:18
I (405) cpu_start: ELF file SHA256: 1e7f73f6a43b4d2c...
I (411) cpu_start: ESP-IDF: v4.3.2-dirty
I (416) heap_init: Initializing. RAM available for dynamic allocation:
I (424) heap_init: At 3FFAE6E0 len 00001920 (6 KiB): DRAM
I (430) heap_init: At 3FFB7E60 len 000281A0 (160 KiB): DRAM
I (436) heap_init: At 3FFE0440 len 00003AE0 (14 KiB): D/IRAM
I (442) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
I (449) heap_init: At 40096B50 len 000094B0 (37 KiB): IRAM
I (456) spi_flash: detected chip: gd
I (459) spi_flash: flash io: dio
W (463) spi_flash: Detected size(16384k) larger than the size in the binary image header(4096k). Using the size in the binary image header.
W (476) flash_encrypt: Flash encryption mode is DEVELOPMENT (not secure)
I (485) cpu_start: Starting scheduler on PRO CPU.
I (0) cpu_start: Starting scheduler on APP CPU.
I (517) nvs: NVS partition "nvs" is encrypted.
I (527) example: Command history enabled
This is an example of ESP-IDF console component.
Type 'help' to get the list of commands.
Use UP/DOWN arrows to navigate through command history.
Press TAB when typing command name to auto-complete.
Press Enter or Ctrl+C will terminate the console environment.
esp32> nvs_list nvs
namespace 'test', key 'testKey', type 'u8'
namespace 'test', key 'testStringKey', type 'str'
esp32>
附录:常见问题
1. Secure boot V2 后出现以下 log:
ets Jul 29 2019 12:21:46
rst:0x3 (SW_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:1
load:0x3fff0030,len:8956
ho 0 tail 12 room 4
load:0x40078000,len:19436
load:0x40080400,len:3416
0x40080400: _init at ??:?
entry 0x40080638
Sig block 0 invalid: Image digest does not match
E (326) secure_boot_v2: Secure Boot V2 verification failed.
E (327) esp_image: Secure boot signature verification failed
E (339) esp_image: Image hash failed - image is corrupt
W (339) esp_image: image corrupted on flash
E (343) secure_boot_v2: bootloader image appears invalid! error 8194
E (350) boot: Secure Boot v2 failed (8194)
E (355) boot: Factory app partition is not bootable
E (360) esp_image: image at 0xf0000 has invalid magic byte (nothing flashed here?)
E (368) boot: OTA app partition slot 0 is not bootable
E (374) esp_image: image at 0x1b0000 has invalid magic byte (nothing flashed here?)
E (383) boot: OTA app partition slot 1 is not bootable
E (388) boot: No bootable app partitions in the partition table
ets Jul 29 2019 12:21:46
rst:0x3 (SW_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:1
load:0x3fff0030,len:8956
ho 0 tail 12 room 4
load:0x40078000,len:19436
load:0x40080400,len:3416
0x40080400: _init at ??:?
entry 0x40080638
Sig block 0 invalid: Image digest does not match
E (326) secure_boot_v2: Secure Boot V2 verification failed.
E (327) esp_image: Secure boot signature verification failed
E (339) esp_image: Image hash failed - image is corrupt
W (339) esp_image: image corrupted on flash
E (343) secure_boot_v2: bootloader image appears invalid! error 8194
E (350) boot: Secure Boot v2 failed (8194)
E (355) boot: Factory app partition is not bootable
E (360) esp_image: image at 0xf0000 has invalid magic byte (nothing flashed here?)
E (368) boot: OTA app partition slot 0 is not bootable
E (374) esp_image: image at 0x1b0000 has invalid magic byte (nothing flashed here?)
E (383) boot: OTA app partition slot 1 is not bootable
E (388) boot: No bootable app partitions in the partition table
此时需要擦除 flash,然后手动使用指令烧录所有固件(包括签名后的 bootloader.bin 和 app.bin),例如:
esptool.py -p /dev/ttyUSB0 -b 460800 --before default_reset --after no_reset --chip esp32 write_flash --flash_mode dio --flash_size keep --flash_freq 40m 0xf000 build/partition_table/partition-table.bin 0x20000 build/ota_data_initial.bin 0x30000 build/console.bin 0x1000 build/bootloader/bootloader.bin
2. 在 secure boot 时使能 flash 加密后,输入 idf.py flash monitor
后出错
烧录后发现以下异常(未完待续):
rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:2, clock div:2
secure boot v2 enabled
secure boot verification succeeded
load:0x3fff0030 len:0x27bc
load:0x40078000 len:0x50ec
load:0x40080400 len:0xd58
0x40080400: _init at ??:?
entry 0x40080638
W (358) flash_encrypt: Not disabling UART bootloader encryption
E (11393) esp_image: image at 0xf0000 has invalid magic byte (nothing flashed here?)
E (11393) esp_image: image at 0x1b0000 has invalid magic byte (nothing flashed here?)
ets Jul 29 2019 12:21:46
rst:0x3 (SW_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:2, clock div:2
secure boot v2 enabled
secure boot verification succeeded
load:0x3fff0030 len:0x27bc
load:0x40078000 len:0x50ec
load:0x40080400 len:0xd58
0x40080400: _init at ??:?
entry 0x40080638
No signature block magic byte found at signature sector (found 0xc2 not 0xe7). Image not V2 signed?
E (372) secure_boot_v2: Secure Boot V2 verification failed.
E (375) esp_image: Secure boot signature verification failed
此时读取了 efuse, 发现已经进行了 flash 加密。如下:
FLASH_CRYPT_CNT (BLOCK0): Flash encryption mode counter = 1 R/W (0b0000001)
UART_DOWNLOAD_DIS (BLOCK0): Disable UART download mode (ESP32 rev3 only) = False R/W (0b0)
FLASH_CRYPT_CONFIG (BLOCK0): Flash encryption config (key tweak bits) = 15 R/W (0xf)
CONSOLE_DEBUG_DISABLE (BLOCK0): Disable ROM BASIC interpreter fallback = True R/W (0b1)
ABS_DONE_0 (BLOCK0): Secure boot V1 is enabled for bootloader image = False R/W (0b0)
ABS_DONE_1 (BLOCK0): Secure boot V2 is enabled for bootloader image = True R/W (0b1)
JTAG_DISABLE (BLOCK0): Disable JTAG = True R/W (0b1)
DISABLE_DL_ENCRYPT (BLOCK0): Disable flash encryption in UART bootloader = False R/W (0b0)
DISABLE_DL_DECRYPT (BLOCK0): Disable flash decryption in UART bootloader = True R/W (0b1)
DISABLE_DL_CACHE (BLOCK0): Disable flash cache in UART bootloader = True R/W (0b1)
BLOCK1 (BLOCK1): Flash encryption key
= 4f f9 8a db 0d 78 67 a3 ca be 30 23 39 36 15 76 e8 4b b7 d1 1a 5e 02 75 c1 13 d3 57 5e 27 ba cf R/-
BLOCK2 (BLOCK2): Secure boot key
= 03 9b 4d b3 33 c8 7d 14 e2 30 6f a1 eb 6f e9 03 4e d6 a4 eb ed df 24 79 60 06 d4 2c bf 44 4f 96 R/-
BLOCK3 (BLOCK3): Variable Block 3
= 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 R/W
此时是因为 flash 加密已经启动,需要烧录被 flash 加密秘钥加密后的固件,输入以下指令:
idf.py -p /dev/ttyUSB0 encrypted-app-flash monitor
然后重新查看 log 。发现程序成功正常运行。这部分具体可以参考 此链接,如下: