Ir al contenido

Building an MQTT over TLS Connection: A Guide for ESP32 PLC

TLS mutuo en el puerto 8884 con SSLClient y PubSubClient: certificado de cliente, trust anchors, código probado y solución de errores.
10 de febrero de 2025 por
Building an MQTT over TLS Connection: A Guide for ESP32 PLC
Boot & Work Corp. S.L, Joan Vilardaga Castro

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. 1 · TCPSocket al puerto 8884Ethernet o Wi-Fi, el mismo código
  2. 2 · ServidorCertificado del brokerSe verifica con el trust anchor guardado en el PLC
  3. 3 · ClienteCertificado del PLCEl PLC demuestra que tiene la clave privada
  4. 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:

ArchivoQué esDónde va¿Secreto?
mosquitto.org.crtCertificado de la CA del brokerSe convierte en trust_anchors.hNo
trust_anchors.hLa CA en formato BearSSLFirmware del PLCNo
client.keyClave privada del PLCFirmware, a través de ssl_client.hSí
client.csrSolicitud de certificadoSe envía a la CA una vezNo
client.crtCertificado de cliente firmado por la CAFirmware, a través de ssl_client.hNo
ssl_client.hCertificado y clave como cadenas CFirmware del PLCSí (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.

Ver la familia ESP32 PLC

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_7 de 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íntomaCausaSolución
Failed to connect using m_client, state -2No hay conexión TCP: sin dirección IP, sin DNS o con el puerto 8884 cerradoRevisa 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 anchortrust_anchors.h no corresponde a la CA del brokerGenera de nuevo el archivo a partir de la CA que firmó el certificado del broker.
Unknown error code: 304El broker ha enviado una alerta TLS fatal (304 = 256 + alerta 48, CA desconocida): no confía en el certificado de clienteUsa 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 chainEl certificado de cliente o la clave no son PEM válido, y SSLClient se conecta sin mTLSGenera de nuevo ssl_client.h con el script y revisa las líneas BEGIN y END.
Se agota el tiempo del handshake solo por EthernetEl buffer del socket Ethernet es demasiado pequeño para los registros TLSLa 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 segundosOtro cliente usa el mismo client IDUsa un ID único para cada equipo, como en el sketch.
Conectado, pero no llega ningún mensajeError en el topic, o un mensaje más grande que el buffer de PubSubClientSuscrí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, certfile y keyfile, y require_certificate true para mTLS (referencia de mosquitto.conf).
  • Identidad y permisos: use_identity_as_username true convierte 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:

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.

Abrir el simulador de telemetríaHablar con un ingeniero

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.

Buscar en nuestro blog

Building an MQTT over TLS Connection: A Guide for ESP32 PLC
Boot & Work Corp. S.L, Joan Vilardaga Castro 10 de febrero de 2025
Compartir esta publicación
Etiquetas

¿Estás buscando tu Controlador Lógico Programable ideal?

Echa un vistazo a esta comparativa de producto de varios controladores industriales basados en Arduino.

Comparamos entradas, salidas, comunicaciones y otras especificaciones con las de los equipos de otras marcas destacadas.

Comparativa de PLCs