Seguridad MQTT

Cómo almacenar de forma segura las credenciales MQTT en dispositivos embebidos

Una comparativa práctica de las formas de proteger usuarios y contraseñas MQTT en dispositivos ESP32 y Linux, desde NVS cifrado y cifrado de flash hasta elementos seguros y credenciales de systemd respaldadas por TPM.

Ilustración de credenciales MQTT seguras en dispositivos, almacenadas en flash cifrada y en un elemento seguro sobre una placa de circuito
Pruébalo en tu propio broker MQTTConsigue un broker MQTT privado y gestionado con el plan Free y empieza con ejemplos de código listos para usar.
Pruébalo gratis Ejemplos de código Sin tarjeta

Mantener credenciales MQTT seguras en los dispositivos significa garantizar que el nombre de usuario y la contraseña que usa tu dispositivo para llegar al broker no puedan leerse desde imágenes de firmware, volcados de chips de flash, puertos de depuración ni archivos de log. Esta guía compara las opciones prácticas en dispositivos ESP32 y Linux, muestra la configuración de cada una y enumera los errores que con más frecuencia filtran credenciales sobre el terreno.

El modelo de amenazas en términos sencillos

Antes de elegir un método de almacenamiento, ten claro de quién te estás defendiendo:

Atacante Acceso Lo que intenta
Alguien con tu archivo de firmware Un .bin de una página de descargas o de un ticket de soporte Buscar cadenas en la imagen
Alguien con un dispositivo Acceso físico, un adaptador USB-serie Volcar la flash por UART, conectar JTAG
Malware o un fallo en el dispositivo Código que se ejecuta en el dispositivo Leer archivos o memoria
Alguien en la red Captura de paquetes Leer la contraseña en CONNECT

Del atacante de red se encarga TLS en el puerto 8883 (u 8084 para WebSockets), que todos los brokers de ConnectMQ admiten con un certificado de Let's Encrypt; la autenticación MQTT con usuario y contraseña explica por qué las credenciales nunca deben viajar por el puerto 1883. Este artículo se centra en los otros tres: proteger el secreto en reposo dentro del dispositivo.

Regla cero: las credenciales no van en el firmware

Un #define MQTT_PASSWORD "..." en el código fuente acaba en el binario del firmware, en tu historial de Git, en los logs de CI y en cada copia de la imagen. Ejecutar strings firmware.bin | grep -i pass sobre un proyecto aficionado típico suele encontrarla en segundos. El firmware debe ser idéntico en todos los dispositivos; las credenciales deben ser únicas por dispositivo. Mantenlos separados y escribe las credenciales en el almacenamiento durante el aprovisionamiento seguro de dispositivos IoT.

Opciones en el ESP32

NVS sin cifrar (base, no segura)

La biblioteca Non-Volatile Storage (o el wrapper Preferences de Arduino) guarda pares clave-valor en una partición de flash. Separa las credenciales del firmware, lo que ya es una mejora, pero los datos se almacenan sin cifrar. Cualquiera que pueda leer el chip de flash puede leer la contraseña.

Cifrado de NVS más cifrado de flash

ESP-IDF puede cifrar las particiones NVS. En el esquema clásico, las claves de cifrado de NVS viven en una partición nvs_keys dedicada, y esa partición está protegida por el cifrado de flash, cuya clave se guarda en eFuses que el software no puede leer. Los chips más recientes, como el ESP32-S3 y el ESP32-C3, también admiten un esquema basado en HMAC que deriva las claves de NVS de una clave en eFuse sin partición nvs_keys.

Una tabla de particiones con una partición de claves cifrada:

# 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,

Opciones relevantes de 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

El modo release es importante: el modo de desarrollo deja vías para volver a flashear imágenes en texto claro, lo que anula el objetivo en las unidades distribuidas. Secure Boot complementa el cifrado negándose a ejecutar firmware que no hayas firmado, de modo que un atacante no puede flashear código que lea e imprima las credenciales descifradas. Ambos queman eFuses de forma irreversible, así que prueba primero en placas de repuesto y sigue paso a paso la documentación de cifrado de flash de Espressif.

Leer las credenciales en un sketch de Arduino para ESP32 es igual con NVS cifrado o sin cifrar, y esa es precisamente la idea: la protección está por debajo de la API.

#include <Preferences.h>

struct MqttCreds { String user, pass, clientId; };

bool loadCreds(MqttCreds &c) {
  Preferences prefs;
  if (!prefs.begin("mqtt", true)) return false;   // solo lectura
  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 la identidad, nunca la contraseña
  Serial.printf("MQTT user=%s clientId=%s pass=<%u bytes>\n",
                c.user.c_str(), c.clientId.c_str(), c.pass.length());
}

Elementos seguros

Un elemento seguro como el ATECC608 de Microchip es un chip independiente que guarda claves y realiza operaciones criptográficas sin liberar nunca las claves privadas. Es más potente para la identidad basada en certificados, donde la clave privada del TLS mutuo nunca sale del chip. Con autenticación por contraseña, un elemento seguro puede seguir ayudando al guardar la clave que cifra el blob de la contraseña en la flash, lo que dificulta la extracción física. Añade coste a la lista de materiales, espacio en la placa e integración de drivers, por lo que es más habitual en productos con datos de alto valor o presión regulatoria.

Opciones en dispositivos Linux

Los gateways y equipos edge con Linux disponen de herramientas más completas.

Permisos de archivo (base)

Guarda las credenciales en un archivo que solo pueda leer la cuenta de servicio, fuera del directorio de la aplicación:

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

Esto protege frente a otros usuarios y servicios, no frente a alguien que extraiga la tarjeta SD. Combínalo con cifrado de disco completo para obtener protección física.

Credenciales cifradas de systemd con TPM

En sistemas con systemd y un chip TPM 2.0, systemd-creds cifra un secreto para que solo pueda descifrarse en esa máquina y únicamente para el servicio que lo declara:

# Cifra la contraseña, vinculada al TPM de esta 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

El servicio lee el secreto descifrado desde un directorio privado en memoria:

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()

Una imagen de disco copiada no sirve de nada en otra máquina, porque el TPM no desprecintará la credencial.

Comparativa de opciones de almacenamiento

Método Plataforma Protege frente a filtraciones del firmware Protege frente a volcados de flash Esfuerzo
Incrustada en el firmware Cualquiera No No Ninguno
NVS sin cifrar / archivo de configuración ESP32 / Linux Sí No Bajo
Cifrado de NVS más cifrado de flash, Secure Boot ESP32 Sí Sí Medio, eFuses irreversibles
Elemento seguro ESP32, Linux Sí Sí, el más sólido para claves Alto
Permisos de archivo más cifrado de disco Linux Sí Parcialmente Bajo a medio
systemd-creds con TPM Linux Sí Sí Medio

Credenciales en memoria, copias de seguridad y actualizaciones

El almacenamiento cifrado protege la credencial en reposo, pero aun así hay que descifrarla en la RAM para construir el paquete CONNECT. Algunos hábitos reducen la exposición en el resto de su recorrido:

  • Mantenla en un solo lugar. Carga la credencial una vez en un único buffer propiedad del módulo MQTT en lugar de copiarla en cadenas globales, estructuras de configuración y objetos de diagnóstico.
  • Exclúyela de los diagnósticos. Los volcados de núcleo, los informes de fallos y las subidas remotas de logs nunca deben contener el almacén de credenciales. En Linux, deja el archivo de credenciales fuera de las copias de seguridad y de los paquetes de soporte.
  • No la hagas pasar por la nube. Una función de sincronización de configuración que sube los ajustes del dispositivo a tu backend debe omitir por completo los campos de credenciales.
  • Escribe las actualizaciones de forma atómica. Cuando llegue una nueva credencial durante la rotación, escríbela en una segunda ranura y cambia solo tras una conexión correcta, para que un corte de luz no deje al dispositivo con una contraseña escrita a medias.
  • Bórrala al retirar el dispositivo. Elimina el espacio de nombres NVS o el archivo de credenciales como parte de los procedimientos de restablecimiento de fábrica y retirada.

Errores que filtran credenciales

  1. Imprimir la contraseña en logs de depuración o volcados de fallos.
  2. Mostrarla en una página web del dispositivo "por comodidad", aunque esté enmascarada en el HTML.
  3. Guardarla en un mensaje MQTT retenido o enviarla a un topic compartido.
  4. Dejar activados el modo de descarga por UART y JTAG en las unidades de producción.
  5. Usar una sola credencial para toda la flota, de modo que una extracción lo compromete todo.
  6. No rotar nunca, de modo que una credencial filtrada hace años sigue funcionando.

El punto 5 merece especial atención. En ConnectMQ cada client ID está vinculado a sus credenciales emitidas, así que da a cada dispositivo su propio conjunto; una contraseña extraída expondrá entonces un único dispositivo, que puedes revocar y sustituir con el proceso descrito en cómo rotar credenciales MQTT. El aislamiento de tenants hace que incluso las credenciales de un dispositivo comprometido solo lleguen a tu propio broker, y el bloqueo de IP tras inicios de sesión fallidos repetidos frena a los atacantes que intentan adivinar otras contraseñas. El conjunto más amplio de controles se trata en las buenas prácticas de seguridad MQTT.

Empieza con ConnectMQ

Combina el almacenamiento protegido en el dispositivo con un broker MQTT gestionado que se encargue por ti de TLS y de la emisión de credenciales. Crea un broker Serverless gratuito en app.connectmq.com, sin tarjeta de crédito, y prueba el almacenamiento de credenciales de tu ESP32 o dispositivo Linux contra un endpoint TLS real. Si eres nuevo en la parte de hardware, el tutorial de MQTT con ESP32 es un buen punto de partida, y la guía del broker MQTT en la nube explica las opciones de alojamiento.

Preguntas frecuentes

¿Es segura la biblioteca Preferences del ESP32 para contraseñas MQTT?

Solo si están activados el cifrado de NVS y el cifrado de flash. Sin ellos, Preferences guarda los datos en texto claro en la flash, que cualquiera con acceso físico puede volcar.

¿Necesito un elemento seguro para las credenciales MQTT?

No siempre. El almacenamiento cifrado con cifrado de flash y Secure Boot es suficiente para muchos productos; un elemento seguro aporta más valor en identidades basadas en certificados o despliegues de alto valor.

¿Dónde debe guardar su contraseña MQTT un gateway Linux?

Como mínimo, en un archivo con permisos 0600 propiedad de la cuenta de servicio. Si hay un TPM disponible, las credenciales cifradas de systemd vinculan el secreto a esa máquina.

¿TLS protege las credenciales almacenadas en el dispositivo?

No. TLS protege las credenciales en tránsito. La protección del almacenamiento es una capa independiente que necesitas en el propio dispositivo.

Artículos relacionados

Cómo rotar credenciales MQTT sin tiempo de inactividad

Una estrategia de doble credencial paso a paso para rotar usuarios y contraseñas MQTT en toda una flota: prepara la nueva credencial, cambia con respaldo, verifica y solo entonces revoca la antigua.

8 min de lectura

Listas de control de acceso (ACL) en MQTT: conceptos y diseño

Qué son las listas de control de acceso de MQTT, cómo expresan brokers como Mosquitto las reglas de lectura y escritura por topic, cómo se comportan las operaciones denegadas y cómo diseñar espacios de nombres de topics para que la autorización siga siendo sencilla.

8 min de lectura

Todos los artículos →

Tu broker MQTT privado, a unos pocos clics

Crea una cuenta, obtén las credenciales de tu broker y conecta tu primer dispositivo, sin servidores que instalar, parchear ni escalar.