Hi,
I despereately try to get “secure boot” and “flash encryption” working on my ESP32S3 WROOM 1U N16.
There is this “egg and chicken” problem:
I am only able to test this bye “enable” and “burn” the efuses for
a) secure boot
b) flash encryption
But by enabling and burning the efuses with a false encryption, then I am stuck and the device is “kaput” !!
When I run the project without secure boot and flash encryption, then everyting works fine.
Only when I try to introduce secure-boot and flash-encryption, things get broken!
I have established all this (see code below):
- script 1: does the key generation for secure-boot and flash-encryption
- script 2: does the flash burning and after asking, does the efuse enable and burn
- platformio.ini file ready
Here are the Arduino IDE settings of my board:
Note: The ESP32S3 WROOM 1U N16
device has WiFi and Bluetooth.
Board: ESP32S3 Dev Module
Upload Speed: 921600
USB Mode: Hardware CDC and JTAG
USB CDC On Boot: Enabled (connects Serial-Port automatically)
USB FW MSC on Boot: Disabled
USB DFU On Boot: Disabled
Upload Mode: UART0 / Hardware CDC
CPU Frequency: 240MHz (WiFi)
Flash Mode: QIO 80MHy
Flash Size: 16MB (128 Mb)
Partition Scheme: No OTA (2M APP, 2M SPIFFS) // too tight: Default 4MB with spiffs (1.2MB APP/1.5MB SPIFFS)
Core Debug Level: None
PSRAM: Disabled
Arduino Runs On: Core 1
Events Run On: Core 1
Erase All Flash Before Sketch Upload: Enabled
JTAG Adapter: Disabled
Zigbee Mode: Disabled
Here is the platformio.ini
file:
[env:esp32s3dev]
platform = espressif32
board = esp32-s3-devkitc-1
framework = arduino
upload_speed = 921600
monitor_speed = 115200
board_build.mcu = esp32s3
board_build.cpu = esp32s3
board_build.f_cpu = 240000000L
board_build.flash_mode = qio
board_build.flash_freq = 80m
board_build.flash_size = 16MB
board_build.partitions = partitions.csv
lib_ldf_mode = deep+
lib_deps =
ESP32Time@^2.0.6
ArduinoJson@^7.4.1
GxEPD2@^1.6.4
U8g2_for_Adafruit_GFX@^1.8.0
Adafruit GFX Library@^1.12.1
build_flags =
; ---- Arduino USB Config ----
-D ARDUINO_USB_CDC_ON_BOOT=1
-D ARDUINO_USB_MODE=1 ; USB Mode: CDC + JTAG
-D CONFIG_ARDUINO_RUNNING_CORE=1
-D CONFIG_ARDUINO_EVENT_RUNNING_CORE=1
; Debug level: none
-D CORE_DEBUG_LEVEL=0
; ---- Secure Boot V2 ----
-D CONFIG_SECURE_BOOT=y
-D CONFIG_SECURE_BOOT_V2_ENABLED=y
-D CONFIG_SECURE_SIGNED_BINARIES=y
-D CONFIG_SECURE_BOOT_ALLOW_ROM_BASIC=y
-D CONFIG_SECURE_BOOT_V2_EFUSE_KEY_BLOCK_KEY0=y
-D CONFIG_SECURE_BOOT_V2_EFUSE_KEY_DIS_WRITE_PROTECT=y
; ---- Flash Encryption ----
-D CONFIG_FLASH_ENCRYPTION_ENABLED=y
-D FLASH_ENCRYPTION_ENABLED=1
; ---- NVS Encryption ----
-D CONFIG_NVS_ENCRYPTION=n
; ---- Bootloader Config ----
-D CONFIG_BOOTLOADER_LOG_LEVEL=0
upload_protocol = esptool
Here is my partitions.csv file:
# Name, Type, SubType, Offset, Size
nvs, data, nvs, 0x9000, 0x6000
phy_init, data, phy, 0xf000, 0x1000
nvs_key, data, nvs_keys,0x10000, 0x1000
app0, app, factory, 0x20000, 4M
spiffs, data, spiffs, 0x420000, 2M
I don’t have any sdkconfig file!
Here is script 1: key generation:
#!/bin/bash
set -e
# === USB PORT ===
PORT="/dev/cu.usbmodem213101"
# === ESP-IDF PATH ===
KEY_DIR="./secure"
BUILD_DIR=".pio/build/esp32s3dev"
NVS_KEY="$(pwd)/$KEY_DIR/nvs_encryption_key.bin"
NVS_KEY_FILE="$KEY_DIR/nvs_encryption_key.bin"
BOOT_APP0="/Users/myapp/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin"
FLASH_KEY="$KEY_DIR/flash_encryption_key.bin"
BOOT_SIGN_KEY="$KEY_DIR/secure_boot_private_key.pem"
BOOT_DIGEST="$KEY_DIR/secure_boot_public_key_digest.bin"
OUTPUT_DIR="./bin"
mkdir -p "$OUTPUT_DIR"
# === Step 1: Build firmware ===
echo "🔧 Clean target..."
pio run --target clean
echo "🔧 Building firmware..."
pio run
# /////////////////// SECURE BOOT ///////////////////
echo "🔐 Secure boot setup..."
# === Step 1: Generate secure boot signing key if missing ===
if [ ! -f "$BOOT_SIGN_KEY" ]; then
echo "🔏 Generating secure boot signing key..."
espsecure.py generate_signing_key --version 2 $BOOT_SIGN_KEY
else
echo "🔏 Secure boot signing key already exists."
fi
# === Step 2: Sign bootloader ===
echo "✍️ Signing bootloader..."
espsecure.py sign_data \
--version 2 \
--keyfile "$BOOT_SIGN_KEY" \
--output "$OUTPUT_DIR/bootloader-signed.bin" \
"$BUILD_DIR/bootloader.bin"
# === Step 3: Create public key digest for eFuse if not exists ===
if [ ! -f "$BOOT_DIGEST" ]; then
echo "📥 Generating secure boot public key digest..."
espsecure.py digest_sbv2_public_key \
--key $BOOT_SIGN_KEY \
--output $BOOT_DIGEST
fi
# # === Step 3b: Copy signed bootloader to output directory ===
echo "📁 Copying bootloader..."
cp "$BUILD_DIR/bootloader.bin" "$OUTPUT_DIR/"
# === Step 4: Copy partitions.bin ===
echo "📁 Copying required binaries..."
cp "$BUILD_DIR/partitions.bin" "$OUTPUT_DIR/"
# === Step 5: Copy bootapp0.bin ===
if [ -f "$BOOT_APP0" ]; then
echo "📁 Copying boot_app0.bin... "
cp "$BOOT_APP0" "$OUTPUT_DIR/boot_app0.bin"
else
echo "⚠️ Warning: boot_app0.bin not found at $BOOT_APP0. Skipping copy."
fi
# /////////////////// FLASH ENCRYPTION ///////////////////
echo "🔒 After eFuses are burned, the hardware will automatically encrypt the firmware in flash on first boot !"
# === Step 6: Copy firmware.bin ===
echo "📁 Therefore copying firmware is enough..."
cp "$BUILD_DIR/firmware.bin" "$OUTPUT_DIR/"
echo "🔒 However, we still need the Flash Encryption Key since used when burning the eFuse..."
# === Step 7: Generate flash encryption key if missing ===
if [ ! -f "$FLASH_KEY" ]; then
echo "🔐 Generating flash encryption key..."
espsecure.py generate_flash_encryption_key "$FLASH_KEY"
else
echo "🔐 Flash encryption key already exists."
fi
# === Step 7b: Encript firmware ===
echo "🔐 Encrypting firmware..."
espsecure.py encrypt_flash_data \
--keyfile "$FLASH_KEY" \
--address 0x10000 \
--output $OUTPUT_DIR/firmware-encrypted.bin \
$BUILD_DIR/firmware.bin
# === Step 7c: Sign encripted firmware ===
# Sign encrypted firmware
echo "🔐 Signing encrypted firmware..."
espsecure.py sign_data \
--version 2 \
--keyfile $BOOT_SIGN_KEY \
--output $OUTPUT_DIR/firmware-encrypted-signed.bin \
$OUTPUT_DIR/firmware-encrypted.bin
# === Step 8: Copy partitions.bin ===
echo "📁 Copying required binaries..."
cp "$BUILD_DIR/partitions.bin" "$OUTPUT_DIR/"
# === Final info ===
echo "✅ Done. Files ready in $OUTPUT_DIR:"
ls -lh "$OUTPUT_DIR"
echo
echo "🧩 Flash Map:"
echo " 0x0000 bootloader-signed.bin (encrypted secure boot !)"
echo " 0x8000 partitions.bin (not encrypted)"
echo " 0xe000 boot_app0.bin (not encrypted)"
echo " 0x10000 firmware.bin (encrypted on first boot !)"
echo
echo " NOTE: Be sure to burn eFuses only after verifying successful app!"
Here is my script 2: flash download and efuse-enable / efuse-burn
#!/bin/bash
set -e
# === CONFIGURATION ===
PORT="/dev/cu.usbmodem213101"
FLASH_MODE="dio" # typical value for QIO flash
FLASH_FREQ="80m" # typical value for QIO flash
FLASH_SIZE="16MB" # adjust if needed
BAUD="115200" # typical value for ESP32-S3
CHIP="esp32s3" # specify the chip type
BUILD_DIR=".pio/build/esp32s3dev"
BIN_DIR="./bin"
KEY_DIR="./secure"
BOOTLOADER_BIN="$BUILD_DIR/bootloader.bin"
BOOTLOADER_ENCR_BIN="$BIN_DIR/bootloader-signed.bin"
PARTITIONS_BIN="$BIN_DIR/partitions.bin"
BOOT_APP0="$BIN_DIR/boot_app0.bin"
# NVS_BIN="$BIN_DIR/nvs.bin"
# FIRMWARE_ENCR_BIN="$BIN_DIR/firmware-encrypted.bin"
FIRMWARE_ENCR_AND_SIGNED_BIN="$BIN_DIR/firmware-encrypted-signed.bin"
BOOT_SIGN_KEY="$KEY_DIR/secure_boot_private_key.pem"
BOOT_DIGEST_FILE="$KEY_DIR/secure_boot_public_key_digest.bin"
FLASH_KEY_FILE="$KEY_DIR/flash_encryption_key.bin"
NVS_KEY_FILE="$KEY_DIR/nvs_encryption_key.bin"
# Create directories
mkdir -p "$BIN_DIR"
# # === Step 1: Sign encrypted firmware also with the same key as the bootloader ===
# # The ESP32-S3 ROM bootloader encrypts flash contents on first boot after flash encryption efuse is set.
# # This happens automatically if the bootloader is not already encrypted.
# echo "🔧 Sign unencrypted firmware with the same key as the bootloader..."
# echo "🔧 It is enough to flash the unencrypted, but signed! firmware"
# echo "🔧 # The ESP32-S3 ROM bootloader encrypts flash contents on first boot after flash encryption efuse is set"
# espsecure.py sign_data \
# --version 2 \
# --keyfile $BOOT_SIGN_KEY \
# --output $FIRMWARE_SIGNED_BIN \
# $BUILD_DIR/firmware.bin
# espsecure.py sign_data \
# --version 2 \
# --keyfile $BOOT_SIGN_KEY \
# --output $BIN_DIR/firmware-encrypted-signed.bin \
# $BIN_DIR/firmware-encrypted.bin
# === Step 1: Erase entire flash before flashing ===
echo "🧹 Erasing entire flash..."
esptool.py --chip $CHIP --port $PORT erase_flash
# === Step 2: Flash bootloader, partitions, nvs and firmware ===
echo "⚡ Flashing encrypted bootloader, partitions, encrypted nvs and encrypted firmware..."
# === Step 3: write entire firmware to flash ===
esptool.py \
--chip "$CHIP" \
--port "$PORT" \
--baud "$BAUD" \
--before default_reset \
--after hard_reset \
write_flash -z \
--flash_mode "$FLASH_MODE" \
--flash_freq "$FLASH_FREQ" \
--flash_size "$FLASH_SIZE" \
0x0000 $BOOTLOADER_ENCR_BIN \
0x8000 $PARTITIONS_BIN \
0xe000 $BOOT_APP0 \
0x10000 $FIRMWARE_ENCR_AND_SIGNED_BIN
# esptool.py \
# --chip "$CHIP" \
# --port "$PORT" \
# --baud "$BAUD" \
# --before default_reset \
# --after hard_reset \
# write_flash -z \
# --flash_mode "$FLASH_MODE" \
# --flash_freq "$FLASH_FREQ" \
# --flash_size "$FLASH_SIZE" \
# 0x0000 "$BOOTLOADER_BIN" \
# 0x8000 $BUILD_DIR/partitions.bin \
# 0xe000 $BOOT_APP0 \
# 0x10000 $BUILD_DIR/firmware.bin
# espefuse.py --port /dev/cu.usbmodem213101 summary
# espsecure.py verify_signature \
# --version 2 \
# --keyfile $KEY_DIR/secure_boot_private_key.pem \
# $BIN_DIR/bootloader-signed.bin
# === Step 4: Burn eFuses for secure boot, flash encryption, and security config ===
echo "🔥 WARNING: About to burn eFuses - this is IRREVERSIBLE!"
echo "🔥 Make sure the device boots successfully before proceeding!"
echo
read -p "Continue with eFuse burning? (y/n): " confirm
if [ "$confirm" != "y" ]; then
echo "❌ eFuse burning cancelled."
exit 1
fi
echo "🔥 Burning eFuses for Secure Boot, Flash Encryption, and Security Configuration..."
# Burn secure boot public key digest
echo "🔐 Burning secure boot public key digest..."
espefuse.py --chip $CHIP --port $PORT --do-not-confirm burn_key \
BLOCK_KEY2 "$BOOT_DIGEST_FILE" \
SECURE_BOOT_DIGEST0
# Enable secure boot v2
echo "🔐 Enabling Secure Boot v2..."
espefuse.py --chip $CHIP --port $PORT --do-not-confirm burn_efuse \
SECURE_BOOT_EN 1
# 🔒 Burn flash encryption key
echo "🔒 Burning flash encryption key..."
espefuse.py --chip $CHIP --port $PORT --do-not-confirm burn_key \
BLOCK_KEY0 "$FLASH_KEY_FILE" \
XTS_AES_256_KEY_1
# ✅ Optional: Write-protect key block
espefuse.py --chip $CHIP --port $PORT --do-not-confirm write_protect_efuse \
BLOCK_KEY0
# 🔒 Enable flash encryption via counter (burning 1 bit of SPI_BOOT_CRYPT_CNT)
echo "🔒 Enabling Flash Encryption..."
espefuse.py --chip $CHIP --port $PORT --do-not-confirm burn_efuse \
SPI_BOOT_CRYPT_CNT 0x1
# Security configuration eFuses
echo "🛡️ Burning security configuration eFuses..."
# Disable USB JTAG
espefuse.py --chip $CHIP --port $PORT --do-not-confirm burn_efuse \
DIS_USB_JTAG 1
# Hard disable JTAG
espefuse.py --chip $CHIP --port $PORT --do-not-confirm burn_efuse \
HARD_DIS_JTAG 1
# Soft disable JTAG (set to 7)
espefuse.py --chip $CHIP --port $PORT --do-not-confirm burn_efuse \
SOFT_DIS_JTAG 7
# Disable USB OTG download mode
espefuse.py --chip $CHIP --port $PORT --do-not-confirm burn_efuse \
DIS_USB_OTG_DOWNLOAD_MODE 1
# Disable direct boot
espefuse.py --chip $CHIP --port $PORT --do-not-confirm burn_efuse \
DIS_DIRECT_BOOT 1
# Disable download mode instruction cache
espefuse.py --chip $CHIP --port $PORT --do-not-confirm burn_efuse \
DIS_DOWNLOAD_ICACHE 1
# Disable download mode data cache
espefuse.py --chip $CHIP --port $PORT --do-not-confirm burn_efuse \
DIS_DOWNLOAD_DCACHE 1
# Disable download mode manual encryption
espefuse.py --chip $CHIP --port $PORT --do-not-confirm burn_efuse \
DIS_DOWNLOAD_MANUAL_ENCRYPT 1
echo "✅ Flashing and eFuse burning complete."
echo "🇨🇭🇨🇭🇨🇭🇨🇭🇨🇭 Your device is now locked-down and secure !! 🇨🇭🇨🇭🇨🇭🇨🇭🇨🇭"