A Automação da Criptografia e de eFuses no ESP32 é essencial para quem faz protótipos e até produtos, pois disponibilizar soluções que usam ESP32 sem criptografia é um grande risco para a propriedade intelectual.
1. Explicando a estrutura e como será a automação
Antes de mais nada é importante entender que as automações feitas aqui são baseadas na proteção contra leitura e gravação do ESP32 e a proteção contra leitura e gravação do ESP32S3. Então se caiu aqui de paraquedas, volte e leia esses outros dois posts.
Se tu já entendeu como funciona a criptografia e já fez uma ou outra vez de forma manual, então vamos apenas entender como é estruturado o processo de criptografia, dessa forma vai ficar mais fácil de compreender o que será feito e o motivo disso. O sistema de criptografia basicamente é dividido em 4 partes, sendo elas:
- Criptografia dos binários;
- Gravação da chave no eFuse;
- Ativação dos eFuses responsáveis pela criptografia;
- Gravação dos binários no microcontrolador.
O primeiro passo é o único que fica do lado do desenvolvedor, pois os binários não serão criptografados toda hora, apenas quando houver atualizações de firmware. No caso dos outros 3 vão ser gravados a cada microcontrolador, nesse caso teríamos 2 automações:
- Criptografia dos binários;
- Instalação de chave, configuração dos eFuses e gravação dos binários no microcontrolador.
Este post em si é na verdade mais um caminho das pedras do que te entregar tudo mastigado. A automação basicamente vai ser um abstração de todo o processo que é feito linha a linha de forma manual, mas a real automação é você que vai fazer. Se entendeu isso, então vamos iniciar todo o processo.
2. Instalando o ESP-IDF e Ferramentas no Raspberry Pi
O tutorial em si é feito em um Raspberry Pi 4, porém você pode usar um Raspberry Pi Zero W sem problemas ou um Banana Pi M2 Zero. Caso tenha apenas um computador Windows, recomendo fortemente ver alguma forma de colocar um Linux dentro do Windows, porém se seu sistema for o Win11 há uma funcionalidade chamada Windows Service Linux (WSL) que basicamente roda um Linux no Windows como se fosse um acesso SSH só que é um outro sistema rodando em paralelo.

Para que possamos realizar todo o processo de automação, é fundamental que tenhamos tudo instado para garantir que o ESP-IDF e as ferramentas necessárias, como esptool.py, espsecure.py e espefuse.py, estejam corretamente instaladas no seu sistema e funcionando normalmente.
2.1 Atualizando o Sistema
Abra o terminal e atualize os pacotes do Raspberry Pi ou de qualquer outro computador com Linux e execute os comandos a baixo:
sudo apt update
sudo apt upgrade
2.2 Instalando Dependências
Em seguida instale algumas dependências que serão necessárias para que o ESP-IDF seja executado sem problemas.
sudo apt install git wget flex bison gperf python3 python3-pip python3-setuptools cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0
2.3 Instalando o ESP-IDF
Agora, vamos clonar o repositório do ESP-IDF e instalar as ferramentas necessárias.
mkdir -p ~/esp
cd ~/esp
git clone --recursive https://github.com/espressif/esp-idf.git
Depois de clonar o repositório, entre no diretório do ESP-IDF e execute o script de instalação:
cd ~/esp/esp-idf
./install.sh
Esse script instalará as ferramentas necessárias, como espsecure.py, espefuse.py e esptool.py.
2.4 Ativando o Ambiente ESP-IDF
Toda vez que for trabalhar com o ESP-IDF será necessário executar o comando a baixo, então é importante que antes de executar o código de automação ele seja executado. Uma solução viável é criar um arquivo shell que executa esse comando e em seguida executa o arquivo python.
source ~/esp/esp-idf/export.sh
3. Fazendo o ESP-IDF rodar dentro de um comando python
Após ter ativado o ambiente do ESP-IDF e se for executar um comando idf.py ele irá funcionar, mas se tentar fazer isso via subprocess.run acontecerá um erro miserável como se o idf.py e tudo do ESP-IDF nem existissem.
Particularmente, passei bastante tempo preso a esse erro, tanto no Windows quanto no Linux, até que tive a ideia de tentar executar algum comando que me retornasse o caminho completo do comando. Para minha alegria, essa possibilidade existe: no Windows, o comando é where, e no Linux, é which. Então, bastava executar which esptool.py, e precisei repetir esse processo para todas as ferramentas do ESP-IDF. Isso resultou nas seguintes saídas:
/home/gustavo/.espressif/python_env/idf5.4_py3.11_env/bin/espsecure.py/home/gustavo/.espressif/python_env/idf5.4_py3.11_env/bin/espefuse.py/home/gustavo/.espressif/python_env/idf5.4_py3.11_env/bin/esptool.py
Agora, com esses diretórios em mãos, podemos de fato executar as ferramentas do ESP-IDF via subprocess no Python. Assim, em vez de rodar o comando como: espefuse.py <comando>, devemos usar o caminho completo: /home/gustavo/.espressif/python_env/idf5.4_py3.11_env/bin/espefuse.py <comando>.
4. Automação da Criptografia de Binários
A criptografia dos binários do firmware é uma etapa importante no lado do desenvolvedor. Aqui, você criará um script Python que usará o espsecure.py para criptografar os binários do ESP32. Este processo não precisa ser feito toda hora, apenas quando uma nova atualização de firmware for compilada
4.1 Estrutura do Script
O script a seguir criptografa os binários da flash do ESP32S3 usando uma chave AES-128. Certifique-se de que todos os caminhos estão corretos e os binários estão disponíveis. Crie um arquivo chamado encrypt_binaries.py:
import os
import subprocess
esp_path = '/caminho/para/seus/binarios/'
def encrypt_flash_data(address, input_file, output_file):
espsecure_path = '/caminho/para/espsecure.py'
command = f'{espsecure_path} encrypt_flash_data --aes_xts --keyfile "{esp_path}Key/Dev.bin" --address {address} --output "{output_file}" "{input_file}"'
subprocess.run(command, shell=True, check=True)
tasks = [
('0x0', f"{esp_path}projeto/bootloader.bin", f"{esp_path}projeto/cryp/0x0.bin"),
('0xe000', f"{esp_path}projeto/boot_app0.bin", f"{esp_path}projeto/cryp/0xe000.bin"),
('0x8000', f"{esp_path}projeto/partitions.bin", f"{esp_path}projeto/cryp/0x8000.bin"),
('0x10000', f"{esp_path}projeto/firmware.bin", f"{esp_path}projeto/cryp/0x10000.bin")
]
for address, input_file, output_file in tasks:
encrypt_flash_data(address, input_file, output_file)
5.2 Explicação
encrypt_flash_data(): Função que criptografa um binário da flash do ESP32 usando a chave AES.- Lista
tasks: Define os endereços de memória flash e os arquivos binários que precisam ser criptografados. Esses binários incluem o bootloader, partições e o firmware principal.
5.3 Executando o Script de criptografia de binários
Antes de executar verifique se de fato o ambiente ESP-IDF está habilitado e em seguida execute o comando python encrypt_binaries.py, se tudo ocorrer bem a saída será igual a essa ou bem similar:
espsecure.py v4.8.1
Using 256-bit key
espsecure.py v4.8.1
Using 256-bit key
espsecure.py v4.8.1
Using 256-bit key
espsecure.py v4.8.1
Using 256-bit key
6. Automação da Gravação da Chave AES128, Configuração dos eFuses e Gravação do ESP32
Nesta parte, você configurará os eFuses do ESP32 e gravará o firmware criptografado na flash. O script também grava a chave AES-128 no bloco de eFuses do ESP32.
6.1 Estrutura do Script
Crie um arquivo chamado burn_efuses_and_flash.py:
import os
import subprocess
esp_path = '/caminho/para/seus/binarios/'
espsecure_path = '/caminho/para/espsecure.py'
espefuse_path = '/caminho/para/espefuse.py'
esptool_path = '/caminho/para/esptool.py'
port = '/dev/ttyUSB0'
device = f'--port {port} --chip esp32s3'
device_param = '--baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size 8MB'
p0 = f'{esp_path}projeto/cryp/0x0.bin'
p1 = f'{esp_path}projeto/cryp/0x8000.bin'
p2 = f'{esp_path}projeto/cryp/0xe000.bin'
p3 = f'{esp_path}projeto/cryp/0x10000.bin'
def encrypt_esp(command):
subprocess.run(command, shell=True, check=True)
tasks = [
f'{espefuse_path} {device} burn_key BLOCK_KEY0 "{esp_path}Key/Dev.bin" XTS_AES_128_KEY --force-write-always --do-not-confirm',
f'{espefuse_path} {device} burn_efuse SPI_BOOT_CRYPT_CNT 7 --do-not-confirm',
f'{espefuse_path} {device} burn_efuse DIS_DOWNLOAD_MANUAL_ENCRYPT --do-not-confirm',
f'{esptool_path} {device} {device_param} 0x0 {p0} 0x8000 {p1} 0xe000 {p2} 0x10000 {p3} --force'
]
for command in tasks:
encrypt_esp(command)
6.2 Explicação
burn_key BLOCK_KEY0: Grava a chave AES no bloco de eFuses do ESP32.burn_efuse SPI_BOOT_CRYPT_CNT: Configura o contador de criptografia de boot.esptool.py: Grava o firmware criptografado na memória flash do ESP32.- Comandos
--do-not-confirme--force-write-always: Evitam a necessidade de confirmação manual durante a queima dos eFuses e forçam a gravação.
6.3 Executando o Script
Certifique-se de que o ambiente ESP-IDF esteja ativado e, em seguida, execute o comando python burn_efuses_and_flash.py. Se tudo ocorrer conforme esperado, a saída será semelhante à mostrada abaixo. A única diferença é que o meu ESP32-S3 já está criptografado.
espefuse.py v4.8.1
Connecting....
=== Run "burn_key" command ===
Sensitive data will be hidden (see --show-sensitive-info)
Burn keys to blocks:
- BLOCK_KEY0 -> [?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ??]
Reversing byte order for AES-XTS hardware peripheral
BLOCK_KEY0 is read-protected. Burn in this case may damage an already written value. Skipped because '--force-write-always' option.
BLOCK_KEY0 is write-protected. Burn is not possible. Skipped because '--force-write-always' option.
'KEY_PURPOSE_0' is already 'XTS_AES_128_KEY'.
Disabling read to key block
The same value for RD_DIS is already burned. Do not change the efuse.
Disabling write to key block
The same value for WR_DIS is already burned. Do not change the efuse.
Check all blocks for burn...
idx, BLOCK_NAME, Conclusion
BLOCK_KEY0 is read-protected. Burn in this case may damage an already written value. Skipped because '--force-write-always' option.
BLOCK_KEY0 is write-protected. Burn is not possible. Skipped because '--force-write-always' option.
[04] BLOCK_KEY0 is empty, will burn the new value
.
This is an irreversible operation!
Repeat burning BLOCK4 (#2) because not all bits were set
Repeat burning BLOCK4 (#3) because not all bits were set
After 3 attempts, the required data was not set to BLOCK4
BLOCK_KEY0 (['BLOCK4']) is read-protected. Read back the burn value is not possible.
Read all '0'
Reading updated efuses...
Successful
espefuse.py v4.8.1
Connecting...
=== Run "burn_efuse" command ===
The efuses to burn:
from BLOCK0
- SPI_BOOT_CRYPT_CNT
Burning efuses:
- 'SPI_BOOT_CRYPT_CNT' (Enables flash encryption when 1 or 3 bits are set and disabled otherwise) 0b111 -> 0b111
The same value for SPI_BOOT_CRYPT_CNT is already burned. Do not change the efuse.
Check all blocks for burn...
idx, BLOCK_NAME, Conclusion
Nothing to burn, see messages above.
Checking efuses...
Successful
espefuse.py v4.8.1
Connecting...
=== Run "burn_efuse" command ===
The efuses to burn:
from BLOCK0
- DIS_DOWNLOAD_MANUAL_ENCRYPT
Burning efuses:
- 'DIS_DOWNLOAD_MANUAL_ENCRYPT' (Set this bit to disable flash encryption when in download boot modes) 0b1 -> 0b1
The same value for DIS_DOWNLOAD_MANUAL_ENCRYPT is already burned. Do not change the efuse.
Check all blocks for burn...
idx, BLOCK_NAME, Conclusion
Nothing to burn, see messages above.
Checking efuses...
Successful
esptool.py v4.8.1
Serial port /dev/ttyUSB0
Connecting...
Chip is ESP32-S3 (QFN56) (revision v0.1)
Features: WiFi, BLE, Embedded PSRAM 8MB (AP_3v3)
Crystal is 40MHz
MAC: f4:12:fa:e7:89:4c
Uploading stub...
Running stub...
Stub running...
Changing baud rate to 921600
Changed.
Configuring flash size...
Flash will be erased from 0x00000000 to 0x00003fff...
Flash will be erased from 0x00008000 to 0x00008fff...
Flash will be erased from 0x0000e000 to 0x0000ffff...
Flash will be erased from 0x00010000 to 0x0004ffff...
Warning: Image file at 0x0 doesn't look like an image file, so not changing any flash settings.
Compressed 15088 bytes to 15099...
Wrote 15088 bytes (15099 compressed) at 0x00000000 in 0.4 seconds (effective 302.0 kbit/s)...
Hash of data verified.
Compressed 3072 bytes to 3083...
Wrote 3072 bytes (3083 compressed) at 0x00008000 in 0.1 seconds (effective 256.3 kbit/s)...
Hash of data verified.
Compressed 8192 bytes to 8203...
Wrote 8192 bytes (8203 compressed) at 0x0000e000 in 0.2 seconds (effective 315.2 kbit/s)...
Hash of data verified.
Compressed 259872 bytes to 259958...
Wrote 259872 bytes (259958 compressed) at 0x00010000 in 3.3 seconds (effective 634.0 kbit/s)...
Hash of data verified.
Leaving...
Hard resetting via RTS pin...
7. Explorando Melhorias e Aplicações Finais
É importante entender que esta automação foi desenvolvida com base nos posts relacionados à proteção contra leitura e gravação do ESP32, especialmente no modelo ESP32-S3. No entanto, independentemente da versão, a estrutura será a mesma, e, com a base apresentada neste post, você conseguirá realizar outras gravações de eFuses específicos com o suporte da documentação da Espressif.
Após finalizado, o processo se torna bem fácil de entender. Claro, ainda há pontos que podem ser melhorados para adaptar o sistema a uma linha de produção, como a adição de botões e indicadores de status de gravação/erros. Mas o caminho já está dado, então essa parte fica contigo!
