ESP32 nvs 加密

由于部分用户在 nvs 中存储了一些安全性需求较高的数据,比如 cloud 对应的秘钥。希望在 flash 加密时同时开启 nvs 加密。此篇文章着重描述 nvs 加密需要进行的流程,大体分为以下三步:

  1. 编写需要生成的自定义 nvs 键值对
  2. 生成 nvs.bin 的 key 与加密后的 nvs.bin 进行加密
  3. 烧录加密后的 nvs.bin 和 nvs key 到 flash

注意:nvs 加密同时需要修改分区表,具体会在后文说明。

nvs 加密对应的指令可以简要概括如下:

  1. 使用 nvs_partition_gen.py 生成 nvs_key.bin 用于加密 nvs 区域:
    python nvs_partition_gen.py generate-key --keyfile nvs_key.bin

  2. 使用 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

  3. 加密烧录 MAC_nvs_key.bin
    esptool_3.0.exe -p COM157 write_flash --encrypt 0xff000 ./nvs_key.bin

  4. 烧录 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 。发现程序成功正常运行。这部分具体可以参考 此链接,如下:
请添加图片描述

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值