Para usar MQTT sobre TLS en un ESP32 PLC, se envuelve el cliente de red (Ethernet o Wi-Fi) en un cliente TLS, se le da el certificado de la CA del broker como trust anchor y se conecta PubSubClient al puerto TLS del broker. Con TLS mutuo (mTLS), el PLC también envía su propio certificado de cliente y el broker sabe qué equipo se conecta. Esta guía usa test.mosquitto.org en el puerto 8884, que exige certificado de cliente.
La guía cubre toda la cadena: los comandos de OpenSSL para el certificado de cliente, el archivo de trust anchors de la CA del broker, un script que pasa el certificado y la clave a un archivo de cabecera, el código Arduino para el ESP32 PLC y una tabla de diagnóstico con los mensajes de error reales. Los pasos de certificados son los mismos para cualquier controlador o gateway que use MQTT sobre TLS, aunque no programes en Arduino.
¿Cómo funciona MQTT sobre TLS en un ESP32 PLC?
MQTT por sí solo no cifra nada. MQTT sobre TLS (también llamado MQTTS o MQTT seguro) transporta los paquetes MQTT normales dentro de una sesión TLS sobre TCP. El puerto registrado para MQTT sobre TLS es el 8883. Los brokers públicos de prueba suelen usar otros puertos para otras opciones de seguridad, como el 8884 de test.mosquitto.org.
TLS aporta tres cosas: cifrado de los datos en la red, la comprobación de que el servidor es el broker real y, con mTLS, la comprobación de que el cliente es un equipo conocido. Sin la comprobación del servidor, el PLC enviaría sus datos a cualquiera que pueda desviar el tráfico.
- 1 · TCPSocket al puerto 8884Ethernet o Wi-Fi, el mismo código
- 2 · ServidorCertificado del brokerSe verifica con el trust anchor guardado en el PLC
- 3 · ClienteCertificado del PLCEl PLC demuestra que tiene la clave privada
- 4 · MQTTCONNECT, PUBLISH, SUBSCRIBEPaquetes MQTT normales dentro de la sesión cifrada
En el ESP32 PLC, la librería SSLClient (OPEnS Lab, basada en BearSSL) hace los pasos 2 y 3. Funciona sobre cualquier Client de Arduino, así que el mismo sketch sirve para el puerto Ethernet o para Wi-Fi. PubSubClient hace el paso 4 y no sabe que hay TLS por debajo.
Estos son los archivos que se preparan y dónde acaba cada uno:
| Archivo | Qué es | Dónde va | ¿Secreto? |
|---|---|---|---|
mosquitto.org.crt | Certificado de la CA del broker | Se convierte en trust_anchors.h | No |
trust_anchors.h | La CA en formato BearSSL | Firmware del PLC | No |
client.key | Clave privada del PLC | Firmware, a través de ssl_client.h | Sí |
client.csr | Solicitud de certificado | Se envía a la CA una vez | No |
client.crt | Certificado de cliente firmado por la CA | Firmware, a través de ssl_client.h | No |
ssl_client.h | Certificado y clave como cadenas C | Firmware del PLC | Sí (contiene la clave) |
Hardware, librerías y red para MQTT sobre TLS
- Controlador: cualquier ESP32 PLC. El ejemplo original funcionaba en un ESP32 PLC 21+, y el código no usa E/S propias de un modelo.
- Paquete de placas: las placas de Industrial Shields para el Arduino IDE, que incluyen la librería Ethernet del PLC (ver cómo instalar las placas).
- Librerías: SSLClient (versión 1.6.11 en nuestra prueba) y PubSubClient (versión 2.8 en nuestra prueba). Las dos se instalan desde el gestor de librerías del Arduino IDE.
- Herramientas en el PC: OpenSSL y Python 3, para crear el certificado y los archivos de cabecera.
- Red: el PLC debe llegar al broker por el puerto TCP 8884, con DNS. En una red de planta, pide a IT que abra este puerto de salida.
Para configurar el Wi-Fi del PLC, consulta cómo conectar un PLC basado en ESP32 a Wi-Fi. En instalaciones sin cable, el mismo cliente TLS funciona también sobre la conexión 4G del ESP32 PLC, siempre que la librería del módem ofrezca un Client de Arduino.
Crear el certificado de cliente con OpenSSL
El certificado de cliente identifica a un PLC. Se crea una clave privada y una solicitud de certificado (CSR) en el PC, y la CA del broker firma la solicitud. En test.mosquitto.org, la CA es el formulario de firma de test.mosquitto.org/ssl.
openssl genrsa -out client.key 2048
openssl req -new -key client.key -out client.csr -subj "/C=ES/O=Your company/CN=esp32plc-line1"
Escribe tu propio país (C), organización (O) y nombre común (CN). La CSR debe incluir estos tres campos, y el firmante no acepta los valores por defecto de OpenSSL (ver el ejemplo de TLS mutuo de Espressif para el mismo broker). Un buen nombre común es el nombre del equipo, por ejemplo la línea y el PLC.
Abre client.csr con un editor de texto, pégalo entero en el formulario, con las líneas BEGIN y END, y envíalo. La página devuelve client.crt. Compruébalo con la CA antes de seguir:
openssl verify -CAfile mosquitto.org.crt client.crt
Por qué la clave no lleva contraseña. El PLC necesita la clave en claro para firmar el handshake TLS, así que una contraseña en client.key solo protege el archivo en el PC. Guarda client.key y ssl_client.h fuera de carpetas compartidas y repositorios de código, y crea una clave y un certificado para cada PLC.
Convertir la CA del broker en trust anchors de SSLClient
SSLClient no lee certificados PEM para comprobar el servidor. Necesita trust anchors: el nombre y la clave pública de la CA como arrays de C. Descarga el certificado de la CA del broker desde test.mosquitto.org/ssl/mosquitto.org.crt y conviértelo con pycert_bearssl.py, la herramienta del repositorio de SSLClient (necesita los paquetes de Python click, certifi y pyopenssl):
python pycert_bearssl.py convert --no-search mosquitto.org.crt -o trust_anchors.h
La utilidad online de certificados BearSSL no sirve en este caso: construye los trust anchors a partir de CA raíz públicas, y la CA de mosquitto.org es una CA autofirmada que no está en los almacenes públicos. Con --no-search, la herramienta usa el archivo tal cual.
Este es el resultado para la CA actual de mosquitto.org (válida del 9 de junio de 2020 al 7 de junio de 2030). Lo hemos generado de nuevo el 22 de octubre de 2026, y los bytes de la clave son los mismos que en la primera versión de esta guía:
#ifndef _TRUST_ANCHORS_H_
#define _TRUST_ANCHORS_H_
#ifdef __cplusplus
extern "C"
{
#endif
/* This file is auto-generated by the pycert_bearssl tool. Do not change it manually.
* Certificates are BearSSL br_x509_trust_anchor format. Included certs:
*
* Index: 0
* Label: mosquitto.org
* Subject: [email protected],CN=mosquitto.org,OU=CA,O=Mosquitto,L=Derby,ST=United Kingdom,C=GB
* Type: Certificate Authority
*/
#define TAs_NUM 1
static const unsigned char TA_DN0[] = {
0x30, 0x81, 0x90, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06,
0x13, 0x02, 0x47, 0x42, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04,
0x08, 0x0c, 0x0e, 0x55, 0x6e, 0x69, 0x74, 0x65, 0x64, 0x20, 0x4b, 0x69,
0x6e, 0x67, 0x64, 0x6f, 0x6d, 0x31, 0x0e, 0x30, 0x0c, 0x06, 0x03, 0x55,
0x04, 0x07, 0x0c, 0x05, 0x44, 0x65, 0x72, 0x62, 0x79, 0x31, 0x12, 0x30,
0x10, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x09, 0x4d, 0x6f, 0x73, 0x71,
0x75, 0x69, 0x74, 0x74, 0x6f, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55,
0x04, 0x0b, 0x0c, 0x02, 0x43, 0x41, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03,
0x55, 0x04, 0x03, 0x0c, 0x0d, 0x6d, 0x6f, 0x73, 0x71, 0x75, 0x69, 0x74,
0x74, 0x6f, 0x2e, 0x6f, 0x72, 0x67, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x09,
0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01, 0x16, 0x10, 0x72,
0x6f, 0x67, 0x65, 0x72, 0x40, 0x61, 0x74, 0x63, 0x68, 0x6f, 0x6f, 0x2e,
0x6f, 0x72, 0x67,
};
static const unsigned char TA_RSA_N0[] = {
0xc1, 0x34, 0x1c, 0xa9, 0x88, 0xcd, 0xf4, 0xce, 0xc2, 0x42, 0x8b, 0x4f,
0x74, 0xc7, 0x1d, 0xef, 0x8e, 0x6d, 0xd8, 0xb3, 0x6a, 0x63, 0xe0, 0x51,
0x99, 0x83, 0xeb, 0x84, 0xdf, 0xdf, 0x32, 0x5d, 0x35, 0xe6, 0x06, 0x62,
0x7e, 0x02, 0x11, 0x76, 0xf2, 0x3f, 0xa7, 0xf2, 0xde, 0xd5, 0x9c, 0xf1,
0x2d, 0x9b, 0xa1, 0x6e, 0x9d, 0xce, 0xb1, 0xfc, 0x49, 0xd1, 0x5f, 0xf6,
0xea, 0x37, 0xdb, 0x41, 0x89, 0x03, 0xd0, 0x7b, 0x53, 0x51, 0x56, 0x4d,
0xed, 0xf1, 0x75, 0xaf, 0xcb, 0x9b, 0x72, 0x45, 0x7d, 0xa1, 0xe3, 0x91,
0x6c, 0x3b, 0x8c, 0x1c, 0x1c, 0x6a, 0xe4, 0x19, 0x8e, 0x91, 0x88, 0x34,
0x76, 0xa9, 0x1d, 0x19, 0x69, 0x88, 0x26, 0x6c, 0xaa, 0xe0, 0x2d, 0x84,
0xe8, 0x31, 0x5b, 0xd4, 0xa0, 0x0e, 0x06, 0x25, 0x1b, 0x31, 0x00, 0xb3,
0x4e, 0xa9, 0x90, 0x41, 0x62, 0x33, 0x0f, 0xaa, 0x0d, 0xf2, 0xe8, 0xfe,
0xcc, 0x45, 0x28, 0x1e, 0xaf, 0x42, 0x51, 0x5e, 0x90, 0xc7, 0x82, 0xca,
0x68, 0xcb, 0x09, 0xb3, 0x70, 0x3c, 0x9c, 0xaa, 0xca, 0x11, 0x66, 0x3d,
0x6c, 0x22, 0xa3, 0xf3, 0xc3, 0x32, 0xbb, 0x81, 0x4f, 0x33, 0xc7, 0xdd,
0xc8, 0xa8, 0x06, 0x7a, 0xc9, 0x58, 0xa5, 0xdc, 0xdc, 0xe8, 0xd7, 0x74,
0xb1, 0x85, 0x24, 0xe7, 0xe3, 0xee, 0x93, 0xf4, 0x8f, 0xf7, 0x6b, 0xd8,
0xb1, 0xfb, 0xd9, 0xe4, 0xaf, 0xbf, 0x73, 0xd0, 0x40, 0x59, 0x7d, 0xd0,
0x26, 0x4f, 0x16, 0x1a, 0xc2, 0x51, 0xc4, 0x47, 0x49, 0x2c, 0x68, 0x13,
0xac, 0xa3, 0x18, 0xe7, 0x67, 0xcf, 0xb7, 0xfa, 0x3e, 0xf7, 0x8b, 0x20,
0x1e, 0x7b, 0xe2, 0x44, 0x0e, 0x47, 0x0b, 0x7c, 0x78, 0xf9, 0xf4, 0xca,
0x27, 0x6b, 0x4c, 0x2d, 0x62, 0x72, 0xd8, 0xa4, 0x10, 0x3d, 0xe7, 0x1d,
0x88, 0x4c, 0x50, 0xe5,
};
static const unsigned char TA_RSA_E0[] = {
0x01, 0x00, 0x01,
};
static const br_x509_trust_anchor TAs[] = {
{
{ (unsigned char *)TA_DN0, sizeof TA_DN0 },
BR_X509_TA_CA,
{
BR_KEYTYPE_RSA,
{ .rsa = {
(unsigned char *)TA_RSA_N0, sizeof TA_RSA_N0,
(unsigned char *)TA_RSA_E0, sizeof TA_RSA_E0,
} }
}
},
};
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* ifndef _TRUST_ANCHORS_H_ */
Si usas tu propio broker, genera el archivo a partir de la CA que firmó el certificado del broker, no a partir del certificado del broker.
Poner el certificado y la clave de cliente en ssl_client.h
El sketch lee el certificado de cliente y la clave desde ssl_client.h, como dos cadenas C en formato PEM. Este script escribe el archivo y se detiene si la clave todavía tiene contraseña:
"""Write ssl_client.h with the client certificate and the private key as C strings.
Usage: python pem_to_header.py client.crt client.key
"""
import sys
def c_string(path):
with open(path, "r", encoding="ascii") as f:
lines = [line.strip() for line in f if line.strip()]
if not lines or not lines[0].startswith("-----BEGIN"):
sys.exit(f"{path} is not a PEM file")
if "ENCRYPTED" in lines[0] or any(l.startswith("Proc-Type") for l in lines):
sys.exit(f"{path} is encrypted. Remove the passphrase first: "
f"openssl pkey -in {path} -out plain.key")
return "\n".join(f' "{line}\\n"' for line in lines)
if __name__ == "__main__":
if len(sys.argv) != 3:
sys.exit("Usage: python pem_to_header.py client.crt client.key")
cert, key = c_string(sys.argv[1]), c_string(sys.argv[2])
with open("ssl_client.h", "w", encoding="ascii") as out:
out.write("#pragma once\n\n")
out.write(f"const char my_cert[] =\n{cert};\n\n")
out.write(f"const char my_key[] =\n{key};\n")
print("ssl_client.h written")
python pem_to_header.py client.crt client.key
Copia ssl_client.h y trust_anchors.h en la carpeta del sketch. SSLClient acepta claves RSA y EC, en formato PKCS#1 (BEGIN RSA PRIVATE KEY) o en el formato PKCS#8 (BEGIN PRIVATE KEY) que OpenSSL 3 escribe por defecto.
Código para ESP32 PLC con MQTT sobre TLS, SSLClient y PubSubClient
El sketch se conecta a test.mosquitto.org por el puerto 8884 con el certificado de cliente, publica un mensaje en industrialshields/<client ID>/out e imprime cada mensaje que llega a industrialshields/<client ID>/in. Por defecto usa el puerto Ethernet. Para usar Wi-Fi, comenta la línea #define USE_ETHERNET y escribe el nombre y la contraseña de tu red.
// MQTT over TLS with client certificate (mutual TLS) on an ESP32 PLC
// Broker: test.mosquitto.org, port 8884 (TLS, client certificate required)
// Libraries: SSLClient (OPEnS Lab) and PubSubClient (Nick O'Leary)
#include <SSLClient.h>
#include <PubSubClient.h>
#include "trust_anchors.h" // CA certificate of the broker (BearSSL format)
#include "ssl_client.h" // client certificate and private key (PEM)
#define USE_ETHERNET // Comment out this line to use Wi-Fi
#ifdef USE_ETHERNET
#include <Ethernet.h>
byte mac[] = {0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED}; // use a unique MAC on your network
EthernetClient netClient;
#else
#include <WiFi.h>
const char* WIFI_SSID = "your-ssid";
const char* WIFI_PASS = "your-password";
WiFiClient netClient;
#endif
const char* BROKER_HOST = "test.mosquitto.org";
const uint16_t BROKER_PORT = 8884;
// SSLClient reads 16 values from an analog pin to seed its random generator.
// Use a pin that is not wired to the process.
SSLClient tlsClient(netClient, TAs, (size_t)TAs_NUM, Q0_7);
SSLClientParameters mtls = SSLClientParameters::fromPEM(my_cert, sizeof my_cert, my_key, sizeof my_key);
PubSubClient mqtt(tlsClient);
char clientId[24];
char topicOut[48];
char topicIn[48];
unsigned long lastAttempt = 0;
const unsigned long RETRY_MS = 5000;
void onMessage(char* topic, byte* payload, unsigned int length) {
Serial.print("Message on ");
Serial.print(topic);
Serial.print(": ");
for (unsigned int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
}
Serial.println();
}
// One connection attempt (TCP, TLS handshake, MQTT CONNECT).
// After a failure, loop() waits RETRY_MS before the next attempt.
bool connectMqtt() {
Serial.print("Connecting to the broker... ");
if (mqtt.connect(clientId)) {
Serial.println("connected");
mqtt.publish(topicOut, "hello industrial world");
mqtt.subscribe(topicIn);
return true;
}
Serial.print("failed, state=");
Serial.println(mqtt.state());
return false;
}
void setup() {
Serial.begin(115200);
// Unique client ID and topics: the broker closes an older session with the same ID
uint32_t chip = (uint32_t)((ESP.getEfuseMac() >> 24) & 0xFFFFFF); // last 3 bytes of the MAC
snprintf(clientId, sizeof clientId, "esp32plc-%06lX", (unsigned long)chip);
snprintf(topicOut, sizeof topicOut, "industrialshields/%s/out", clientId);
snprintf(topicIn, sizeof topicIn, "industrialshields/%s/in", clientId);
Serial.print("Client ID: ");
Serial.println(clientId);
tlsClient.setMutualAuthParams(mtls);
mqtt.setServer(BROKER_HOST, BROKER_PORT);
mqtt.setCallback(onMessage);
#ifdef USE_ETHERNET
Ethernet.begin(mac); // DHCP
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
Serial.println("Ethernet hardware not found");
}
if (Ethernet.linkStatus() == LinkOFF) {
Serial.println("Ethernet cable is not connected");
}
Serial.print("IP address: ");
Serial.println(Ethernet.localIP());
#else
WiFi.begin(WIFI_SSID, WIFI_PASS);
Serial.print("Connecting to Wi-Fi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
#endif
}
void loop() {
if (!mqtt.connected()) {
if (lastAttempt == 0 || millis() - lastAttempt >= RETRY_MS) {
lastAttempt = millis();
connectMqtt();
}
} else {
mqtt.loop(); // keep-alive and incoming messages
}
// The control logic of the PLC runs here, also while the broker is offline.
}
En un programa de PLC importan cuatro detalles:
- Client ID único. El broker cierra la sesión anterior cuando se conecta un cliente nuevo con el mismo ID. Dos PLC con el mismo ID fijo se desconectan entre sí en bucle. El sketch crea el ID a partir de la dirección MAC del chip ESP32 y también lo usa en los topics, porque cualquiera puede leer un broker público de pruebas.
- Reintentos de conexión que no bloquean el PLC.
loop()hace un intento cada 5 segundos y sigue. La lógica de control continúa mientras el broker o la red no están disponibles. Cada intento sigue durando lo que tarda el handshake TLS. - Semilla aleatoria. SSLClient inicializa su generador aleatorio con 16 lecturas del pin analógico indicado en el constructor. El ejemplo mantiene
Q0_7de la versión original. Antes de usarlo en planta, imprime 16 lecturas de ese pin en el monitor serie: si siempre dan el mismo valor, la semilla es predecible y las claves de sesión TLS quedan débiles; en ese caso usa una entrada analógica libre, sin cablear. - Hora de validación. Para comprobar las fechas del certificado del servidor, SSLClient usa por defecto la fecha y hora de compilación. Un PLC que funciona durante años no puede ver que un certificado ha caducado si no se llama a
setVerificationTime()con una fecha real, por ejemplo del RTC sincronizado con NTP.
PubSubClient publica solo con QoS 0 y se suscribe con QoS 0 o 1. Su buffer por defecto es de 256 bytes, que limita el tamaño de un mensaje con el topic incluido; usa mqtt.setBufferSize() para payloads JSON más largos. El keep-alive por defecto es de 15 segundos.
Cómo lo hemos probado (22 de octubre de 2026). Hemos compilado el sketch sin avisos (-Wall -Wextra) en un PC Linux junto con el código fuente real de SSLClient 1.6.11, BearSSL y PubSubClient 2.8, con el cliente Ethernet sustituido por un socket TCP. Contra un broker Mosquitto 2.0.18 local con require_certificate true en el puerto 8884, y con un certificado creado con los comandos anteriores: el código del PLC se conectó, publicó, recibió un mensaje de mosquitto_pub y se reconectó solo después de reiniciar el broker. Un certificado firmado por otra CA y un trust anchor incorrecto se rechazaron, con los mensajes que aparecen en la tabla de diagnóstico. Esta prueba no sustituye una prueba en el ESP32 PLC con tu red.
Errores de MQTT sobre TLS en el ESP32 PLC y cómo resolverlos
Con el nivel de log por defecto, SSLClient imprime sus errores en el monitor serie. mqtt.state() da el resultado MQTT: -2 indica que ha fallado la conexión de red o TLS, 5 indica que el broker ha rechazado al cliente.
| Mensaje o síntoma | Causa | Solución |
|---|---|---|
Failed to connect using m_client, state -2 | No hay conexión TCP: sin dirección IP, sin DNS o con el puerto 8884 cerrado | Revisa el cable o el Wi-Fi, el DNS y el firewall. Prueba el puerto desde un PC en la misma red. |
Chain could not be linked to a trust anchor | trust_anchors.h no corresponde a la CA del broker | Genera de nuevo el archivo a partir de la CA que firmó el certificado del broker. |
Unknown error code: 304 | El broker ha enviado una alerta TLS fatal (304 = 256 + alerta 48, CA desconocida): no confía en el certificado de cliente | Usa un certificado de cliente firmado por la CA del broker y comprueba que setMutualAuthParams() se ejecuta antes de la primera conexión. |
Log del broker: peer did not send certificate chain | El certificado de cliente o la clave no son PEM válido, y SSLClient se conecta sin mTLS | Genera de nuevo ssl_client.h con el script y revisa las líneas BEGIN y END. |
| Se agota el tiempo del handshake solo por Ethernet | El buffer del socket Ethernet es demasiado pequeño para los registros TLS | La documentación de SSLClient describe buffers de socket más grandes para la librería Ethernet de Arduino. Prueba el mismo código por Wi-Fi para confirmarlo. |
| El PLC se conecta y se desconecta cada pocos segundos | Otro cliente usa el mismo client ID | Usa un ID único para cada equipo, como en el sketch. |
| Conectado, pero no llega ningún mensaje | Error en el topic, o un mensaje más grande que el buffer de PubSubClient | Suscríbete a industrialshields/# con mosquitto_sub y revisa el tamaño del buffer. |
Ante un problema de TLS, prueba primero los mismos archivos desde un PC. Si este comando se conecta, los certificados son correctos y el problema está en el lado del PLC:
mosquitto_sub -h test.mosquitto.org -p 8884 --cafile mosquitto.org.crt --cert client.crt --key client.key -t 'industrialshields/#' -v
Más pruebas de MQTT desde un PC con Windows: MQTT troubleshooting in Windows.
Del broker de pruebas a un broker de planta con TLS
test.mosquitto.org es un servicio público para pruebas. Cualquiera puede suscribirse a tus topics y puede no estar disponible. En planta, los PLC se conectan a un broker propio: por ejemplo Mosquitto en un servidor o en un Raspberry PLC. Los mismos archivos y el mismo sketch funcionan: solo cambian el host, el puerto y la CA.
- Listener: puerto 8883 con
cafile,certfileykeyfile, yrequire_certificate truepara mTLS (referencia de mosquitto.conf). - Identidad y permisos:
use_identity_as_username trueconvierte el nombre común del certificado en el usuario MQTT, así un archivo ACL puede limitar cada PLC a sus propios topics. - Equipos perdidos o sustituidos: un certificado por PLC permite revocar solo ese equipo con una lista de revocación de certificados (
crlfile). - La clave en el firmware: se guarda en la memoria flash en texto claro. Con acceso físico y un programador, alguien la puede leer, salvo que el cifrado de flash esté activo en el ESP32. Cierra el armario y usa certificados de vida corta cuando sea posible.
- Hora: ajusta la hora de validación desde el RTC o por NTP, para detectar certificados caducados.
El broker Mosquitto central para una flota de seguidores solares de la PLC Solution Library muestra un broker de planta con autenticación y explica cuándo añadir el listener TLS. Para los conceptos de seguridad detrás de estas decisiones, lee MQTT security fundamentals in industrial automation.
Telemetría remota con ESP32 PLC y MQTT: ejemplos
MQTT cifrado es más útil cuando los PLC envían datos desde instalaciones remotas por 4G, redes compartidas o internet. Estas páginas muestran el resto del sistema:
- Simulador de telemetría remota: la red de agua de un municipio supervisada con estaciones ESP32 PLC por 4G y LoRaWAN, con detección de fugas y alarmas.
- Caso de tratamiento de agua: un ESP32 PLC 42 dosifica pH y cloro y envía los valores por MQTT a un Panel PC por Ethernet.
- Comandos remotos por MQTT en un ESP32 PLC con topic de respuesta y registro de datos MQTT offline-first con tarjeta SD, dos funciones que se pueden ejecutar sobre la conexión TLS.
- Concentrador MQTT en Node-RED para flotas de ESP32 PLC: el lado del servidor que recibe los datos.
Planifica un proyecto de MQTT sobre TLS con ESP32 PLC. Prueba el simulador de telemetría, o envíanos tu arquitectura (broker, red, número de PLC) y un ingeniero la revisará contigo.
Preguntas frecuentes sobre MQTT sobre TLS en ESP32
¿Qué puerto usa MQTT sobre TLS?
El puerto registrado es el 8883. Un broker puede usar cualquier puerto. En test.mosquitto.org, el puerto 8884 es el puerto TLS que exige certificado de cliente, y usa la CA de mosquitto.org.
¿Puedo usar usuario y contraseña en lugar de un certificado de cliente?
Sí. Mantén TLS con la comprobación del servidor (trust anchors) y pasa el usuario y la contraseña en mqtt.connect(clientId, user, password); en ese caso no se llama a setMutualAuthParams(). TLS protege la contraseña en la red. Un certificado de cliente evita las contraseñas compartidas y permite revocar un solo equipo.
¿Cuánto tarda el handshake TLS en un ESP32?
La documentación de SSLClient indica de 1 a 4 segundos para un handshake completo en microcontroladores, y recomienda prever de 4 a 10 segundos en un SAMD21; el ESP32 es más rápido. Después, la sesión queda abierta y cada mensaje solo añade el cifrado de unos pocos bytes.
¿Puedo usar WiFiClientSecure en lugar de SSLClient?
Por Wi-Fi, sí: el core Arduino del ESP32 incluye un cliente TLS basado en mbedTLS con funciones para la CA, el certificado de cliente y la clave. Aquí se usa SSLClient porque funciona sobre cualquier Client de Arduino, así que Ethernet y Wi-Fi comparten el mismo código.
¿Necesito un trust_anchors.h nuevo cuando cambia el certificado del broker?
No, si el broker recibe un certificado nuevo de la misma CA, porque el trust anchor es la CA. Hace falta un archivo nuevo cuando cambia la propia CA. La CA actual de mosquitto.org es válida hasta el 7 de junio de 2030.
¿Basta MQTT sobre TLS para proteger un PLC?
Protege los datos en la red y controla qué equipos se conectan. Siguen haciendo falta permisos por topic en el broker, una gestión segura de las claves, actualizaciones de firmware y segmentación de red entre la planta e internet.