Introducción
La seguridad en las comunicaciones IoT es fundamental para proteger datos sensores y comandos de control. En esta clase aprenderemos a implementar cifrado SSL/TLS en nuestro stack ESP32 + DHT11 + Mosquitto + Flask, asegurando que las temperaturas y datos de humedad viajen de forma segura desde el sensor hasta nuestra aplicación web.
SSL/TLS (Secure Sockets Layer/Transport Layer Security) proporciona autenticación, integridad y confidencialidad en las comunicaciones. Para sistemas IoT profesionales, es esencial implementar estas medidas de seguridad desde el diseño inicial del proyecto.
Conceptos Fundamentales
SSL/TLS funciona mediante un protocolo de handshake que establece una conexión segura entre cliente y servidor. En nuestro contexto IoT, esto se aplica en dos niveles:
1. Arquitectura de Seguridad IoT
- Nivel de Dispositivo: ESP32 → Broker MQTT (SSL/TLS)
- Nivel de Aplicación: Cliente Web → Flask Server (HTTPS)
- Broker Seguro: Mosquitto con certificados SSL
- Autenticación: Certificados cliente y servidor
2. Componentes de Certificados SSL
- CA (Certificate Authority): Autoridad que firma certificados
- Certificado del Servidor: Identifica el broker MQTT o servidor Flask
- Certificado del Cliente: Identifica dispositivos ESP32
- Clave Privada: Clave secreta para descifrado
- Clave Pública: Clave compartida para cifrado
3. Proceso de Handshake SSL/TLS
- Cliente envía "Client Hello" con versiones TLS soportadas
- Servidor responde "Server Hello" + certificado
- Cliente verifica certificado contra CA
- Intercambio de claves para sesión simétrica
- Activación de cifrado para datos de aplicación
4. Beneficios en Sistemas IoT
- Confidencialidad: Datos de sensores cifrados
- Integridad: Detección de modificaciones
- Autenticación: Verificación de identidad de dispositivos
- No repudio: Prueba de origen de datos
Implementación Práctica
Implementaremos SSL/TLS paso a paso, comenzando con la generación de certificados, configuración del broker Mosquitto, programación del ESP32 y finalmente el servidor Flask con HTTPS.
Paso 1: Generación de Certificados SSL
# Crear directorio para certificados
mkdir -p /etc/mosquitto/certs
cd /etc/mosquitto/certs
# Generar CA (Certificate Authority)
openssl req -new -x509 -days 3650 -extensions v3_ca -keyout ca.key -out ca.crt \
-subj "/C=ES/ST=Madrid/L=Madrid/O=IoT-Company/OU=CA/CN=IoT-CA"
# Generar clave privada del servidor
openssl genrsa -out server.key 2048
# Crear solicitud de certificado del servidor
openssl req -new -key server.key -out server.csr \
-subj "/C=ES/ST=Madrid/L=Madrid/O=IoT-Company/OU=Server/CN=localhost"
# Firmar certificado del servidor con CA
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-out server.crt -days 365
# Generar certificado cliente para ESP32
openssl genrsa -out client.key 2048
openssl req -new -key client.key -out client.csr \
-subj "/C=ES/ST=Madrid/L=Madrid/O=IoT-Company/OU=Device/CN=ESP32-DHT11"
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-out client.crt -days 365
Paso 2: Configuración Mosquitto con SSL
# /etc/mosquitto/mosquitto.conf
port 1883
listener 8883
# Rutas de certificados
cafile /etc/mosquitto/certs/ca.crt
certfile /etc/mosquitto/certs/server.crt
keyfile /etc/mosquitto/certs/server.key
# Configuración SSL
tls_version tlsv1.2
require_certificate true
use_identity_as_username true
# Configuración de logging
log_dest file /var/log/mosquitto/mosquitto.log
log_type all
# Configuración de persistencia
persistence true
persistence_location /var/lib/mosquitto/
# Control de acceso
allow_anonymous false
Código ESP32 con SSL/TLS:
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <DHT.h>
#include <ArduinoJson.h>
// Configuración DHT11
#define DHT_PIN 4
#define DHT_TYPE DHT11
DHT dht(DHT_PIN, DHT_TYPE);
// Configuración WiFi
const char* ssid = "Tu_WiFi";
const char* password = "Tu_Password";
// Configuración MQTT SSL
const char* mqtt_server = "192.168.1.100";
const int mqtt_port = 8883;
const char* mqtt_topic = "sensors/dht11";
// Certificados SSL (embebidos como strings)
const char* ca_cert = R"EOF(
-----BEGIN CERTIFICATE-----
MIIDXTCCAkWgAwIBAgIJAKoK/heBjcOuMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
BAYTAkVTMQ8wDQYDVQQIDAZNYWRyaWQxDzANBgNVBAcMBk1hZHJpZDEUMBIGA1UE
CgwLSW9ULUNvbXBhbnkwHhcNMjQwMTAxMDAwMDAwWhcNMzQwMTAxMDAwMDAwWjBF
MQswCQYDVQQGEwJFUzEPMA0GA1UECAwGTWFkcmlkMQ8wDQYDVQQHDAZNYWRyaWQx
FDASBgNVBAoMC0lvVC1Db21wYW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEA2K4mGzM8+X...
-----END CERTIFICATE-----
)EOF";
const char* client_cert = R"EOF(
-----BEGIN CERTIFICATE-----
MIIDXjCCAkagAwIBAgIJAKoK/heBjcOvMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
BAYTAkVTMQ8wDQYDVQQIDAZNYWRyaWQxDzANBgNVBAcMBk1hZHJpZDEUMBIGA1UE
CgwLSW9ULUNvbXBhbnkwHhcNMjQwMTAxMDAwMDAwWhcNMjUwMTAxMDAwMDAwWjBH
MQswCQYDVQQGEwJFUzEPMA0GA1UECAwGTWFkcmlkMQ8wDQYDVQQHDAZNYWRyaWQx
FjAUBgNVBAoMDUVTUDMyLURIVDExMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
AQC7yK4mGzM8+X...
-----END CERTIFICATE-----
)EOF";
const char* client_key = R"EOF(
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC7yK4mGzM8+XtN
kYjKlOP9s2Bx7K4mGzM8+XtNkYjKlOP9s2Bx7K4mGzM8+XtNkYjKlOP9s2Bx...
-----END PRIVATE KEY-----
)EOF";
WiFiClientSecure espClient;
PubSubClient client(espClient);
void setup() {
Serial.begin(115200);
dht.begin();
// Configurar certificados SSL
espClient.setCACert(ca_cert);
espClient.setCertificate(client_cert);
espClient.setPrivateKey(client_key);
// Conectar WiFi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("WiFi conectado");
Serial.print("IP: ");
Serial.println(WiFi.localIP());
// Configurar cliente MQTT
client.setServer(mqtt_server, mqtt_port);
client.setCallback(mqttCallback);
}
void loop() {
if (!client.connected()) {
reconnectMQTT();
}
client.loop();
// Leer sensores y publicar cada 30 segundos
static unsigned long lastRead = 0;
if (millis() - lastRead > 30000) {
publishSensorData();
lastRead = millis();
}
}
void reconnectMQTT() {
while (!client.connected()) {
Serial.print("Conectando MQTT SSL...");
String clientId = "ESP32-DHT11-";
clientId += String(random(0xffff), HEX);
if (client.connect(clientId.c_str())) {
Serial.println("conectado");
client.subscribe("commands/esp32");
} else {
Serial.print("falló, rc=");
Serial.print(client.state());
Serial.println(" reintentando en 5 segundos");
delay(5000);
}
}
}
void publishSensorData() {
float temperature = dht.readTemperature();
float humidity = dht.readHumidity();
if (isnan(temperature) || isnan(humidity)) {
Serial.println("Error leyendo DHT11");
return;
}
// Crear JSON
StaticJsonDocument<200> doc;
doc["device"] = "ESP32-DHT11";
doc["timestamp"] = millis();
doc["temperature"] = temperature;
doc["humidity"] = humidity;
doc["location"] = "Lab-IoT";
String payload;
serializeJson(doc, payload);
// Publicar con SSL
if (client.publish(mqtt_topic, payload.c_str())) {
Serial.println("Datos publicados: " + payload);
} else {
Serial.println("Error publicando datos");
}
}
void mqttCallback(char* topic, byte* message, unsigned int length) {
String messageTemp;
for (int i = 0; i < length; i++) {
messageTemp += (char)message[i];
}
Serial.println("Mensaje recibido: " + messageTemp);
}
Cliente MQTT SSL en Python:
import ssl
import json
import paho.mqtt.client as mqtt
from datetime import datetime
import logging
# Configuración logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class SecureMQTTClient:
def __init__(self, broker_host, broker_port=8883):
self.broker_host = broker_host
self.broker_port = broker_port
self.client = mqtt.Client()
self.client.on_connect = self.on_connect
self.client.on_message = self.on_message
self.client.on_disconnect = self.on_disconnect
self.sensor_data = {}
def setup_ssl(self, ca_cert_path, client_cert_path, client_key_path):
"""Configurar certificados SSL/TLS"""
context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
context.check_hostname = False
context.verify_mode = ssl.CERT_REQUIRED
# Cargar certificados
context.load_verify_locations(ca_cert_path)
context.load_cert_chain(client_cert_path, client_key_path)
self.client.tls_set_context(context)
logger.info("Certificados SSL configurados")
def connect(self):
"""Conectar al broker MQTT con SSL"""
try:
self.client.connect(self.broker_host, self.broker_port, 60)
self.client.loop_start()
logger