Proteger credenciais MQTT em dispositivos significa garantir que o usuário e a senha que o seu dispositivo usa para chegar ao broker não possam ser lidos a partir de imagens de firmware, de chips de flash copiados, de portas de depuração ou de arquivos de log. Este guia compara as opções práticas em dispositivos ESP32 e Linux, mostra a configuração de cada uma e lista os erros que mais vazam credenciais em campo.
O modelo de ameaças em termos simples
Antes de escolher um método de armazenamento, deixe claro contra quem você está se defendendo:
| Atacante | Acesso | O que ele tenta |
|---|---|---|
| Alguém com o seu arquivo de firmware | Um .bin de uma página de download ou de um chamado de suporte |
Procurar strings na imagem |
| Alguém com um dispositivo | Acesso físico, um adaptador USB-serial | Copiar a flash via UART, conectar JTAG |
| Malware ou bug no dispositivo | Código rodando no dispositivo | Ler arquivos ou memória |
| Alguém na rede | Captura de pacotes | Ler a senha no CONNECT |
O atacante na rede é tratado pelo TLS na porta 8883 (ou 8084 para WebSockets), que todo broker ConnectMQ suporta com um certificado Let's Encrypt; autenticação MQTT com usuário e senha explica por que as credenciais nunca devem trafegar pela porta 1883. Este artigo se concentra nos outros três: proteger o segredo em repouso no dispositivo.
Regra zero: credenciais não pertencem ao firmware
Um #define MQTT_PASSWORD "..." no código-fonte vai parar no binário do firmware, no histórico do Git, nos logs de CI e em cada cópia da imagem. Executar strings firmware.bin | grep -i pass em um projeto hobby típico muitas vezes a encontra em segundos. O firmware deve ser idêntico em todos os dispositivos; as credenciais devem ser únicas por dispositivo. Mantenha-os separados e grave as credenciais no armazenamento durante o provisionamento seguro de dispositivos IoT.
Opções no ESP32
NVS simples (base, não segura)
A biblioteca Non-Volatile Storage (ou o wrapper Preferences do Arduino) armazena pares chave-valor em uma partição da flash. Ela separa as credenciais do firmware, o que já é uma melhoria, mas os dados ficam sem criptografia. Qualquer pessoa que consiga ler o chip de flash consegue ler a senha.
Criptografia da NVS mais criptografia de flash
O ESP-IDF consegue criptografar partições NVS. No esquema clássico, as chaves de criptografia da NVS ficam em uma partição dedicada nvs_keys, e essa partição é protegida pela criptografia de flash, cuja chave fica armazenada em eFuses que o software não consegue ler de volta. Chips mais novos, como o ESP32-S3 e o ESP32-C3, também suportam um esquema baseado em HMAC que deriva as chaves da NVS de uma chave em eFuse, sem partição nvs_keys.
Uma tabela de partições com uma partição de chaves criptografada:
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, , 0x6000,
nvs_keys, data, nvs_keys, , 0x1000, encrypted
phy_init, data, phy, , 0x1000,
factory, app, factory, , 1M,
Opções relevantes do sdkconfig:
CONFIG_SECURE_FLASH_ENC_ENABLED=y
CONFIG_SECURE_FLASH_ENCRYPTION_MODE_RELEASE=y
CONFIG_NVS_ENCRYPTION=y
CONFIG_SECURE_BOOT=y
CONFIG_SECURE_BOOT_V2_ENABLED=y
O modo release é importante: o modo de desenvolvimento deixa formas de regravar imagens em texto claro, o que anula o propósito em unidades já enviadas. O Secure Boot complementa a criptografia ao recusar executar firmware que você não assinou, de modo que um atacante não consiga gravar um código que leia e imprima as credenciais descriptografadas. Ambos queimam eFuses de forma irreversível, então teste primeiro em placas sobressalentes e siga passo a passo a documentação de criptografia de flash da Espressif.
Ler as credenciais em um sketch Arduino no ESP32 tem a mesma cara com ou sem NVS criptografada, e é exatamente essa a ideia: a proteção fica abaixo da API.
#include <Preferences.h>
struct MqttCreds { String user, pass, clientId; };
bool loadCreds(MqttCreds &c) {
Preferences prefs;
if (!prefs.begin("mqtt", true)) return false; // somente leitura
c.user = prefs.getString("user", "");
c.pass = prefs.getString("pass", "");
c.clientId = prefs.getString("cid", "");
prefs.end();
return c.user.length() && c.pass.length() && c.clientId.length();
}
void logCreds(const MqttCreds &c) {
// Registra a identidade, nunca a senha
Serial.printf("MQTT user=%s clientId=%s pass=<%u bytes>\n",
c.user.c_str(), c.clientId.c_str(), c.pass.length());
}
Secure elements
Um secure element, como o ATECC608 da Microchip, é um chip separado que armazena chaves e executa operações criptográficas sem nunca liberar as chaves privadas. Ele é mais forte para identidade baseada em certificados, em que a chave privada do TLS mútuo nunca sai do chip. Com autenticação por senha, um secure element ainda pode ajudar guardando a chave usada para criptografar o blob da senha na flash, dificultando a extração física. Ele aumenta o custo da lista de materiais, o espaço na placa e o trabalho de integração de drivers, por isso é mais comum em produtos com dados de alto valor ou pressão regulatória.
Opções em dispositivos Linux
Gateways e computadores de borda com Linux têm ferramentas mais ricas.
Permissões de arquivo (base)
Guarde as credenciais em um arquivo legível apenas pela conta de serviço, fora do diretório da aplicação:
sudo install -d -m 0700 -o mqttsvc -g mqttsvc /etc/mqtt-agent
sudo install -m 0600 -o mqttsvc -g mqttsvc credentials.env /etc/mqtt-agent/credentials.env
Isso protege contra outros usuários e serviços, não contra alguém que remove o cartão SD. Combine com criptografia de disco completo para ter proteção física.
Credenciais criptografadas do systemd com TPM
Em sistemas com systemd e um chip TPM 2.0, o systemd-creds criptografa um segredo para que ele só possa ser descriptografado naquela máquina e apenas para o serviço que o declara:
# Criptografa a senha, vinculada ao TPM desta máquina
echo -n 'YOUR_PASSWORD' | sudo systemd-creds encrypt --with-key=tpm2 \
--name=mqtt-password - /etc/credstore.encrypted/mqtt-password
# /etc/systemd/system/mqtt-agent.service
[Unit]
Description=MQTT telemetry agent
After=network-online.target
Wants=network-online.target
[Service]
User=mqttsvc
LoadCredentialEncrypted=mqtt-password:/etc/credstore.encrypted/mqtt-password
Environment=MQTT_USERNAME=YOUR_USERNAME MQTT_CLIENT_ID=YOUR_CLIENT_ID
ExecStart=/usr/bin/python3 /opt/mqtt-agent/agent.py
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
O serviço lê o segredo descriptografado de um diretório privado em memória:
import os
import ssl
import paho.mqtt.client as mqtt
cred_dir = os.environ["CREDENTIALS_DIRECTORY"]
with open(os.path.join(cred_dir, "mqtt-password")) as f:
password = f.read().strip()
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
client_id=os.environ["MQTT_CLIENT_ID"],
protocol=mqtt.MQTTv5)
client.username_pw_set(os.environ["MQTT_USERNAME"], password)
client.tls_set(cert_reqs=ssl.CERT_REQUIRED)
client.connect("YOUR_BROKER_HOST", 8883, keepalive=60)
client.loop_forever()
Uma imagem de disco copiada é inútil em outra máquina, porque o TPM não vai liberar a credencial.
Comparação das opções de armazenamento
| Método | Plataforma | Protege contra vazamento do firmware | Protege contra cópia da flash | Esforço |
|---|---|---|---|---|
| Fixo no firmware | Qualquer | Não | Não | Nenhum |
| NVS simples / arquivo de configuração | ESP32 / Linux | Sim | Não | Baixo |
| Criptografia da NVS mais criptografia de flash, Secure Boot | ESP32 | Sim | Sim | Médio, eFuses irreversíveis |
| Secure element | ESP32, Linux | Sim | Sim, o mais forte para chaves | Alto |
| Permissões de arquivo mais criptografia de disco | Linux | Sim | Parcialmente | Baixo a médio |
| systemd-creds com TPM | Linux | Sim | Sim | Médio |
Credenciais na memória, em backups e em atualizações
O armazenamento criptografado protege a credencial em repouso, mas ela ainda precisa ser descriptografada na RAM para montar o pacote CONNECT. Alguns hábitos reduzem a exposição no restante do caminho:
- Mantenha-a em um só lugar. Carregue a credencial uma única vez em um buffer pertencente ao módulo MQTT, em vez de copiá-la para strings globais, estruturas de configuração e objetos de diagnóstico.
- Exclua-a dos diagnósticos. Core dumps, relatórios de falhas e envios remotos de logs nunca devem conter o armazenamento de credenciais. No Linux, mantenha o arquivo de credenciais fora dos conjuntos de backup e dos pacotes de suporte.
- Não a faça passar pela nuvem. Um recurso de sincronização de configuração que envia as configurações do dispositivo ao seu backend deve ignorar totalmente os campos de credenciais.
- Grave atualizações de forma atômica. Quando uma nova credencial chega durante a rotação, grave-a em um segundo slot e só faça a troca após uma conexão bem-sucedida, para que uma queda de energia não deixe o dispositivo com uma senha gravada pela metade.
- Apague no descomissionamento. Apague o namespace da NVS ou o arquivo de credenciais como parte dos procedimentos de reset de fábrica e de descomissionamento.
Erros que vazam credenciais
- Imprimir a senha em logs de depuração ou em dumps de falha.
- Expô-la em uma página web do dispositivo "por conveniência", mesmo mascarada no HTML.
- Guardá-la em uma retained message MQTT ou enviá-la para um topic compartilhado.
- Deixar o modo de download via UART e o JTAG ativados em unidades de produção.
- Usar uma única credencial para a frota inteira, de modo que uma extração compromete tudo.
- Nunca fazer rotação, de modo que uma credencial vazada anos atrás ainda funciona.
O ponto 5 merece destaque. No ConnectMQ, cada client ID é vinculado às credenciais emitidas, então dê a cada dispositivo o seu próprio conjunto; uma senha extraída expõe então apenas um dispositivo, que você pode revogar e substituir com o processo descrito em como rotacionar credenciais MQTT. O isolamento de tenants significa que até as credenciais de um dispositivo comprometido só alcançam o seu próprio broker, e o bloqueio de IP após repetidas falhas de login atrasa atacantes que tentam adivinhar outras senhas. O conjunto mais amplo de controles está em boas práticas de segurança MQTT.
Comece a usar o ConnectMQ
Combine o armazenamento protegido no dispositivo com um broker MQTT gerenciado que cuida do TLS e da emissão de credenciais para você. Crie um broker Serverless gratuito em app.connectmq.com, sem cartão de crédito, e teste o armazenamento de credenciais do seu ESP32 ou Linux com um endpoint TLS real. O servidor MQTT público gratuito serve para protótipos rápidos, nunca para credenciais ou dados reais. Se você está começando no lado do hardware, o tutorial de MQTT com ESP32 é um bom ponto de partida, e o guia de broker MQTT na nuvem explica as opções de hospedagem.
Perguntas frequentes
A biblioteca Preferences do ESP32 é segura para senhas MQTT?
Somente se a criptografia da NVS e a criptografia de flash estiverem ativadas. Sem elas, a Preferences armazena os dados em texto claro na flash, que qualquer pessoa com acesso físico consegue copiar.
Preciso de um secure element para credenciais MQTT?
Nem sempre. Armazenamento criptografado com criptografia de flash e Secure Boot é adequado para muitos produtos; um secure element tem mais valor para identidade baseada em certificados ou implantações de alto valor.
Onde um gateway Linux deve guardar sua senha MQTT?
No mínimo em um arquivo 0600 pertencente à conta de serviço. Onde houver um TPM disponível, as credenciais criptografadas do systemd vinculam o segredo àquela máquina.
O TLS protege as credenciais armazenadas no dispositivo?
Não. O TLS protege as credenciais em trânsito. A proteção do armazenamento é uma camada separada, necessária no próprio dispositivo.



