How-To-Tutorials · September 23, 2025

How to Implement Secure Key Storage with eFUSE on ESP32 for IoT

how to implement secure key storage with efuse on esp32 for iot

Why eFuses for Key Storage?

Storing cryptographic keys in flash or source code is a terrible idea—anyone with a UART connection and esptool can dump your firmware and extract them. The ESP32’s eFuse blocks are one-time-programmable hardware storage that’s significantly harder to extract. Once a key is burned into an eFuse block and read protection is enabled, even physical access to the chip won’t easily reveal it.

This matters for IoT because your devices sit in the field, often physically accessible to anyone. eFuse-based key storage is one layer in a proper security chain that includes secure boot, flash encryption, and TLS for communications.

Prerequisites

  • Experience with ESP-IDF (v5.3+) — this is an ESP-IDF workflow, not Arduino
  • ESP32 development board (ESP32, ESP32-S3, or ESP32-C3 all support eFuses)
  • USB cable for programming
  • Understanding of basic cryptographic concepts (keys, AES, HMAC)
  • A healthy respect for irreversible operations—eFuses cannot be unburned

Parts and Tools

  • ESP32 dev board (ESP32-DevKitC, ESP32-S3-DevKitC, or similar)
  • USB cable
  • ESP-IDF v5.3+ installed and configured
  • Computer with espefuse.py tool (included with ESP-IDF)

Steps

  1. Understand the eFuse Layout Before You Touch Anything

    The ESP32 has several eFuse blocks. BLOCK1, BLOCK2, and BLOCK3 can store 256 bits each. On newer chips like ESP32-S3 and ESP32-C3, there are more key blocks (KEY0 through KEY5) with better access control.

    Before writing anything, read the current eFuse state:

    
    espefuse.py --port /dev/ttyUSB0 summary
    

    This shows you which blocks are already used, which are write/read protected, and how many bits have been burned. On a fresh chip, everything should be zeroed out. If you’re working with a dev board that’s been used before, check carefully—you can’t undo previous eFuse writes.

  2. Generate Your Key Properly

    Don’t generate keys on the ESP32 itself for production use. The hardware RNG is fine for nonces and session tokens, but for permanent keys burned into eFuses, generate them on a secure workstation:

    
    # Generate a 256-bit key for flash encryption or HMAC
    dd if=/dev/urandom of=my_key.bin bs=32 count=1
    
    # Or use espsecure.py
    espsecure.py generate_flash_encryption_key my_flash_key.bin
    

    Keep backups of these keys in a secure location (hardware security module, encrypted vault, etc.). Once burned into the eFuse, you cannot read them back if read protection is enabled—and you want read protection enabled.

  3. Burn the Key to an eFuse Block

    Use espefuse.py to write the key. For an ESP32-S3 or C3, the syntax is cleaner:

    
    # Burn a key to KEY0 block with a specific purpose
    espefuse.py --port /dev/ttyUSB0 burn_key BLOCK_KEY0 my_key.bin HMAC_UP
    

    The purpose field (like HMAC_UP, XTS_AES_128_KEY, etc.) tells the hardware what this key is for and restricts its use to that specific peripheral. This is a security feature—a key burned for HMAC cannot be used for flash encryption, even if the software is compromised.

    For the original ESP32 (not S2/S3/C3), the process is slightly different because the key blocks are less structured:

    
    # Burn flash encryption key on original ESP32
    espefuse.py --port /dev/ttyUSB0 burn_key flash_encryption my_flash_key.bin
    

    Watch out: this is irreversible. Triple-check your key file and target block before confirming. The tool will ask you to confirm—read the confirmation message carefully.

  4. Enable Read Protection

    After burning the key, protect it from software readback:

    
    espefuse.py --port /dev/ttyUSB0 read_protect_efuse BLOCK_KEY0
    

    With read protection enabled, the key is only accessible to the hardware peripherals (AES accelerator, HMAC module, etc.) but cannot be read by software—not even your own firmware. This is the whole point.

  5. Use the Key in Your Firmware

    Your firmware never reads the raw key. Instead, it uses ESP-IDF APIs that tell the hardware to use the eFuse-stored key for operations. Here’s an example using the HMAC peripheral on ESP32-S3/C3:

    
    #include "esp_hmac.h"
    
    void compute_hmac_with_efuse_key(void)
    {
        uint8_t message[] = "data to authenticate";
        uint8_t hmac_result[32];
    
        // The hardware reads the key from eFuse internally
        // HMAC_KEY0 corresponds to the key block we burned
        esp_err_t err = esp_hmac_calculate(
            HMAC_KEY0,
            message,
            sizeof(message) - 1,
            hmac_result
        );
    
        if (err == ESP_OK) {
            ESP_LOG_BUFFER_HEX("HMAC", hmac_result, sizeof(hmac_result));
        } else {
            ESP_LOGE("HMAC", "HMAC calculation failed: %s", esp_err_to_name(err));
        }
    }
    

    For flash encryption, you don’t even interact with the key directly—ESP-IDF handles it transparently once you enable flash encryption in menuconfig under Security Features.

  6. Verify and Test

    After burning, run espefuse.py summary again to confirm the key block shows as programmed and read-protected. Then test your firmware to ensure the HMAC or encryption operations work correctly with the stored key.

    Pro tip: test the entire flow on a dev board you’re willing to “brick” (eFuse-wise) before doing this on production hardware. Keep a stash of fresh dev boards for exactly this purpose.

Troubleshooting

  • espefuse.py reports "Key block already used"

    That eFuse block has already been programmed. You cannot overwrite it. Use a different key block, or use a fresh ESP32 module. This is by design—it’s a security feature, not a bug.

  • HMAC calculation returns ESP_ERR_INVALID_STATE

    The key purpose set during burning doesn’t match the operation you’re trying to perform. If you burned the key with HMAC_UP purpose, it can only be used for upstream HMAC. Check that your key purpose matches your use case.

  • Flash encryption enabled but device won’t boot

    If you enabled flash encryption in Release mode (as opposed to Development mode), you cannot flash unencrypted firmware anymore. Development mode allows re-flashing a limited number of times. Always start with Development mode until you’re confident in your firmware.

  • espefuse.py can’t connect

    Same as any ESP32 flashing issue—check your USB connection, hold BOOT while pressing RESET if needed, and verify the correct port in the command. On some boards, you may need to install the CP2102 or CH340 driver.

The Bigger Security Picture

eFuse key storage is one piece of the puzzle. For a properly secured ESP32 IoT device, you should also enable secure boot (so only your signed firmware can run), flash encryption (so firmware can’t be dumped and reverse-engineered), and TLS 1.3 for all network communications. ESP-IDF v5.3+ has solid support for all of these. The Espressif security guide in the official docs walks through the full chain—I’d strongly recommend reading it end to end before shipping anything to production.