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:
- Intercambio de claves: un cálculo ECDHE, normalmente sobre X25519 o la curva P-256. Ocurre en ambas versiones de TLS.
- 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.
- 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
- Mantén las conexiones abiertas. Un handshake al día es más barato que cualquier optimización de un handshake por minuto.
- Usa la reanudación de sesión donde tu pila la admita en los dispositivos que deben dormir.
- Activa la criptografía por hardware en la configuración de tu SDK.
- 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.
- No fijes una sola versión en el firmware. Permite la negociación para que los dispositivos sigan funcionando cuando cambien los servidores.
- 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:
- Versión mínima del protocolo: TLS 1.2 en los clientes, con TLS 1.3 permitido y preferido.
- Suites de TLS 1.2: solo intercambio de claves ECDHE con AES-GCM o ChaCha20-Poly1305.
- Suites de TLS 1.3: los valores por defecto de la librería, salvo que una política exija AES-256.
- Verificación de certificados y comprobación del hostname: siempre activadas.
- Estrategia de reconexión: conexiones de larga duración con backoff, no una conexión por mensaje.
- Reanudación: activada donde la pila la admita, para los dispositivos que duermen.
- 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.



