Flash Encryption
This is a quick start guide to ESP32's flash encryption feature. Using application code as an example, it demonstrates how to test and verify flash encryption operations during development and production.
Note
In this guide, most used commands are in the form of idf.py secure-<command>
, which is a wrapper around corresponding espsecure.py <command>
. The idf.py
based commands provides more user-friendly experience, although may lack some of the advanced functionality of their espsecure.py
based counterparts.
Introduction
Flash encryption is intended for encrypting the contents of the ESP32's off-chip flash memory. Once this feature is enabled, firmware is flashed as plaintext, and then the data is encrypted in place on the first boot. As a result, physical readout of flash will not be sufficient to recover most flash contents.
Secure Boot is a separate feature which can be used together with flash encryption to create an even more secure environment.
Important
For production use, flash encryption should be enabled in the "Release" mode only.
Important
Enabling flash encryption limits the options for further updates of ESP32. Before using this feature, read the document and make sure to understand the implications.
Encrypted Partitions
With flash encryption enabled, the following types of data are encrypted by default:
Partition Table
Otadata
All
app
type partitions
Other types of data can be encrypted conditionally:
Any partition marked with the
encrypted
flag in the partition table. For details, see Encrypted Partition Flag.Secure Boot bootloader digest if Secure Boot is enabled (see below).
Relevant eFuses
The flash encryption operation is controlled by various eFuses available on ESP32. The list of eFuses and their descriptions is given in the table below. The names in eFuse column are also used by espefuse.py
tool and idf.py
based eFuse commands. For usage in the eFuse API, modify the name by adding ESP_EFUSE_
, for example: esp_efuse_read_field_bit (ESP_EFUSE_DISABLE_DL_ENCRYPT).
eFuse |
Description |
Bit Depth |
|
Controls actual number of block1 bits used to derive final 256-bit AES key. Possible values: |
2 |
|
AES key storage. |
256 bit key block |
|
Controls the AES encryption process. |
4 |
|
If set, disables flash encryption operation while running in Firmware Download mode. |
1 |
|
If set, disables flash decryption while running in UART Firmware Download mode. |
1 |
|
A \(2^n\) number that indicating whether the contents of flash have been encrypted.
With each successive unencrypted flash update (e.g., flashing a new unencrypted binary) and encryption of the flash (via the Enable flash encryption on boot option), the next MSB of |
7 |
Note
R/W access control is available for all the eFuse bits listed in the table above.
The default value of these bits is 0 after manufacturing.
Read and write access to eFuse bits is controlled by appropriate fields in the registers WR_DIS
and RD_DIS
. For more information on ESP32 eFuses, see eFuse manager. To change protection bits of eFuse field using idf.py
, use these two commands: efuse-read-protect and efuse-write-protect (idf.py based aliases of espefuse.py commands write_protect_efuse and read_protect_efuse). Example idf.py efuse-write-protect DISABLE_DL_ENCRYPT
.
Flash Encryption Process
Assuming that the eFuse values are in their default states and the second stage bootloader is compiled to support flash encryption, the flash encryption process executes as shown below:
On the first power-on reset, all data in flash is un-encrypted (plaintext). The first stage (ROM) bootloader loads the second stage bootloader.
Second stage bootloader reads the
FLASH_CRYPT_CNT
eFuse value (0b0000000
). Since the value is0
(even number of bits set), it configures and enables the flash encryption block. It also sets theFLASH_CRYPT_CONFIG
eFuse to 0xF. For more information on the flash encryption block, see ESP32 Technical Reference Manual > eFuse Controller (eFuse) > Flash Encryption Block [PDF].Second stage bootloader first checks if a valid key is already present in the eFuse (e.g., burned using espefuse tool), then the process of key generation is skipped and the same key is used for flash encryption process. Otherwise, Second stage bootloader uses RNG (random) module to generate an AES-256 bit key and then writes it into the
flash_encryption
eFuse. The key cannot be accessed via software as the write and read protection bits for theflash_encryption
eFuse are set. The flash encryption operations happen entirely by hardware, and the key cannot be accessed via software.Flash encryption block encrypts the flash contents - the second stage bootloader, applications and partitions marked as
encrypted
. Encrypting in-place can take time, up to a minute for large partitions.Second stage bootloader sets the first available bit in
FLASH_CRYPT_CNT
(0b0000001) to mark the flash contents as encrypted. Odd number of bits is set.For Development Mode, the second stage bootloader sets only the eFuse bits
DISABLE_DL_DECRYPT
andDISABLE_DL_CACHE
to allow the UART bootloader to re-flash encrypted binaries. Also, theFLASH_CRYPT_CNT
eFuse bits are NOT write-protected.For Release Mode, the second stage bootloader sets the eFuse bits
DISABLE_DL_ENCRYPT
,DISABLE_DL_DECRYPT
, andDISABLE_DL_CACHE
to 1 to prevent the UART bootloader from decrypting the flash contents. It also write-protects theFLASH_CRYPT_CNT
eFuse bits. To modify this behavior, see Enabling UART Bootloader Encryption/Decryption.The device is then rebooted to start executing the encrypted image. The second stage bootloader calls the flash decryption block to decrypt the flash contents and then loads the decrypted contents into IRAM.
During the development stage, there is a frequent need to program different plaintext flash images and test the flash encryption process. This requires that Firmware Download mode is able to load new plaintext images as many times as it might be needed. However, during manufacturing or production stages, Firmware Download mode should not be allowed to access flash contents for security reasons.
Hence, two different flash encryption configurations were created: for development and for production. For details on these configurations, see Section Flash Encryption Configuration.
Flash Encryption Configuration
The following flash encryption modes are available:
Development Mode - recommended for use only during development. In this mode, it is still possible to flash new plaintext firmware to the device, and the bootloader will transparently encrypt this firmware using the key stored in hardware. This allows, indirectly, to read out the plaintext of the firmware in flash.
Release Mode - recommended for manufacturing and production. In this mode, flashing plaintext firmware to the device without knowing the encryption key is no longer possible.
This section provides information on the mentioned flash encryption modes and step by step instructions on how to use them.
Development Mode
During development, you can encrypt flash using either an ESP32 generated key or external host-generated key.
Using ESP32 Generated Key
Development mode allows you to download multiple plaintext images using Firmware Download mode.
To test flash encryption process, take the following steps:
Ensure that you have an ESP32 device with default flash encryption eFuse settings as shown in Relevant eFuses.
See how to check ESP32 Flash Encryption Status.
In Project Configuration Menu, do the following:
Select encryption mode (Development mode by default).
Select UART ROM download mode (enabled by default). Note that for the ESP32 target, the choice is only available when CONFIG_ESP32_REV_MIN level is set to 3 (ESP32 V3).
Save the configuration and exit.
Enabling flash encryption will increase the size of bootloader, which might require updating partition table offset. See Bootloader Size.
Run the command given below to build and flash the complete images.
idf.py flash monitorNote
This command does not include any user files which should be written to the partitions on the flash memory. Please write them manually before running this command otherwise the files should be encrypted separately before writing.
This command will write to flash memory unencrypted images: the second stage bootloader, the partition table and applications. Once the flashing is complete, ESP32 will reset. On the next boot, the second stage bootloader encrypts: the second stage bootloader, application partitions and partitions marked as
encrypted
then resets. Encrypting in-place can take time, up to a minute for large partitions. After that, the application is decrypted at runtime and executed.
A sample output of the first ESP32 boot after enabling flash encryption is given below:
--- idf_monitor on /dev/cu.SLAB_USBtoUART 115200 ---
--- Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
ets Jun 8 2016 00:22:57
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:0x3fff0018,len:4
load:0x3fff001c,len:8452
load:0x40078000,len:13608
load:0x40080400,len:6664
entry 0x40080764
I (28) boot: ESP-IDF v4.0-dev-850-gc4447462d-dirty 2nd stage bootloader
I (29) boot: compile time 15:37:14
I (30) boot: Enabling RNG early entropy source...
I (35) boot: SPI Speed : 40MHz
I (39) boot: SPI Mode : DIO
I (43) boot: SPI Flash Size : 4MB
I (47) boot: Partition Table:
I (51) boot: ## Label Usage Type ST Offset Length
I (58) boot: 0 nvs WiFi data 01 02 0000a000 00006000
I (66) boot: 1 phy_init RF data 01 01 00010000 00001000
I (73) boot: 2 factory factory app 00 00 00020000 00100000
I (81) boot: End of partition table
I (85) esp_image: segment 0: paddr=0x00020020 vaddr=0x3f400020 size=0x0808c ( 32908) map
I (105) esp_image: segment 1: paddr=0x000280b4 vaddr=0x3ffb0000 size=0x01ea4 ( 7844) load
I (109) esp_image: segment 2: paddr=0x00029f60 vaddr=0x40080000 size=0x00400 ( 1024) load
0x40080000: _WindowOverflow4 at esp-idf/esp-idf/components/freertos/xtensa_vectors.S:1778
I (114) esp_image: segment 3: paddr=0x0002a368 vaddr=0x40080400 size=0x05ca8 ( 23720) load
I (132) esp_image: segment 4: paddr=0x00030018 vaddr=0x400d0018 size=0x126a8 ( 75432) map
0x400d0018: _flash_cache_start at ??:?
I (159) esp_image: segment 5: paddr=0x000426c8 vaddr=0x400860a8 size=0x01f4c ( 8012) load
0x400860a8: prvAddNewTaskToReadyList at esp-idf/esp-idf/components/freertos/tasks.c:4561
I (168) boot: Loaded app from partition at offset 0x20000
I (168) boot: Checking flash encryption...
I (168) flash_encrypt: Generating new flash encryption key...
I (187) flash_encrypt: Read & write protecting new key...
I (187) flash_encrypt: Setting CRYPT_CONFIG efuse to 0xF
W (188) flash_encrypt: Not disabling UART bootloader encryption
I (195) flash_encrypt: Disable UART bootloader decryption...
I (201) flash_encrypt: Disable UART bootloader MMU cache...
I (208) flash_encrypt: Disable JTAG...
I (212) flash_encrypt: Disable ROM BASIC interpreter fallback...
I (219) esp_image: segment 0: paddr=0x00001020 vaddr=0x3fff0018 size=0x00004 ( 4)
I (227) esp_image: segment 1: paddr=0x0000102c vaddr=0x3fff001c size=0x02104 ( 8452)
I (239) esp_image: segment 2: paddr=0x00003138 vaddr=0x40078000 size=0x03528 ( 13608)
I (249) esp_image: segment 3: paddr=0x00006668 vaddr=0x40080400 size=0x01a08 ( 6664)
I (657) esp_image: segment 0: paddr=0x00020020 vaddr=0x3f400020 size=0x0808c ( 32908) map
I (669) esp_image: segment 1: paddr=0x000280b4 vaddr=0x3ffb0000 size=0x01ea4 ( 7844)
I (672) esp_image: segment 2: paddr=0x00029f60 vaddr=0x40080000 size=0x00400 ( 1024)
0x40080000: _WindowOverflow4 at esp-idf/esp-idf/components/freertos/xtensa_vectors.S:1778
I (676) esp_image: segment 3: paddr=0x0002a368 vaddr=0x40080400 size=0x05ca8 ( 23720)
I (692) esp_image: segment 4: paddr=0x00030018 vaddr=0x400d0018 size=0x126a8 ( 75432) map
0x400d0018: _flash_cache_start at ??:?
I (719) esp_image: segment 5: paddr=0x000426c8 vaddr=0x400860a8 size=0x01f4c ( 8012)
0x400860a8: prvAddNewTaskToReadyList at esp-idf/esp-idf/components/freertos/tasks.c:4561
I (722) flash_encrypt: Encrypting partition 2 at offset 0x20000...
I (13229) flash_encrypt: Flash encryption completed
I (13229) boot: Resetting with flash encryption enabled...
A sample output of subsequent ESP32 boots just mentions that flash encryption is already enabled:
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:0x3fff0018,len:4
load:0x3fff001c,len:8452
load:0x40078000,len:13652
ho 0 tail 12 room 4
load:0x40080400,len:6664
entry 0x40080764
I (30) boot: ESP-IDF v4.0-dev-850-gc4447462d-dirty 2nd stage bootloader
I (30) boot: compile time 16:32:53
I (31) boot: Enabling RNG early entropy source...
I (37) boot: SPI Speed : 40MHz
I (41) boot: SPI Mode : DIO
I (45) boot: SPI Flash Size : 4MB
I (49) boot: Partition Table:
I (52) boot: ## Label Usage Type ST Offset Length
I (60) boot: 0 nvs WiFi data 01 02 0000a000 00006000
I (67) boot: 1 phy_init RF data 01 01 00010000 00001000
I (75) boot: 2 factory factory app 00 00 00020000 00100000
I (82) boot: End of partition table
I (86) esp_image: segment 0: paddr=0x00020020 vaddr=0x3f400020 size=0x0808c ( 32908) map
I (107) esp_image: segment 1: paddr=0x000280b4 vaddr=0x3ffb0000 size=0x01ea4 ( 7844) load
I (111) esp_image: segment 2: paddr=0x00029f60 vaddr=0x40080000 size=0x00400 ( 1024) load
0x40080000: _WindowOverflow4 at esp-idf/esp-idf/components/freertos/xtensa_vectors.S:1778
I (116) esp_image: segment 3: paddr=0x0002a368 vaddr=0x40080400 size=0x05ca8 ( 23720) load
I (134) esp_image: segment 4: paddr=0x00030018 vaddr=0x400d0018 size=0x126a8 ( 75432) map
0x400d0018: _flash_cache_start at ??:?
I (162) esp_image: segment 5: paddr=0x000426c8 vaddr=0x400860a8 size=0x01f4c ( 8012) load
0x400860a8: prvAddNewTaskToReadyList at esp-idf/esp-idf/components/freertos/tasks.c:4561
I (171) boot: Loaded app from partition at offset 0x20000
I (171) boot: Checking flash encryption...
I (171) flash_encrypt: flash encryption is enabled (3 plaintext flashes left)
I (178) boot: Disabling RNG early entropy source...
I (184) cpu_start: Pro cpu up.
I (188) cpu_start: Application information:
I (193) cpu_start: Project name: flash-encryption
I (198) cpu_start: App version: v4.0-dev-850-gc4447462d-dirty
I (205) cpu_start: Compile time: Jun 17 2019 16:32:52
I (211) cpu_start: ELF file SHA256: 8770c886bdf561a7...
I (217) cpu_start: ESP-IDF: v4.0-dev-850-gc4447462d-dirty
I (224) cpu_start: Starting app cpu, entry point is 0x40080e4c
0x40080e4c: call_start_cpu1 at esp-idf/esp-idf/components/esp32/cpu_start.c:265
I (0) cpu_start: App cpu up.
I (235) heap_init: Initializing. RAM available for dynamic allocation:
I (241) heap_init: At 3FFAE6E0 len 00001920 (6 KiB): DRAM
I (247) heap_init: At 3FFB2EC8 len 0002D138 (180 KiB): DRAM
I (254) heap_init: At 3FFE0440 len 00003AE0 (14 KiB): D/IRAM
I (260) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
I (266) heap_init: At 40087FF4 len 0001800C (96 KiB): IRAM
I (273) cpu_start: Pro cpu start user code
I (291) cpu_start: Starting scheduler on PRO CPU.
I (0) cpu_start: Starting scheduler on APP CPU.
Sample program to check Flash Encryption
This is ESP32 chip with 2 CPU cores, WiFi/BT/BLE, silicon revision 1, 4MB external flash
Flash encryption feature is enabled
Flash encryption mode is DEVELOPMENT
Flash in encrypted mode with flash_crypt_cnt = 1
Halting...
At this stage, if you need to update and re-flash binaries, see Re-flashing Updated Partitions.
Using Host Generated Key
It is possible to pre-generate a flash encryption key on the host computer and burn it into the eFuse. This allows you to pre-encrypt data on the host and flash already encrypted data without needing a plaintext flash update. This feature can be used in both Development Mode and Release Mode. Without a pre-generated key, data is flashed in plaintext and then ESP32 encrypts the data in-place.
Note
This option is not recommended for production, unless a separate key is generated for each individual device.
To use a host generated key, take the following steps:
Ensure that you have an ESP32 device with default flash encryption eFuse settings as shown in Relevant eFuses.
See how to check ESP32 Flash Encryption Status.
Generate a random key by running:
idf.py secure-generate-flash-encryption-key my_flash_encryption_key.bin
Before the first encrypted boot, burn the key into your device's eFuse using the command below. This action can be done only once.
idf.py --port PORT efuse-burn-key flash_encryption my_flash_encryption_key.binIf the key is not burned and the device is started after enabling flash encryption, the ESP32 will generate a random key that software cannot access or modify.
In Project Configuration Menu, do the following:
Select encryption mode (Development mode by default)
Save the configuration and exit.
Enabling flash encryption will increase the size of bootloader, which might require updating partition table offset. See Bootloader Size.
Run the command given below to build and flash the complete images.
idf.py flash monitorNote
This command does not include any user files which should be written to the partitions on the flash memory. Please write them manually before running this command otherwise the files should be encrypted separately before writing.
This command will write to flash memory unencrypted images: the second stage bootloader, the partition table and applications. Once the flashing is complete, ESP32 will reset. On the next boot, the second stage bootloader encrypts: the second stage bootloader, application partitions and partitions marked as
encrypted
then resets. Encrypting in-place can take time, up to a minute for large partitions. After that, the application is decrypted at runtime and executed.
If using Development Mode, then the easiest way to update and re-flash binaries is Re-flashing Updated Partitions.
If using Release Mode, then it is possible to pre-encrypt the binaries on the host and then flash them as ciphertext. See Manually Encrypting Files.
Re-flashing Updated Partitions
If you update your application code (done in plaintext) and want to re-flash it, you will need to encrypt it before flashing. To encrypt the application and flash it in one step, run:
idf.py encrypted-app-flash monitor
If all partitions needs to be updated in encrypted format, run:
idf.py encrypted-flash monitor
Release Mode
In Release mode, UART bootloader cannot perform flash encryption operations. New plaintext images can ONLY be downloaded using the over-the-air (OTA) scheme which will encrypt the plaintext image before writing to flash.
To use this mode, take the following steps:
Ensure that you have an ESP32 device with default flash encryption eFuse settings as shown in Relevant eFuses.
See how to check ESP32 Flash Encryption Status.
In Project Configuration Menu, do the following:
Select Release mode (Note that once Release mode is selected, the
DISABLE_DL_ENCRYPT
andDISABLE_DL_DECRYPT
eFuse bits will be burned to disable flash encryption hardware in ROM Download Mode.)Select UART ROM download mode (Permanently disabled (recommended)) (Note that this option is only available when CONFIG_ESP32_REV_MIN is set to 3 (ESP32 V3).) The default choice is to keep UART ROM download mode enabled, however it is recommended to permanently disable this mode to reduce the options available to an attacker.
Save the configuration and exit.
Enabling flash encryption will increase the size of bootloader, which might require updating partition table offset. See Bootloader Size.
Run the command given below to build and flash the complete images.
idf.py flash monitorNote
This command does not include any user files which should be written to the partitions on the flash memory. Please write them manually before running this command otherwise the files should be encrypted separately before writing.
This command will write to flash memory unencrypted images: the second stage bootloader, the partition table and applications. Once the flashing is complete, ESP32 will reset. On the next boot, the second stage bootloader encrypts: the second stage bootloader, application partitions and partitions marked as
encrypted
then resets. Encrypting in-place can take time, up to a minute for large partitions. After that, the application is decrypted at runtime and executed.
Once the flash encryption is enabled in Release mode, the bootloader will write-protect the FLASH_CRYPT_CNT
eFuse.
For subsequent plaintext field updates, use OTA scheme.
Note
If you have pre-generated the flash encryption key and stored a copy, and the UART download mode is not permanently disabled via CONFIG_SECURE_UART_ROM_DL_MODE (ESP32 V3 only), then it is possible to update the flash locally by pre-encrypting the files and then flashing the ciphertext. See Manually Encrypting Files.
Best Practices
When using Flash Encryption in production:
Do not reuse the same flash encryption key between multiple devices. This means that an attacker who copies encrypted data from one device cannot transfer it to a second device.
When using ESP32 V3, if the UART ROM Download Mode is not needed for a production device then it should be disabled to provide an extra level of protection. Do this by calling
esp_efuse_disable_rom_download_mode()
during application startup. Alternatively, configure the project CONFIG_ESP32_REV_MIN level to 3 (targeting ESP32 V3 only) and select the CONFIG_SECURE_UART_ROM_DL_MODE to "Permanently disable ROM Download Mode (recommended)". The ability to disable ROM Download Mode is not available on earlier ESP32 versions.Enable Secure Boot as an extra layer of protection, and to prevent an attacker from selectively corrupting any part of the flash before boot.
Enable Flash Encryption Externally
In the process mentioned above, flash encryption related eFuses which ultimately enable flash encryption are programmed through the second stage bootloader. Alternatively, all the eFuses can be programmed with the help of espefuse
tool. Please refer Enable Flash Encryption Externally for more details.
Possible Failures
Once flash encryption is enabled, the FLASH_CRYPT_CNT
eFuse value will have an odd number of bits set. It means that all the partitions marked with the encryption flag are expected to contain encrypted ciphertext. Below are the three typical failure cases if the ESP32 is erroneously loaded with plaintext data:
If the bootloader partition is re-flashed with a plaintext second stage bootloader image, the first stage (ROM) bootloader will fail to load the second stage bootloader resulting in the following failure:
rst:0x3 (SW_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
flash read err, 1000
ets_main.c 371
ets Jun 8 2016 00:22:57
rst:0x7 (TG0WDT_SYS_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
flash read err, 1000
ets_main.c 371
ets Jun 8 2016 00:22:57
rst:0x7 (TG0WDT_SYS_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
flash read err, 1000
ets_main.c 371
ets Jun 8 2016 00:22:57
rst:0x7 (TG0WDT_SYS_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
flash read err, 1000
ets_main.c 371
ets Jun 8 2016 00:22:57
rst:0x7 (TG0WDT_SYS_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
flash read err, 1000
ets_main.c 371
ets Jun 8 2016 00:22:57
Note
This error also appears if the flash contents are erased or corrupted.
If the second stage bootloader is encrypted, but the partition table is re-flashed with a plaintext partition table image, the bootloader will fail to read the partition table resulting in the following failure:
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:2 load:0x3fff0018,len:4 load:0x3fff001c,len:10464 ho 0 tail 12 room 4 load:0x40078000,len:19168 load:0x40080400,len:6664 entry 0x40080764 I (60) boot: ESP-IDF v4.0-dev-763-g2c55fae6c-dirty 2nd stage bootloader I (60) boot: compile time 19:15:54 I (62) boot: Enabling RNG early entropy source... I (67) boot: SPI Speed : 40MHz I (72) boot: SPI Mode : DIO I (76) boot: SPI Flash Size : 4MB E (80) flash_parts: partition 0 invalid magic number 0x94f6 E (86) boot: Failed to verify partition table E (91) boot: load partition table error!
If the bootloader and partition table are encrypted, but the application is re-flashed with a plaintext application image, the bootloader will fail to load the application resulting in the following failure:
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:2 load:0x3fff0018,len:4 load:0x3fff001c,len:8452 load:0x40078000,len:13616 load:0x40080400,len:6664 entry 0x40080764 I (56) boot: ESP-IDF v4.0-dev-850-gc4447462d-dirty 2nd stage bootloader I (56) boot: compile time 15:37:14 I (58) boot: Enabling RNG early entropy source... I (64) boot: SPI Speed : 40MHz I (68) boot: SPI Mode : DIO I (72) boot: SPI Flash Size : 4MB I (76) boot: Partition Table: I (79) boot: ## Label Usage Type ST Offset Length I (87) boot: 0 nvs WiFi data 01 02 0000a000 00006000 I (94) boot: 1 phy_init RF data 01 01 00010000 00001000 I (102) boot: 2 factory factory app 00 00 00020000 00100000 I (109) boot: End of partition table E (113) esp_image: image at 0x20000 has invalid magic byte W (120) esp_image: image at 0x20000 has invalid SPI mode 108 W (126) esp_image: image at 0x20000 has invalid SPI size 11 E (132) boot: Factory app partition is not bootable E (138) boot: No bootable app partitions in the partition table
ESP32 Flash Encryption Status
Ensure that you have an ESP32 device with default flash encryption eFuse settings as shown in Relevant eFuses.
To check if flash encryption on your ESP32 device is enabled, do one of the following:
flash the application example security/flash_encryption onto your device. This application prints the
FLASH_CRYPT_CNT
eFuse value and if flash encryption is enabled or disabled.Find the serial port name under which your ESP32 device is connected, replace
PORT
with your port name in the following command, and run it:idf.py efuse-summary
Reading and Writing Data in Encrypted Flash
ESP32 application code can check if flash encryption is currently enabled by calling esp_flash_encryption_enabled()
. Also, a device can identify the flash encryption mode by calling esp_get_flash_encryption_mode()
.
Once flash encryption is enabled, be more careful with accessing flash contents from code.
Scope of Flash Encryption
Whenever the FLASH_CRYPT_CNT
eFuse is set to a value with an odd number of bits, all flash content accessed via the MMU's flash cache is transparently decrypted. It includes:
Executable application code in flash (IROM).
All read-only data stored in flash (DROM).
Any data accessed via
spi_flash_mmap()
.The second stage bootloader image when it is read by the first stage (ROM) bootloader.
Important
The MMU flash cache unconditionally decrypts all existing data. Data which is stored unencrypted in flash memory will also be "transparently decrypted" via the flash cache and will appear to software as random garbage.
Reading from Encrypted Flash
To read data without using a flash cache MMU mapping, you can use the partition read function esp_partition_read()
. This function will only decrypt data when it is read from an encrypted partition. Data read from unencrypted partitions will not be decrypted. In this way, software can access encrypted and non-encrypted flash in the same way.
You can also use the following SPI flash API functions:
esp_flash_read()
to read raw (encrypted) data which will not be decryptedesp_flash_read_encrypted()
to read and decrypt data
Data stored using the Non-Volatile Storage (NVS) API is always stored and read decrypted from the perspective of flash encryption. It is up to the library to provide encryption feature if required. Refer to NVS Encryption for more details.
Writing to Encrypted Flash
It is recommended to use the partition write function esp_partition_write()
. This function will only encrypt data when it is written to an encrypted partition. Data written to unencrypted partitions will not be encrypted. In this way, software can access encrypted and non-encrypted flash in the same way.
You can also pre-encrypt and write data using the function esp_flash_write_encrypted()
Also, the following ROM function exist but not supported in esp-idf applications:
esp_rom_spiflash_write_encrypted
pre-encrypts and writes data to flashSPIWrite
writes unencrypted data to flash
Since data is encrypted in blocks, the minimum write size for encrypted data is 16 bytes and the alignment is also 16 bytes.
Updating Encrypted Flash
OTA Updates
OTA updates to encrypted partitions will automatically write encrypted data if the function esp_partition_write()
is used.
Before building the application image for OTA updating of an already encrypted device, enable the option Enable flash encryption on boot in project configuration menu.
For general information about ESP-IDF OTA updates, please refer to OTA.
Updating Encrypted Flash via Serial
Flashing an encrypted device via serial bootloader requires that the serial bootloader download interface has not been permanently disabled via eFuse.
In Development Mode, the recommended method is Re-flashing Updated Partitions.
In Release Mode, if a copy of the same key stored in eFuse is available on the host then it is possible to pre-encrypt files on the host and then flash them. See Manually Encrypting Files.
Disabling Flash Encryption
If flash encryption was enabled accidentally, flashing of plaintext data will soft-brick the ESP32. The device will reboot continuously, printing the error flash read err, 1000
or invalid header: 0xXXXXXX
.
For flash encryption in Development mode, encryption can be disabled by burning the FLASH_CRYPT_CNT
eFuse. It can only be done three times per chip by taking the following steps:
In Project Configuration Menu, disable Enable flash encryption on boot, then save and exit.
Open project configuration menu again and double-check that you have disabled this option! If this option is left enabled, the bootloader will immediately re-enable encryption when it boots.
With flash encryption disabled, build and flash the new bootloader and application by running
idf.py flash
.Use
idf.py
to disable theFLASH_CRYPT_CNT
by running:
idf.py efuse-burn FLASH_CRYPT_CNT
Reset the ESP32. Flash encryption will be disabled, and the bootloader will boot as usual.
Key Points About Flash Encryption
Flash memory contents is encrypted using AES-256. The flash encryption key is stored in the
flash_encryption
eFuse internal to the chip and, by default, is protected from software access.The flash encryption algorithm is AES-256, where the key is "tweaked" with the offset address of each 32 byte block of flash. This means that every 32-byte block (two consecutive 16 byte AES blocks) is encrypted with a unique key derived from the flash encryption key.
Flash access is transparent via the flash cache mapping feature of ESP32 - any flash regions which are mapped to the address space will be transparently decrypted when read.
Some data partitions might need to remain unencrypted for ease of access or might require the use of flash-friendly update algorithms which are ineffective if the data is encrypted. NVS partitions for non-volatile storage cannot be encrypted since the NVS library is not directly compatible with flash encryption. For details, refer to NVS Encryption.
If flash encryption might be used in future, the programmer must keep it in mind and take certain precautions when writing code that uses encrypted flash.
If secure boot is enabled, re-flashing the bootloader of an encrypted device requires a "Re-flashable" secure boot digest (see Flash Encryption and Secure Boot).
Enabling flash encryption will increase the size of bootloader, which might require updating partition table offset. See Bootloader Size.
Important
Do not interrupt power to the ESP32 while the first boot encryption pass is running. If power is interrupted, the flash contents will be corrupted and will require flashing with unencrypted data again. In this case, re-flashing will not count towards the flashing limit.
Limitations of Flash Encryption
Flash encryption protects firmware against unauthorised readout and modification. It is important to understand the limitations of the flash encryption feature:
Flash encryption is only as strong as the key. For this reason, we recommend keys are generated on the device during first boot (default behaviour). If generating keys off-device, ensure proper procedure is followed and do not share the same key between all production devices.
Not all data is stored encrypted. If storing data on flash, check if the method you are using (library, API, etc.) supports flash encryption.
Flash encryption does not prevent an attacker from understanding the high-level layout of the flash. This is because the same AES key is used for every pair of adjacent 16 byte AES blocks. When these adjacent 16 byte blocks contain identical content (such as empty or padding areas), these blocks will encrypt to produce matching pairs of encrypted blocks. This may allow an attacker to make high-level comparisons between encrypted devices (i.e., to tell if two devices are probably running the same firmware version).
For the same reason, an attacker can always tell when a pair of adjacent 16 byte blocks (32 byte aligned) contain two identical 16 byte sequences. Keep this in mind if storing sensitive data on the flash, design your flash storage so this does not happen (using a counter byte or some other non-identical value every 16 bytes is sufficient). NVS Encryption deals with this and is suitable for many uses.
Flash encryption alone may not prevent an attacker from modifying the firmware of the device. To prevent unauthorised firmware from running on the device, use flash encryption in combination with Secure Boot.
Flash Encryption and Secure Boot
It is recommended to use flash encryption in combination with Secure Boot. However, if Secure Boot is enabled, additional restrictions apply to device re-flashing:
OTA Updates are not restricted, provided that the new app is signed correctly with the Secure Boot signing key.
Plaintext serial flash updates are only possible if the Re-flashable Secure Boot mode is selected and a Secure Boot key was pre-generated and burned to the ESP32 (refer to Secure Boot). In such configuration,
idf.py bootloader
will produce a pre-digested bootloader and secure boot digest file for flashing at offset 0x0. When following the plaintext serial re-flashing steps it is necessary to re-flash this file before flashing other plaintext data.Re-flashing via Pregenerated Flash Encryption Key is still possible, provided the bootloader is not re-flashed. Re-flashing the bootloader requires the same Re-flashable option to be enabled in the Secure Boot config.
Advanced Features
The following section covers advanced features of flash encryption.
Encrypted Partition Flag
Some partitions are encrypted by default. Other partitions can be marked in the partition table description as requiring encryption by adding the flag encrypted
to the partitions' flag field. As a result, data in these marked partitions will be treated as encrypted in the same manner as an app partition.
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x6000
phy_init, data, phy, 0xf000, 0x1000
factory, app, factory, 0x10000, 1M
secret_data, 0x40, 0x01, 0x20000, 256K, encrypted
For details on partition table description, see partition table.
Further information about encryption of partitions:
Default partition tables do not include any encrypted data partitions.
With flash encryption enabled, the
app
partition is always treated as encrypted and does not require marking.If flash encryption is not enabled, the flag "encrypted" has no effect.
You can also consider protecting
phy_init
data from physical access, readout, or modification, by marking the optionalphy
partition with the flagencrypted
.The
nvs
partition cannot be encrypted, because the NVS library is not directly compatible with flash encryption.
Enabling UART Bootloader Encryption/Decryption
On the first boot, the flash encryption process burns by default the following eFuses:
DISABLE_DL_ENCRYPT
which disables flash encryption operation when running in UART bootloader boot mode.DISABLE_DL_DECRYPT
which disables transparent flash decryption when running in UART bootloader mode, even if the eFuseFLASH_CRYPT_CNT
is set to enable it in normal operation.DISABLE_DL_CACHE
which disables the entire MMU flash cache when running in UART bootloader mode.
However, before the first boot you can choose to keep any of these features enabled by burning only selected eFuses and write-protect the rest of eFuses with unset value 0. For example:
idf.py --port PORT efuse-burn DISABLE_DL_DECRYPT
idf.py --port PORT efuse-write-protect DISABLE_DL_ENCRYPT
Important
Leaving DISABLE_DL_DECRYPT
unset (0) makes flash encryption useless.
An attacker with physical access to the chip can use UART bootloader mode with custom stub code to read out the flash contents.
Setting FLASH_CRYPT_CONFIG
The eFuse FLASH_CRYPT_CONFIG
determines the number of bits in the flash encryption key which are "tweaked" with the block offset. For details, see Flash Encryption Algorithm.
On the first boot of the second stage bootloader, this value is set to the maximum 0xF
.
It is possible to burn this eFuse manually and write protect it before the first boot in order to select different tweak values. However, this is not recommended.
It is strongly recommended to never write-protect FLASH_CRYPT_CONFIG
when it is unset. Otherwise, its value will remain zero permanently, and no bits in the flash encryption key will be tweaked. As a result, the flash encryption algorithm will be equivalent to AES ECB mode.
JTAG Debugging
By default, when Flash Encryption is enabled (in either Development or Release mode) then JTAG debugging is disabled via eFuse. The bootloader does this on first boot, at the same time it enables flash encryption.
See JTAG with Flash Encryption or Secure Boot for more information about using JTAG Debugging with Flash Encryption.
Manually Encrypting Files
Manually encrypting or decrypting files requires the flash encryption key to be pre-burned in eFuse (see Using Host Generated Key) and a copy to be kept on the host. If the flash encryption is configured in Development Mode then it is not necessary to keep a copy of the key or follow these steps, the simpler Re-flashing Updated Partitions steps can be used.
The key file should be a single raw binary file (example: key.bin
).
For example, these are the steps to encrypt the file my-app.bin
to flash at offset 0x10000. Run idf.py
as follows:
idf.py secure-encrypt-flash-data --keyfile /path/to/key.bin --address 0x10000 --output my-app-ciphertext.bin my-app.bin
The file my-app-ciphertext.bin
can then be flashed to offset 0x10000 using esptool.py
. To see all of the command line options recommended for esptool.py
, see the output printed when idf.py build
succeeds.
Note
If the flashed ciphertext file is not recognized by the ESP32 when it boots, check that the keys match and that the command line arguments match exactly, including the correct offset.
If your ESP32 uses non-default FLASH_CRYPT_CONFIG value in eFuse then you will need to pass the --flash-crypt-conf
argument to idf.py
command to set the matching value. This will not happen if the device configured flash encryption by itself, but may happen if burning eFuses manually to enable flash encryption.
The command idf.py decrypt-flash-data
can be used with the same options (and different input/output files), to decrypt ciphertext flash contents or a previously encrypted file.
Technical Details
The following sections provide some reference information about the operation of flash encryption.
Flash Encryption Algorithm
AES-256 operates on 16-byte blocks of data. The flash encryption engine encrypts and decrypts data in 32-byte blocks - two AES blocks in series.
The main flash encryption key is stored in the
flash_encryption
eFuse and, by default, is protected from further writes or software readout.AES-256 key size is 256 bits (32 bytes) read from the
flash_encryption
eFuse. The hardware AES engine uses the key in reversed byte order as compared to the storage order inflash_encryption
.If the
CODING_SCHEME
eFuse is set to0
(default, "None" Coding Scheme) then the eFuse key block is 256 bits and the key is stored as-is (in reversed byte order).If the
CODING_SCHEME
eFuse is set to1
(3/4 Encoding) then the eFuse key block is 192 bits (in reversed byte order), so overall entropy is reduced. The hardware flash encryption still operates on a 256-bit key, after being read (and un-reversed), the key is extended askey = key[0:255] + key[64:127]
.
AES algorithm is used inverted in flash encryption, so the flash encryption "encrypt" operation is AES decrypt and the "decrypt" operation is AES encrypt. This is for performance reasons and does not alter the efficiency of the algorithm.
Each 32-byte block (two adjacent 16-byte AES blocks) is encrypted with a unique key. The key is derived from the main flash encryption key in
flash_encryption
, XORed with the offset of this block in the flash (a "key tweak").The specific tweak depends on the
FLASH_CRYPT_CONFIG
eFuse setting. This is a 4-bit eFuse where each bit enables XORing of a particular range of the key bits:Bit 1, bits 0-66 of the key are XORed.
Bit 2, bits 67-131 of the key are XORed.
Bit 3, bits 132-194 of the key are XORed.
Bit 4, bits 195-256 of the key are XORed.
It is recommended that
FLASH_CRYPT_CONFIG
is always left at the default value0xF
, so that all key bits are XORed with the block offset. For details, see Setting FLASH_CRYPT_CONFIG.The high 19 bits of the block offset (bit 5 to bit 23) are XORed with the main flash encryption key. This range is chosen for two reasons: the maximum flash size is 16MB (24 bits), and each block is 32 bytes so the least significant 5 bits are always zero.
There is a particular mapping from each of the 19 block offset bits to the 256 bits of the flash encryption key to determine which bit is XORed with which. See the variable
_FLASH_ENCRYPTION_TWEAK_PATTERN
in theespsecure.py
source code for complete mapping.To see the full flash encryption algorithm implemented in Python, refer to the
_flash_encryption_operation()
function in theespsecure.py
source code.