espefuse.py
Espressif's eFuse tool. Reads and permanently burns the one-time-programmable bits that control Secure Boot, Flash Encryption and JTAG on ESP32 chips. Reading is safe; burning is forever.
espefuse.py is Espressif's tool for reading and permanently burning the one-time-programmable eFuses on ESP32 chips, the silicon bits that control Secure Boot, Flash Encryption and the debug locks.
What it is
eFuses are OTP bits blown into the ESP32 die at manufacture or by the developer. They hold the security keys (the Secure Boot public-key digest, the Flash Encryption key) and the irreversible policy flags that decide whether those defences are active. espefuse.py, shipped alongside esptool.py and speaking the same UART download-mode protocol, is the reference tool for reading that block and, when you choose to, burning it. Reading is completely safe and non-destructive; burning changes the silicon for good.
Why it matters
The eFuses are the single source of truth for an ESP32's security posture. Before you spend time on an offline attack, espefuse.py summary tells you whether it is even possible: are flash dumps going to be plaintext or ciphertext, is Secure Boot verifying images, is the JTAG debug port still open? For an assessor this one read decides the whole approach. If the security fuses are un-burned, and on a large share of shipped devices they are, every offline path (esptool dump, reverse, patch, reflash) is wide open. If they are burned, those doors are shut and you pivot to running-chip and side-channel angles.
Key commands
espefuse.py -p /dev/ttyUSB0 summary # human-readable posture of every fuse
espefuse.py -p /dev/ttyUSB0 dump # raw hex of all eFuse blocks
espefuse.py -p /dev/ttyUSB0 get_custom_mac # the burned custom MAC, if any
espefuse.py -p /dev/ttyUSB0 adc_info # factory ADC calibration values
In a summary the fields that decide the attack are:
- FLASH_CRYPT_CNT (or SPI_BOOT_CRYPT_CNT on newer parts): odd value means Flash Encryption is active, so a raw flash read yields only ciphertext.
- ABS_DONE_0 / SECURE_BOOT_EN: set means Secure Boot is enforcing signed images; you cannot boot a patched app.
- JTAG_DISABLE / DIS_PAD_JTAG: set means the hardware debug port is permanently dead, so OpenOCD over JTAG is off the table.
Pitfalls
- Burning is permanent. A wrong
burn_efuseorburn_keycannot be undone; it can brick the chip or lock you out of it forever. On a target you are assessing, read only, never burn. - Read first, always.
summaryanddumpare non-destructive; make them your default and treat anyburn_*subcommand as a one-way door you open deliberately, on your own lab board, understanding exactly which bit you are setting. - A un-burned fuse is not the same as a disabled feature by design. It usually means the vendor never turned the protection on, which is the finding, not a mistake in your reading.
- Development-mode Flash Encryption still shows in the counter but may remain re-flashable. Do not read "encryption on" as "fully locked" without checking the mode.
What it pairs with
espefuse.py is the reconnaissance step that gates the ESP32 offline workflow. Run it before esptool.py: its summary tells you whether a read_flash will return usable plaintext. It uses the same usb-uart link and the same download-mode entry sequence as esptool, so the two are almost always used back to back. See the esp32, secure-boot, flash-encryption and efuse entries for what each fuse actually protects.