Seguridad MQTT

MQTT con TLS 1.3: handshakes, cipher suites y rendimiento

Cómo cambia TLS 1.3 las conexiones MQTT: menos viajes de ida y vuelta, un conjunto reducido de cipher suites robustos y otros compromisos para los microcontroladores. Incluye ejemplos de Python, Node.js, Mosquitto y OpenSSL.

Ilustración de MQTT con TLS 1.3 que compara un handshake TLS 1.2 de dos viajes de ida y vuelta con un handshake TLS 1.3 de uno solo entre un dispositivo y un broker
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

MQTT con TLS 1.3 acorta el handshake, elimina la criptografía débil y simplifica la elección de cipher suites, pero en los dispositivos alimentados por batería los costes reales vienen de otra parte. Este artículo explica qué cambia realmente entre TLS 1.2 y 1.3 en las conexiones MQTT, qué cipher suites permitir, cómo medir lo que negocia tu conexión y cómo configurar con sensatez los clientes Python, Node.js y Mosquitto.

Por qué importa la versión de TLS en MQTT

MQTT funciona sobre una conexión TCP de larga duración. Un dispositivo se conecta una vez, realiza un handshake TLS y después intercambia mensajes pequeños durante horas o días. Eso significa que el coste del handshake se paga rara vez en los dispositivos siempre encendidos, pero muy a menudo en los que duermen y se reconectan, como los sensores con batería que se despiertan cada pocos minutos. En esos dispositivos, los viajes de ida y vuelta del handshake y el tiempo de CPU dedicado a operaciones de clave pública dominan el consumo energético, y la versión de TLS y la elección del cipher suite importan mucho.

Si eres nuevo en la protección de conexiones MQTT, empieza por nuestra guía de configuración TLS en MQTT y vuelve aquí para los detalles de ajuste.

TLS 1.2 frente a TLS 1.3 de un vistazo

Aspecto TLS 1.2 TLS 1.3
Viajes de ida y vuelta del handshake completo 2 antes de los datos de aplicación 1 antes de los datos de aplicación
Handshake reanudado 1 viaje de ida y vuelta (session ID o ticket) 1 viaje de ida y vuelta (PSK), 0-RTT opcional
Intercambio de claves RSA o (EC)DHE Solo (EC)DHE, siempre con forward secrecy
Cipher suites Decenas, muchos débiles u obsoletos Cinco definidos, tres de uso generalizado
Certificado cifrado en el handshake No Sí
Modo CBC, RC4, SHA-1, RSA estático Permitidos (según la configuración) Eliminados

Para un cliente en un enlace móvil de alta latencia, ahorrar un viaje de ida y vuelta en cada conexión se nota. Para un dispositivo en una red local, el ahorro es pequeño comparado con el trabajo criptográfico.

De dónde viene realmente el coste del handshake

En un microcontrolador, tres operaciones dominan un handshake completo:

  1. Intercambio de claves: un cálculo ECDHE, normalmente sobre X25519 o la curva P-256. Ocurre en ambas versiones de TLS.
  2. Verificación del certificado: comprobar las firmas de la cadena del servidor. La verificación de firmas RSA es barata; la verificación ECDSA cuesta más en el cliente, pero los certificados ECDSA son más pequeños.
  3. Viajes de ida y vuelta por la red: cada uno mantiene la radio despierta.

TLS 1.3 reduce los viajes de ida y vuelta, pero no elimina las operaciones de clave pública. La mayor ganancia para los dispositivos que se reconectan con frecuencia suele ser la reanudación de sesión, que se salta la verificación del certificado y, en el modo PSK con intercambio de claves de TLS 1.3, sigue ofreciendo forward secrecy. Comprueba si tu pila TLS y el broker la admiten antes de depender de ella.

La aceleración por hardware también importa. La familia ESP32 acelera mediante mbedTLS el AES, el SHA y la aritmética de números grandes que usan RSA y ECC, lo que hace viable cualquiera de las dos versiones de TLS. Los microcontroladores más pequeños sin aceleración pueden beneficiarse de ChaCha20-Poly1305, que es rápido en software puro.

Cipher suites de MQTT con TLS 1.3

TLS 1.3 separa el cipher suite (cifrado simétrico y hash) de los algoritmos de intercambio de claves y de firma. Los suites que encontrarás en la práctica:

  • TLS_AES_128_GCM_SHA256: de implementación obligatoria, rápido con hardware AES, una buena opción por defecto.
  • TLS_AES_256_GCM_SHA384: clave más larga, algo más lento, a menudo elegido por motivos de política.
  • TLS_CHACHA20_POLY1305_SHA256: excelente en CPU sin aceleración AES.

Los tres son cifrado autenticado y se consideran robustos. Rara vez necesitarás restringirlos. Los otros dos suites definidos para TLS 1.3 (variantes CCM) aparecen sobre todo en entornos con recursos limitados.

Para TLS 1.2, si todavía debes admitirlo, permite solo suites AEAD con forward secrecy:

ECDHE-ECDSA-AES128-GCM-SHA256
ECDHE-RSA-AES128-GCM-SHA256
ECDHE-ECDSA-CHACHA20-POLY1305
ECDHE-RSA-CHACHA20-POLY1305
ECDHE-ECDSA-AES256-GCM-SHA384
ECDHE-RSA-AES256-GCM-SHA384

Evita los suites con CBC, RC4, 3DES, NULL, EXPORT o los que usan intercambio de claves RSA estático (nombres que empiezan por AES sin ECDHE).

Medir lo que negocia tu conexión

Nunca supongas qué versión admite un broker; mídelo. OpenSSL puede forzar cada versión e informar del resultado:

# Negociar la mejor versión que admitan ambos lados
openssl s_client -connect broker.connectmq.com:8883 -servername broker.connectmq.com -brief </dev/null

# Forzar TLS 1.3
openssl s_client -connect broker.connectmq.com:8883 -servername broker.connectmq.com -tls1_3 -brief </dev/null

# Forzar TLS 1.2
openssl s_client -connect broker.connectmq.com:8883 -servername broker.connectmq.com -tls1_2 -brief </dev/null

Las líneas Protocol version y Ciphersuite muestran el resultado. Ejecuta las mismas pruebas contra cualquier broker que evalúes y repítelas tras actualizar el broker o las librerías, porque las versiones admitidas pueden cambiar con el tiempo.

Para cronometrar handshakes desde un script, este fragmento de Python mide la latencia de conexión más handshake:

import socket
import ssl
import time

HOST, PORT = "broker.connectmq.com", 8883

def handshake(version: ssl.TLSVersion) -> None:
    ctx = ssl.create_default_context()
    ctx.minimum_version = version
    ctx.maximum_version = version
    start = time.perf_counter()
    try:
        with socket.create_connection((HOST, PORT), timeout=10) as sock:
            with ctx.wrap_socket(sock, server_hostname=HOST) as tls:
                elapsed = (time.perf_counter() - start) * 1000
                print(f"{tls.version()}  {tls.cipher()[0]}  {elapsed:.1f} ms")
    except ssl.SSLError as exc:
        print(f"{version.name} not negotiated: {exc.reason}")

for v in (ssl.TLSVersion.TLSv1_2, ssl.TLSVersion.TLSv1_3):
    for _ in range(3):
        handshake(v)

Ejecútalo varias veces; en un enlace rápido, el jitter de la red suele ser mayor que la diferencia entre versiones.

Configurar los clientes

La política de cliente más segura es "permitir TLS 1.2 y superiores, preferir la más reciente", que deja que cliente y servidor elijan la mejor versión común.

Python (paho-mqtt 2.x)

import ssl
import paho.mqtt.client as mqtt

ctx = ssl.create_default_context()           # verifica la cadena y el hostname
ctx.minimum_version = ssl.TLSVersion.TLSv1_2  # rechaza cualquier versión anterior

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="py-tls13-c71e")
client.tls_set_context(ctx)
# client.username_pw_set("YOUR_USERNAME", "YOUR_PASSWORD")
client.connect("broker.connectmq.com", 8883)
client.loop_start()
client.publish("connectmq/demo/c71e/tls", "negotiated securely", qos=1).wait_for_publish()
client.loop_stop()
client.disconnect()

En Python, set_ciphers() solo afecta a TLS 1.2 y anteriores; los suites de TLS 1.3 los gestionan los valores por defecto de OpenSSL, que ya son correctos.

Node.js (MQTT.js v5)

const mqtt = require("mqtt");

const client = mqtt.connect("mqtts://broker.connectmq.com:8883", {
  clientId: "node-tls13-c71e",
  protocolVersion: 5,
  minVersion: "TLSv1.2", // se pasa a tls.connect de Node
});

client.on("connect", () => {
  const sock = client.stream;
  console.log("TLS:", sock.getProtocol(), sock.getCipher().name);
  client.end();
});

Mosquitto como broker autoalojado

Si operas tu propio broker Mosquitto, controlas el lado del servidor:

listener 8883
certfile /etc/mosquitto/certs/server.crt
keyfile /etc/mosquitto/certs/server.key
cafile /etc/mosquitto/certs/chain.pem
tls_version tlsv1.2
ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305
ciphers_tls1.3 TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384

En Mosquitto, tls_version fija la versión mínima aceptada en el listener. Restringir un servidor solo a TLS 1.3 es tentador, pero deja fuera el firmware antiguo y las pilas TLS embebidas que todavía solo admiten TLS 1.2.

Dispositivos con recursos limitados: consejos prácticos

  1. Mantén las conexiones abiertas. Un handshake al día es más barato que cualquier optimización de un handshake por minuto.
  2. Usa la reanudación de sesión donde tu pila la admita en los dispositivos que deben dormir.
  3. Activa la criptografía por hardware en la configuración de tu SDK.
  4. Comprueba el soporte de TLS 1.3 en tu pila. Las versiones recientes de mbedTLS que usa ESP-IDF pueden compilarse con soporte de TLS 1.3 como opción de configuración; los SDK más antiguos solo ofrecen TLS 1.2, que sigue siendo seguro con los suites indicados arriba.
  5. No fijes una sola versión en el firmware. Permite la negociación para que los dispositivos sigan funcionando cuando cambien los servidores.
  6. Evita 0-RTT para los comandos. Los early data pueden reproducirse, lo que es peligroso para los comandos a actuadores.

La guía de certificate pinning en ESP32 cubre el lado de los certificados en TLS embebido, y cómo solucionar fallos del handshake TLS en MQTT ayuda cuando la negociación de versiones sale mal.

Checklist de política de seguridad

Usa esta lista cuando redactes la política TLS de un proyecto MQTT:

  1. Versión mínima del protocolo: TLS 1.2 en los clientes, con TLS 1.3 permitido y preferido.
  2. Suites de TLS 1.2: solo intercambio de claves ECDHE con AES-GCM o ChaCha20-Poly1305.
  3. Suites de TLS 1.3: los valores por defecto de la librería, salvo que una política exija AES-256.
  4. Verificación de certificados y comprobación del hostname: siempre activadas.
  5. Estrategia de reconexión: conexiones de larga duración con backoff, no una conexión por mensaje.
  6. Reanudación: activada donde la pila la admita, para los dispositivos que duermen.
  7. Revisión: vuelve a ejecutar las pruebas de versión con OpenSSL tras cada actualización del broker, las librerías o el firmware.

Mantener la política breve y por escrito facilita las auditorías y evita que excepciones puntuales "temporales", como una comprobación de certificado desactivada, lleguen a producción.

Deja el ajuste del servidor a un broker MQTT gestionado

En un broker autoalojado debes seguir tú mismo la retirada de protocolos, las recomendaciones de cifrado y la renovación de certificados. Un broker MQTT gestionado se ocupa por ti del lado del servidor. ConnectMQ ofrece TLS en el puerto 8883 y WebSocket seguro en el 8084 con un certificado de Let's Encrypt, así que solo necesitas la política de cliente anterior: verificar el certificado, permitir TLS 1.2 y superiores y dejar que la negociación elija la mejor opción. Usa el servidor MQTT público gratuito para realizar las mediciones de este artículo desde tu propia red.

Empieza con ConnectMQ

Regístrate en ConnectMQ en app.connectmq.com, elige el plan gratuito Serverless sin tarjeta de crédito y crea un broker. Apunta a él los ejemplos de Python o Node.js anteriores con tus credenciales generadas y comprueba qué negocia tu conexión.

Preguntas frecuentes

¿Sigue siendo seguro TLS 1.2 para MQTT?

Sí, si se limita a intercambio de claves ECDHE con cifrados AEAD como AES-GCM o ChaCha20-Poly1305. Permite TLS 1.3 junto a él para que los clientes lo usen cuando esté disponible.

¿Reduce TLS 1.3 la sobrecarga de los mensajes MQTT?

No de forma significativa. La sobrecarga por registro es similar; las principales ventajas son un handshake más corto, valores por defecto más robustos y certificados cifrados.

¿Qué cipher suite de TLS 1.3 deberían usar los dispositivos IoT?

TLS_AES_128_GCM_SHA256 en hardware con aceleración AES y TLS_CHACHA20_POLY1305_SHA256 en CPU sin ella. Ambos son robustos.

¿Debo usar early data 0-RTT con MQTT?

En general, no. Un atacante puede reproducir los early data, y los clientes MQTT ganan poco porque las conexiones son de larga duración.

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

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.