Page 1 of 1

Secure Boot and Flash Encryption on ESP32S3 WROOM 1U N16

Posted: Tue Jun 17, 2025 5:24 pm
by skuenstler
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.

Code: Select all

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:

Code: Select all

[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:

Code: Select all

# 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:

Code: Select all

#!/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

Code: Select all

#!/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 !! ๐Ÿ‡จ๐Ÿ‡ญ๐Ÿ‡จ๐Ÿ‡ญ๐Ÿ‡จ๐Ÿ‡ญ๐Ÿ‡จ๐Ÿ‡ญ๐Ÿ‡จ๐Ÿ‡ญ"

The device does not boot correctly. The WiFi is dead (at least the Access Point creation is not happening like it should).....
What can I do to make the secure boot and flash encryption working for this board ?