Chapter 247: Partition Encryption Techniques
Chapter Objectives
By the end of this chapter, you will be able to:
- Understand the relationship between Flash Encryption and partition-level encryption.
- Enable and configure Flash Encryption for an ESP-IDF project.
- Mark specific partitions for encryption within a custom partition table.
- Understand the “first boot” process for enabling encryption and generating keys.
- Read from and write to encrypted partitions transparently.
- Recognize the security implications and limitations of Flash Encryption.
- Identify differences in encryption features across ESP32 variants.
Introduction
In the previous chapter, we learned how to organize flash memory by creating custom partition tables. While this gives us structural control, it does nothing to protect the data itself. If a malicious actor gains physical access to your device, they could read the raw contents of the flash chip, exposing sensitive information like Wi-Fi credentials, private keys, or proprietary firmware.
Partition encryption, built upon the ESP32’s powerful Flash Encryption feature, solves this problem. By enabling it, you can render the data in specified partitions unreadable without the device’s unique, hardware-fused key. This chapter will guide you through the theory, configuration, and practical application of encrypting individual partitions, adding a critical layer of physical security to your embedded projects.
Theory
Partition encryption is not a standalone feature; it is an integral part of the Flash Encryption security mechanism provided by all ESP32 variants. It’s essential to understand the core concepts of Flash Encryption before applying it to partitions.
1. What is Flash Encryption?
Flash Encryption is a hardware-accelerated feature that encrypts the contents of the SPI flash memory. When enabled, all data written to the flash is encrypted on-the-fly by a dedicated hardware block, and all data read is decrypted on-the-fly. The encryption key is stored in the device’s eFuses, which are a special type of one-time-programmable memory. Once a key is written to the eFuses and the corresponding lock bits are set, the key cannot be read out or modified via software, making it a secure, device-specific secret.
%%{init: {'theme': 'base', 'themeVariables': { 'fontFamily': '"Open Sans", sans-serif'}}}%% graph TD subgraph "CPU Domain (Plaintext)" A("Application Code<br>e.g., esp_partition_read()") B("Application Code<br>e.g., esp_partition_write()") end subgraph "Flash Controller (Hardware AES Engine)" C{Transparent<br>Decryption} D{Transparent<br>Encryption} end subgraph "External SPI Flash (Encrypted)" E["Encrypted Data<br>on Flash Chip"] end A -- "Requests Data" --> C C -- "Reads Encrypted Data" --> E E -- "Provides Encrypted Data" --> C C -- "Returns Plaintext Data" --> A B -- "Writes Plaintext Data" --> D D -- "Sends Encrypted Data" --> E %% Styling classDef cpu fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF classDef hardware fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6 classDef flash fill:#FEE2E2,stroke:#DC2626,stroke-width:1px,color:#991B1B class A,B cpu class C,D hardware class E flash
The entire process is transparent to the application. Your code, using standard functions like esp_partition_read()
and esp_partition_write()
, interacts with decrypted data. The hardware handles the cryptographic heavy lifting.
2. The Encryption Key
The encryption key is a 256-bit AES key. It can be generated in two ways:
- Device-Generated: On the very first boot after enabling Flash Encryption, if no key is pre-provisioned, the ESP32’s hardware random number generator (RNG) creates a key and burns it into the eFuses. This is the most common method for development and many production scenarios, as each device will have a unique key.
- Host-Generated: For production environments where all devices in a batch must share the same key, you can generate a key on a host computer and flash it to the device during the manufacturing process. This is an advanced use case.
Method | Device-Generated Key | Host-Generated Key |
---|---|---|
Process | On first boot, the ESP32’s hardware RNG creates a key and burns it to eFuses automatically. | A key is created on a PC, creating a .bin file, which is then flashed to the device during manufacturing before the first boot. |
Best For | Development, prototyping, and production where each device should have a unique identity. | Mass production where a fleet of devices must share the same firmware and key (e.g., for secure OTA). |
Uniqueness | Each device has a completely unique, unknown key. | All devices in a batch share one identical, known key. |
Pros | + Simple setup. + High security per device; a compromised key affects only one unit. |
+ Allows pre-encrypting firmware on the host. + Enables a single OTA update image for the whole fleet. |
Cons | – Requires per-device OTA image generation if updates need to be pre-encrypted. – Key is unknown to the host. |
– Key management is critical; if the host key is lost, updates are impossible. – A compromised key affects all devices. |
Warning: Once Flash Encryption is enabled and the key is burned into the eFuses, the process is irreversible. A device with Flash Encryption enabled can only boot encrypted firmware. You cannot disable it. Furthermore, if the eFuse key is lost or damaged, the encrypted data on the flash is irrecoverable.
3. How Partitions Get Encrypted
When you enable Flash Encryption, you can choose which partitions to encrypt. This is controlled by the flags
column in your partition table .csv
file.
By adding the encrypted
flag to a partition’s entry, you are telling the bootloader and the flashing tools that this partition’s contents must be encrypted on the flash chip.
- App Partitions (
app
): Application partitions are always encrypted if Flash Encryption is enabled, regardless of the flag. This is a fundamental security measure to protect your intellectual property. - Data Partitions (
data
): Data partitions are only encrypted if you explicitly add theencrypted
flag in the partition table. This gives you the flexibility to leave non-sensitive data partitions unencrypted to save on minor performance overhead. Partitions likenvs
andphy_init
are typically left unencrypted.
Partition Entry in CSV | Partition Type | Result when Flash Encryption is ON | Reasoning |
---|---|---|---|
ota_0, app, ota_0, , 1M, | app | Encrypted | All application partitions are always encrypted to protect firmware code, regardless of the flag. |
nvs, data, nvs, , 24K, | data | Not Encrypted | Data partitions are not encrypted by default. The encrypted flag is missing. |
storage, data, 0x99, , 256K, encrypted | data | Encrypted | The encrypted flag explicitly tells the system to encrypt this data partition. |
otadata, data, ota, , 8K, | data | Encrypted | Special case: The otadata and partition table itself are always encrypted with the app partitions for security. |
4. The First Boot: The Point of No Return
The process of enabling encryption is critical:
%%{init: {'theme': 'base', 'themeVariables': { 'fontFamily': '"Open Sans", sans-serif'}}}%% sequenceDiagram participant BTL as 1st Stage Bootloader participant eFuse as eFuse Controller participant Flash as SPI Flash Memory BTL->>Flash: Read App Header Flash-->>BTL: App built with encryption enabled BTL->>eFuse: Check encryption status eFuse-->>BTL: eFuse bits are not set (encryption is off) BTL->>BTL: <b>Point of No Return Begins</b> Note right of BTL: This happens ONLY ONCE! BTL->>eFuse: 1. Generate & Burn AES Key activate eFuse eFuse-->>BTL: Key is now fused deactivate eFuse BTL->>Flash: 2. Read plaintext partitions (app, flagged data, etc.) BTL->>BTL: Encrypt partitions in RAM BTL->>Flash: Write back encrypted partitions BTL->>eFuse: 3. Burn eFuse bits to<br>permanently enable Flash Encryption activate eFuse eFuse-->>BTL: Mode is now permanently ON deactivate eFuse BTL->>BTL: 4. Trigger system reset to<br>re-boot in encrypted mode
- Build: You build your project with Flash Encryption enabled in
menuconfig
. The build tools create an encrypted “plaintext” binary. - Flash: You flash the device as usual. At this stage, the binaries on the flash are not yet encrypted. The eFuses are also not yet programmed.
- First Boot: The device boots. The 1st stage bootloader detects that Flash Encryption is enabled in the app binary’s header. It then performs the one-time encryption process:
- It generates a new AES key (or uses a pre-flashed one).
- It burns this key into the eFuses.
- It encrypts the app partitions, the partition table itself, and any data partitions marked with the
encrypted
flag in-place. - Finally, it permanently sets the eFuse bits that enable Flash Encryption mode.
- Subsequent Boots: On every boot after this, the bootloader expects to find encrypted firmware and data. All read/write operations to the flash are now transparently decrypted/encrypted by the hardware.
Practical Examples
Let’s modify the project from the previous chapter to encrypt our custom storage
partition.
Example: Encrypting the ‘storage’ Partition
Our goal is to secure the data within the storage
partition we created previously.
1. Update the Custom Partition .csv
File
Modify your partitions_custom.csv
file to add the encrypted
flag to the storage
partition.
# ESP-IDF Partition Table
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, , 28K,
otadata, data, ota, , 8K,
ota_0, app, ota_0, , 1856K,
ota_1, app, ota_1, , 1856K,
storage, data, 0x99, , 256K, encrypted
This is the only change needed in the partition table. The build system will now know that this partition requires encryption.
2. Enable Flash Encryption in menuconfig
This is the most critical step.
- Open the Project Configuration menu (
>ESP-IDF: SDK Configuration editor (menuconfig)
). - Navigate to Security features —>.
- Check the box for
[*] Enable flash encryption on boot
. - Leave the
Flash Encryption Mode
asDevelopment (NOT SECURE)
. TheRelease
mode imposes stricter checks and is intended for production. For this example, development mode is sufficient. - Save the configuration and exit.
Tip: In development mode, the bootloader allows re-flashing of plaintext application binaries over an encrypted device. It re-encrypts the new binary on the next boot using the existing key. In
Release
mode, this is forbidden to prevent tampering.
3. The Code (No Changes Needed!)
Here is the beauty of transparent Flash Encryption. The C code from the previous chapter for writing to and reading from the storage
partition does not need to be changed at all.
Your main/main.c
file remains the same:
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_partition.h"
static const char *TAG = "ENCRYPT_PARTITION_EXAMPLE";
void app_main(void)
{
ESP_LOGI(TAG, "Starting Partition Encryption Example...");
const esp_partition_t *storage_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, 0x99, "storage");
if (!storage_partition) {
ESP_LOGE(TAG, "Failed to find 'storage' partition.");
while(1) { vTaskDelay(pdMS_TO_TICKS(1000)); }
} else {
ESP_LOGI(TAG, "Found 'storage' partition: size=0x%x, offset=0x%x", storage_partition->size, storage_partition->address);
}
// The esp_partition functions work identically on encrypted partitions.
// The hardware handles all encryption/decryption transparently.
const char *message = "This is a secret message on an encrypted partition!";
char read_buffer[128] = {0};
ESP_LOGI(TAG, "Writing secret data to encrypted partition...");
esp_err_t err = esp_partition_write(storage_partition, 0, message, strlen(message) + 1);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to write data: %s", esp_err_to_name(err));
} else {
ESP_LOGI(TAG, "Successfully wrote data.");
}
ESP_LOGI(TAG, "Reading secret data back...");
err = esp_partition_read(storage_partition, 0, read_buffer, sizeof(read_buffer));
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to read data: %s", esp_err_to_name(err));
} else {
ESP_LOGI(TAG, "Successfully read data: '%s'", read_buffer);
}
if (strcmp(message, read_buffer) == 0) {
ESP_LOGI(TAG, "Data verification successful! The secret is safe.");
} else {
ESP_LOGE(TAG, "Data verification FAILED!");
}
ESP_LOGI(TAG, "Example finished.");
}
4. Build, Flash, and Monitor the First Boot
This process is different from a normal flash. Pay close attention to the monitor output.
- Build: Build the project as usual.
- Flash: Flash the project to a clean (never-before-encrypted) ESP32.
- Monitor: Open the serial monitor immediately. You will see the special first-boot log messages.
The monitor output will look something like this:
...
I (32) boot: ESP-IDF v5.x-dirty 2nd stage bootloader
...
I (72) boot: Loading partition table
I (82) boot: Checking flash encryption...
I (89) boot: Detected flash encryption in menuconfig, but not enabled in eFuse.
I (89) boot: Therefore, enabling flash encryption...
I (95) boot: Generating new flash encryption key...
I (134) boot: Burned flash encryption key
I (134) boot: Writing EFUSE to enable flash encryption...
I (139) boot: Reading partition table from flash...
I (145) boot: Encrypting partition 2 at offset 0x10000...
I (314) boot: Encrypting partition 3 at offset 0x1a0000...
I (502) boot: Encrypting partition 4 at offset 0x3b0000...
I (528) boot: Flash encryption completed
I (533) boot: Resetting...
ets Jul 29 2019 12:21:46
...
rst:0x3 (SW_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
...
I (72) boot: Checking flash encryption...
I (73) boot: Flash encryption is enabled (Development mode)
...
I (288) ENCRYPT_PARTITION_EXAMPLE: Starting Partition Encryption Example...
I (298) ENCRYPT_PARTITION_EXAMPLE: Found 'storage' partition: size=0x40000, offset=0x3b0000
I (298) ENCRYPT_PARTITION_EXAMPLE: Writing secret data to encrypted partition...
I (318) ENCRYPT_PARTITION_EXAMPLE: Successfully wrote data.
I (318) ENCRYPT_PARTITION_EXAMPLE: Reading secret data back...
I (328) ENCRYPT_PARTITION_EXAMPLE: Successfully read data: 'This is a secret message on an encrypted partition!'
I (338) ENCRYPT_PARTITION_EXAMPLE: Data verification successful! The secret is safe.
I (348) ENCRYPT_PARTITION_EXAMPLE: Example finished.
The application runs perfectly, completely unaware that the underlying data on the flash chip is now scrambled by AES encryption.
Variant Notes
Flash Encryption is a core security feature available on all ESP32 variants, but there are minor implementation differences.
Feature | ESP32 (Classic) | ESP32-S2, S3, C3, C6, H2 (Newer Variants) |
---|---|---|
eFuse Key Blocks | Shares a single AES key block between Flash Encryption and Secure Boot. | More Robust: Has separate, dedicated eFuse key blocks for Flash Encryption and Secure Boot. |
Encryption Algorithm | AES-256 | AES-256, with additional hardware support. ESP32-S2/S3 also support AES-XTS mode. |
Configuration | Identical. The encrypted flag and menuconfig options are consistent across all variants. | Identical. ESP-IDF abstracts the hardware differences, providing a uniform user experience. |
Key Takeaway | Powerful and secure, but the design was refined in later models. | Enhanced hardware security design provides better isolation between core security features. |
- ESP32: Uses one AES key for both Flash Encryption and Secure Boot.
- ESP32-S2, S3, C3, C6, H2: These newer chips have dedicated, separate eFuse key blocks for Flash Encryption and Secure Boot, which is a more robust design. They also feature more advanced hardware support for cryptography. The ESP32-S2, for example, has an “XTS” mode which provides enhanced protection against data tampering.
- Configuration: The
menuconfig
options and theencrypted
flag in the partition table work identically across all variants. The ESP-IDF abstracts the underlying hardware differences.
The practical steps shown in this chapter apply universally. Always refer to the specific datasheet and Technical Reference Manual for your variant for the deepest architectural details.
Common Mistakes & Troubleshooting Tips
Mistake / Issue | Symptom(s) | Troubleshooting / Solution |
---|---|---|
Reading Flash with esptool.py | The read_flash command works, but the resulting binary file contains scrambled, unreadable data. | This is expected behavior. It proves that encryption is working. Data on the flash is encrypted and can only be decrypted by the ESP32’s internal hardware. |
Flashing an Encrypted Device in “Release” Mode | Flashing via idf.py flash fails with an error. The bootloader rejects the new firmware. | Solution: For development, always use Development Mode in menuconfig. Release mode requires securely signed OTA updates and prohibits direct flashing of plaintext binaries. |
Losing Host-Generated Key | You cannot produce new firmware binaries that will run on devices manufactured with a specific key. You are locked out from updating. | Prevention: Treat host-generated keys (.bin files) as highly sensitive secrets. Back them up securely in multiple locations. There is no recovery from a lost key. |
Device Stuck in a Boot Loop After First Encryption | The device continuously resets after the initial encryption process log, often with a “brownout detector” or “guru meditation” error. | Solution: The first boot encryption is power-intensive. Ensure the device has a stable, high-quality power supply (e.g., use a short, thick USB cable; avoid powering from a weak USB hub). |
Exercises
- Encrypting the NVS Partition: In many applications, the NVS partition holds sensitive data like Wi-Fi credentials. Modify your
partitions_custom.csv
to add theencrypted
flag to thenvs
partition. After flashing and the first boot, use the NVS example code to write a key-value pair and verify that the NVS library continues to function correctly, proving that the encryption is transparent. - Verify Encryption with
esptool
: Take the project from this chapter. After flashing and encrypting, use the command line toolesptool.py
to read the entire flash memory to a file (esptool.py --port COMx read_flash 0 4MB flash_dump.bin
). Openflash_dump.bin
in a hex editor. Find the offset of yourstorage
partition (from the monitor logs) and observe the random-looking, encrypted data. Compare this to the plaintext string in your code to visually confirm that the data is not readable.
Summary
- Partition encryption is a function of the master Flash Encryption feature.
- Enabling Flash Encryption is a one-time, irreversible process that burns a unique AES key into the device’s eFuses.
- Partitions are marked for encryption by adding the
encrypted
flag in the partition table CSV file. - Application partitions are always encrypted when the feature is on; data partitions are encrypted only if flagged.
- The encryption/decryption process is hardware-accelerated and transparent to the application code;
esp_partition
and other APIs work without modification. - The first boot after flashing with encryption enabled is a critical step where the device self-encrypts its storage.
- Always use Development Mode for development and be extremely careful with keys in Release Mode.
Further Reading
- ESP-IDF Flash Encryption Documentation: https://docs.espressif.com/projects/esp-idf/en/v5.2.1/security/flash-encryption.html
- ESP32 Technical Reference Manual (TRM): Contains detailed information on the AES hardware accelerator and eFuse block for your specific variant. (Links available on the Espressif website).
- Secure Boot Documentation: A related security feature that ensures only signed, authentic firmware can run on the device. https://docs.espressif.com/projects/esp-idf/en/v5.2.1/security/secure-boot-v2.html