Wiki / Concepts

Flash Encryption

Flash contents are stored encrypted under a key held on-die, so a raw chip dump yields only ciphertext. It closes the offline read that Secure Boot does not.

Transparent decryption path
Flash controller (on-die)
SPI flash: ciphertext (off-chip)
eFuse key: no read-back
CPU: plaintext fetch
The controller reads ciphertext from external flash and decrypts it with the eFuse key as the CPU fetches. The key never crosses the die boundary and read-back is disabled, so an off-chip reader only ever sees ciphertext.

Flash Encryption stores the contents of external flash in encrypted form, with the key held in on-die eFuses, so a physical readout of the chip yields only ciphertext. It closes the offline read path that Secure Boot alone leaves open.

What it is

On an ESP32 the firmware lives in a flash chip that sits outside the SoC, inside the module can. Anyone with a clip and a programmer can read that chip. Flash Encryption makes the stored bytes ciphertext: the plaintext only ever exists inside the SoC, reconstructed on the fly. The encryption key is generated on-device and burned into an eFuse block whose read-back is then disabled, so the key cannot be extracted even by the running firmware.

Why it matters

Reading SPI flash with a soic8-clip and a ch341a, or after desoldering, is trivial and non-invasive. Flash Encryption defeats that offline path: even with a perfect full dump, the attacker holds no plaintext firmware to grep for credentials or load into a disassembler. Paired with Secure Boot it is complete in both directions: encryption stops the offline read, Secure Boot stops the offline write.

How it works

A key is generated and burned into eFuse. The flash controller decrypts data transparently as the CPU fetches it (AES-XTS on the S2/S3/C-series, a tweaked AES-256 scheme on the original ESP32). The key never leaves the die, and read-back of the key eFuse is disabled. From software and from the CPU's point of view flash simply reads as plaintext; from a wire tap or an external reader it reads as ciphertext.

How to inspect or attack it

Check whether it is actually burned before assuming a dump is useless.

espefuse.py -p /dev/ttyUSB0 summary          # look for FLASH_CRYPT_CNT / SPI_BOOT_CRYPT_CNT
flashrom -p ch341a_spi -r dump.bin           # off-chip read: ciphertext if enabled
binwalk -E dump.bin                          # entropy: flat high across the image = encrypted

If the fuse is not burned, the dump is plaintext and you are done. If it is burned, a raw read is ciphertext and you must pivot: read from inside a running chip (a debug interface, an app-level leak, a memory disclosure bug), or attack the key directly via side-channel or fault injection. In practice the usual way in is misconfiguration: development mode left enabled (still re-flashable), only the app encrypted while the bootloader or a data partition is not, or a key accidentally left in a readable region.

Pitfalls

  • Development mode is not release mode. A dev-mode device can often still be re-flashed, which undermines the whole guarantee.
  • Partial encryption is common: confirm that every sensitive partition, not just the app, is covered.
  • Encryption without Secure Boot stops reading but not writing, and vice versa. Assess both fuses; one without the other is a half measure.
  • High entropy alone does not prove encryption, since compressed data also looks random. Correlate with the eFuse state.

Further reading