← Índice del curso

Comunicación entre Dispositivos · Tema 49 · Depuración, selección y proyectos

Seguridad en la comunicación

Hasta acá, los errores tenían causas naturales: ruido, interferencia, un cable flojo. El CRC, las confirmaciones y los reintentos los resuelven porque el ruido no tiene intenciones. Pero un enlace también puede tener un adversario: alguien que escucha, que modifica un mensaje y recalcula el CRC, que graba la orden «abrir» y la repite más tarde, que se hace pasar por el servidor o por el sensor. Contra eso, nada de lo visto sirve, porque el CRC no lleva ningún secreto y cualquiera lo recalcula. Este tema cierra esa brecha con las herramientas de la criptografía aplicada: cifrar para que solo los autorizados lean, autenticar para saber quién envió el mensaje y si fue modificado, rechazar las repeticiones con contadores, ventanas y desafíos, cuidar las claves (donde casi siempre se pierde la batalla) y usar TLS cuando el enlace lo permite, con su costo real en un microcontrolador. Se recorre qué protege cada tecnología del curso (Wi-Fi, BLE, LoRaWAN, MQTT, Modbus) y qué deja sin proteger, y se escribe un módulo completo para ESP32 que sella y abre paquetes con AES en modo contador, HMAC-SHA256 y una ventana contra repeticiones. Con cinco herramientas interactivas cuyo SHA-256, HMAC y AES están implementados completos en la página y verificados con los vectores de la norma.

01 · Objetivo

Qué puede salir mal con alguien enfrente

Para decidir qué defender hay que decir primero de quién y de qué: un modelo de amenazas. Un sensor de temperatura de un invernadero, una cerradura y un medidor de energía no necesitan lo mismo. Las propiedades que se buscan son tres, y conviene no confundirlas:

  • Confidencialidad: solo quien debe puede leer el contenido. Se logra cifrando.
  • Integridad y autenticidad: el mensaje no fue modificado y viene de quien dice venir. Se logra con un código de autenticación o una firma. Son más importantes que la confidencialidad en la mayoría de los sistemas de control: que alguien lea la temperatura es una molestia; que alguien la falsifique y apague la calefacción es un problema.
  • Disponibilidad: el sistema sigue funcionando. Un atacante que inunda el enlace, interfiere la radio o agota la batería de un nodo no necesita romper ninguna clave. La criptografía casi no ayuda acá.
Qué puede hacer un atacante y qué lo frena.
El atacante…Ejemplo en un enlace del cursoDefensa
escuchaUn sniffer (tema 48) en un cable RS-485, en el aire de una red Wi-Fi o en los anuncios BLE lee todos los datosCifrado
modificaCambia un valor de la trama y recalcula el CRC (tema 44)Código de autenticación (HMAC)
repiteGraba la trama «abrir puerta», auténtica, y la reenvía de noche (tema 38)Contador, ventana o desafío
se hace pasar por otroUn servidor falso con el mismo nombre de red; un sensor falso que publica valores en un tema MQTTAutenticación mutua, certificados, claves por dispositivo
prueba clavesTodos los PIN de 4 dígitos de una cerradura; todas las contraseñas de un diccionarioClaves largas y al azar, límite de intentos
llega al dispositivoLee la clave del código, de la memoria flash o por el puerto de depuración de una placa robadaClaves por dispositivo, cifrado de flash, bloqueo del puerto de depuración
cambia el programaInstala un firmware propio por una actualización inalámbricaFirmware firmado, arranque seguro
impide el servicioInterfiere la radio, inunda el bus, agota la batería con mensajes falsosLímites de tasa, diseño robusto, no hay una solución criptográfica

Una advertencia: la seguridad es un problema de sistema, no de componente. Un enlace cifrado con una clave de fábrica igual en mil dispositivos tiene la seguridad de la clave más expuesta de las mil. Un cifrado perfecto con un nonce repetido se rompe sin tocar la clave (herramienta 3). Una conexión TLS que acepta cualquier certificado protege del curioso pero no del atacante activo. Por eso el tema no es una lista de algoritmos, sino cómo se arman bien las piezas y qué se rompe cuando se arman mal.

02 · Lo que no se inventa

Principios que no se negocian

  • No inventes criptografía. Diseñar un algoritmo propio, o combinar los conocidos de una manera original, es la fuente de casi todas las fallas. Se usan algoritmos estándar (AES, SHA-256, HMAC, ECDSA, ChaCha20-Poly1305), bibliotecas de prestigio (mbedTLS, que ya viene en el ESP32, libsodium, BearSSL) y los modos de uso recomendados. El ingenio se aplica en el diseño del sistema, no en el algoritmo.
  • El secreto está en la clave, no en el método. Se asume que el atacante conoce el algoritmo, el formato del mensaje y hasta el código del dispositivo (principio de Kerckhoffs). Un formato «oscuro» no es una defensa: con el sniffer del tema 48 se descifra en una tarde.
  • Cada capa protege su tramo. El cifrado de Wi-Fi (WPA2/WPA3) protege el aire entre el dispositivo y el punto de acceso; desde ahí hasta el servidor, los datos viajan en claro si no hay otra cosa. TLS protege de extremo a extremo entre dos aplicaciones. Un cifrado a nivel de mensaje protege de extremo a extremo aunque pasen por intermediarios (una puerta de enlace, un servidor MQTT). Hay que saber qué protege cada una y quién queda adentro.
  • Un CRC no es un código de autenticación. El CRC (tema 44) está pensado para el ruido; no tiene secreto. Se usa además de la autenticación, no en su lugar.
  • Cifrar sin autenticar es incompleto. Un mensaje cifrado y no autenticado puede ser alterado, o reemplazado por uno falso, sin que el receptor lo note (en muchos modos de cifrado, cambiar un bit del cifrado cambia el mismo bit del texto). La regla es autenticar siempre, y cifrar cuando haga falta.
  • Lo que se guarda, se puede robar. La clave en el código fuente está en el repositorio, en cada copia del programa y en la memoria flash de cada placa. Se diseña pensando que un dispositivo puede caer en manos del atacante: una clave distinta por dispositivo limita el daño a ese dispositivo.
  • La seguridad se mantiene. Aparecen fallas en las bibliotecas, se vencen los certificados, se pierden dispositivos. Un producto necesita un camino de actualización firmado y un plan para rotar claves y certificados.

03 · Mantener el secreto

Cifrar: simétrico, asimétrico y hash

Tres familias de herramientas, con funciones distintas que se combinan:

Las tres familias criptográficas.
FamiliaEjemplosPara qué sirveCosto en un microcontrolador
Cifrado simétrico (una clave compartida)AES-128 o AES-256, ChaCha20Cifrar los datos de los mensajes. Rápido: millones de bytes por segundoBajo; muchos chips tienen un acelerador por hardware de AES
Funciones hash (huella de un mensaje, sin clave)SHA-256, SHA-3Huella de un archivo (firmware), base de HMAC y de las firmas. MD5 y SHA-1 están rotas: no se usanBajo
Criptografía asimétrica (par de claves: pública y privada)ECDSA y ECDH (curvas P-256, Curve25519), RSA-2048 o mayorAcordar claves entre desconocidos, firmar (certificados, firmware), verificar identidadesAlto: cientos de milisegundos y mucho más consumo; por eso se usa para acordar una clave y se sigue con simétrica

El uso típico combina las tres: la asimétrica acuerda una clave entre dos que no se conocían y verifica la identidad (el certificado del servidor); a partir de ahí, un cifrado simétrico con un código de autenticación protege cada mensaje, por su velocidad. Es lo que hace TLS (sección 7). En un enlace punto a punto de un sistema propio, donde la clave se carga en fábrica, se usa solo lo simétrico.

AES en modo contador

Cifrado AES en modo contador Un bloque de contador formado por un nonce y el número de bloque entra a AES con la clave y produce un bloque de flujo de claves de 16 bytes. Ese flujo se combina con un XOR con los 16 bytes del texto para dar los 16 bytes del cifrado. Para el bloque siguiente, el número de bloque aumenta en uno. Descifrar es repetir lo mismo con el cifrado como entrada. Bloque de contadorClave de 128 bitsAES (un bloque)⊕Texto en claroCifrado nonce (8 bytes) · bloque n16 bytes por bloque16 bytes por bloque flujo de claves (16 bytes) Cada mensaje necesita un nonce que no se haya usado antes con esa clave. Con el mismo nonce, dos mensajes se cifran con el mismo flujo. Si el nonce se repite, el XOR de los dos cifrados es el XOR de los dos textos, y se rompe sin tocar la clave.

AES cifra bloques de 16 bytes. Para mensajes de cualquier largo se usa un modo de operación, y elegir mal el modo es la forma más común de arruinar un buen cifrado. El modo ECB (cada bloque por separado) deja ver los patrones: dos bloques iguales de texto dan dos iguales de cifrado; no se usa. El modo CBC exige un vector inicial impredecible y relleno, y con una mala gestión de los errores de relleno es vulnerable. El modo contador (CTR), que usa AES para producir un flujo de claves y lo combina con el texto mediante XOR, es simple, no necesita relleno (el cifrado tiene el mismo largo que el texto, lo que importa en tramas cortas) y se descifra con la misma operación. Su requisito es absoluto: el bloque de contador (nonce más número de bloque) no se debe repetir nunca con la misma clave.

¿Qué pasa si se repite? Con el mismo nonce, dos mensajes P₁ y P₂ se cifran con el mismo flujo F: C₁ = P₁ ⊕ F y C₂ = P₂ ⊕ F. El atacante calcula C₁ ⊕ C₂ = P₁ ⊕ P₂: la clave desapareció de la ecuación. Si conoce o adivina uno de los textos (un comando, un encabezado, una estructura conocida), obtiene el otro directamente. La herramienta 3 lo hace con dos comandos, «ABRIR VALVULA 01» y «CERRAR VALVULA 2»: con el mismo nonce, quien conoce el primero recupera el segundo sin clave alguna. Por eso el nonce es habitualmente un contador de mensajes que solo crece, combinado con el identificador del emisor para que dos dispositivos con la misma clave no coincidan, y por eso ese contador se guarda en memoria no volátil (sección 5).

En la práctica se prefiere un modo AEAD (cifrado autenticado con datos asociados), como AES-GCM o ChaCha20-Poly1305, que hace el cifrado y la autenticación en una sola operación y con una sola clave, y deja autenticar sin cifrar los encabezados (la dirección, el contador). Tienen la misma regla estricta del nonce (en GCM, repetirlo es catastrófico, además de revelar la clave de autenticación). En el taller de esta sección se combina AES-CTR con HMAC, siguiendo la regla de «primero cifrar, después autenticar el resultado», que es correcta y se entiende paso a paso; en un proyecto nuevo, un AEAD de una biblioteca es la mejor opción.

04 · Saber quién y qué

Autenticar: HMAC y la diferencia con el CRC

Un código de autenticación de mensaje (MAC) es un valor corto calculado a partir del mensaje y una clave secreta compartida. El emisor lo calcula y lo agrega al mensaje; el receptor lo recalcula con su copia de la clave y compara. Si coincide, el mensaje no fue modificado y lo generó alguien que conoce la clave. El MAC más usado es HMAC, construido sobre una función hash (HMAC-SHA256):

HMAC(K, m) = H( (K ⊕ opad) ‖ H( (K ⊕ ipad) ‖ m ) )

donde ipad y opad son constantes (0x36 y 0x5C repetidos) y ‖ es la concatenación. Sus propiedades son lo que lo hace útil: el resultado depende de todos los bits del mensaje y de la clave; cambiar un solo bit del mensaje cambia aproximadamente la mitad de los bits de la etiqueta (efecto avalancha: 123 de 256 en la herramienta 2), y sin la clave no hay manera de calcular la etiqueta de un mensaje nuevo, ni siquiera conociendo muchas etiquetas de otros mensajes.

El CRC y el HMAC frente a un atacante.
PreguntaCRC-16 (tema 44)HMAC-SHA256
¿Tiene clave?No: cualquiera lo calculaSí: sin la clave no se puede
¿Protege del ruido?Sí, con una detección garantizada de ráfagas cortasSí, aunque con 8 bytes la probabilidad de no detectar es 2⁻⁶⁴
¿Protege de un atacante que modifica?No: modifica el mensaje y recalcula el CRCSí
Tamaño habitual2 bytes32 bytes completo; 8 bytes truncado (o menos)
CostoMínimoBajo: unos microsegundos con aceleración por hardware

El truncado es habitual en enlaces con tramas cortas: en lugar de los 32 bytes de la etiqueta completa, se envían 8 (64 bits) o 4 (32 bits). La seguridad que se pierde se puede calcular: un atacante que no conoce la clave tiene que adivinar la etiqueta, con una probabilidad de 2−8n por intento para n bytes. Con 8 bytes, 1 en 1,8 × 10¹⁹: fuera de alcance. Con 4 bytes, 1 en 4 300 millones; a diez intentos por segundo, tardaría años, pero si el receptor no limita los intentos o si el enlace es rápido, empieza a ser una amenaza. Con 1 byte, un intento de cada 256: inútil. Importa también qué se autentica: toda la parte que debe ser fiable, incluidos el encabezado (el nodo, el contador) y el tipo de mensaje. Una etiqueta que cubre solo los datos permitiría cambiar el destino o el contador.

Dos reglas de implementación que se olvidan: (1) la comparación de la etiqueta se hace en tiempo constante (sin salir al primer byte distinto), porque una comparación que tarda más cuando más bytes coinciden permite adivinar la etiqueta byte a byte midiendo tiempos; (2) la etiqueta se verifica antes de actuar sobre cualquier contenido del mensaje, incluidos el contador y los campos: un mensaje no autenticado no se interpreta.

El paso lógico siguiente del MAC es la firma digital (ECDSA, RSA): sirve igual para la autenticidad, pero con un par de claves, y cualquiera con la clave pública puede verificar sin poder firmar. Es lo adecuado cuando muchos receptores deben verificar un único emisor (el fabricante firma el firmware, cada dispositivo verifica) o cuando no se quiere compartir un secreto. A cambio, es mucho más lenta que un MAC.

05 · Auténtico, pero viejo

Mensajes repetidos

Un mensaje auténtico, grabado y reenviado más tarde, sigue siendo auténtico: su etiqueta es la misma, su cifrado es el mismo, todo verifica. Es el ataque de repetición (replay), y no necesita romper nada: alcanza con una grabadora. Es el que abre la cerradura Bluetooth del tema 38 con la trama de «abrir» capturada el día anterior, el que repite una orden de pago o el que reenvía una lectura vieja para engañar a un sistema de control. Ni el cifrado ni el HMAC lo impiden por sí solos: lo impide hacer que cada mensaje valga una sola vez. Hay tres maneras, cada una con su costo:

Tres formas de rechazar las repeticiones.
MétodoCómo funcionaVentajasCostos y trampas
Contador de mensajesCada mensaje lleva un número que solo crece, dentro de lo autenticado. El receptor guarda el mayor visto y rechaza los que no lo superan, o los que ya vio dentro de una ventanaNo necesita reloj ni diálogo; un solo sentido; es el que usan LoRaWAN y los controles remotos con código variableEl emisor debe guardar el contador en memoria no volátil. El receptor guarda estado por emisor. Un reinicio del contador deja al emisor mudo
Marca de tiempoEl mensaje lleva la hora, autenticada. El receptor rechaza los que se apartan de su reloj más de una toleranciaSin estado por mensajeExige relojes sincronizados (un sensor sin reloj de tiempo real no lo tiene); dentro de la tolerancia, la repetición sigue funcionando
Desafío y respuestaEl receptor envía un número al azar (nonce) antes de aceptar; el emisor responde con el HMAC de ese número y de su petición. El desafío vale una vezGarantiza frescura sin contadores ni relojes; resiste aun el reinicioCuesta una ida y vuelta adicional; el receptor necesita una fuente de números aleatorios buena; no sirve para mensajes de un solo sentido
Desafío y respuesta Una línea de tiempo entre el teléfono y la cerradura. La cerradura envía un número aleatorio N, el desafío. El teléfono responde con la orden abrir y el HMAC de la clave compartida sobre N y la orden. La cerradura recalcula, compara, abre y olvida N. Un atacante que grabó esa respuesta e intenta repetirla recibe otro desafío distinto N', y la respuesta grabada ya no corresponde. Teléfono (con la clave)Cerradura (con la clave) 1 · desafío: un número al azar N 2 · respuesta: «abrir» + HMAC(K, N ‖ «abrir») La cerradura recalcula, compara, abre y descarta N. Un atacante que repite la grabación recibe otro desafío: 3 · desafío nuevo N′ ≠ N 4 · la respuesta grabada (calculada con N) no verifica La respuesta depende del desafío del momento: grabarla no sirve de nada.

El método más usado en dispositivos pequeños es el contador, y tiene una variante importante: en lugar de aceptar solo los números mayores que el último (contador estricto), se acepta una ventana deslizante. El receptor recuerda el mayor número visto M y cuáles de los últimos N ya vio. Un número mayor que M es nuevo y la ventana avanza; uno dentro de (M − N, M] se acepta si no se había visto (llegó desordenado); uno repetido o anterior a la ventana se rechaza. La ventana permite que lleguen fuera de orden los mensajes de una red inalámbrica o de un reintento sin perder los legítimos, y es la técnica de IPsec y de muchos protocolos de radio. La herramienta 4 compara las tres políticas: con el contador estricto, un mensaje legítimo que llegó desordenado se descarta; con la ventana, se acepta; sin protección, se aceptan todos, incluidas las copias.

Detalles de los que depende la seguridad:

  • El contador va dentro de lo autenticado (y se usa como nonce del cifrado). Si no, el atacante lo cambia.
  • Primero la etiqueta, después la ventana. Si el receptor actualizara su ventana antes de verificar, un atacante podría enviar un mensaje falso con un contador enorme y hacer que el receptor rechace todos los legítimos (un ataque de denegación de servicio con un solo paquete).
  • El emisor guarda su contador en memoria no volátil. Si un corte de energía lo pone en cero, sus mensajes nuevos parecen viejos y el receptor los rechaza (la herramienta 4, con el último valor en 1); peor, un nonce repetido en el cifrado rompe el secreto. Pero escribir en la memoria flash en cada mensaje la desgasta (decenas de miles a cientos de miles de ciclos por celda): la técnica es reservar contadores por adelantado. Se guarda un «techo» (el contador actual más 1000), se usa de a uno en memoria, y solo cuando se llega al techo se guarda uno nuevo. Después de un reinicio, se arranca del techo guardado: se salta algún número, pero jamás se repite uno. Con un mensaje por segundo, son 31 536 escrituras por año en lugar de 31 millones.
  • Un contador por emisor y por clave. Con varios nodos, el receptor lleva una ventana para cada uno.
  • Los contadores se acaban. Uno de 32 bits a un mensaje por segundo dura 136 años; uno de 16 bits (como el de LoRaWAN en su versión básica), 18 horas a un mensaje por segundo, por lo que ese protocolo lo extiende a 32 bits en el receptor. Se diseña con el ancho que alcance a la vida del producto.

06 · Donde se pierde la batalla

Las claves

Toda la criptografía de este tema vale lo que vale el secreto de sus claves. Un algoritmo inquebrantable con una clave expuesta no protege nada, y la experiencia dice que casi todas las fallas reales de dispositivos conectados son de gestión de claves, no de matemática.

Generación. Una clave debe ser elegida al azar por un generador de números aleatorios de calidad criptográfica: el del sistema operativo de una computadora o el generador por hardware del chip. Un random() común, o un valor derivado de millis() o de la hora de arranque, es predecible. Un ESP32 dispone de un generador de números aleatorios por hardware (esp_random()), pero hay que revisar en la documentación del chip cuándo es verdaderamente aleatorio (en el ESP32, mientras la radio está activa o con la fuente de entropía del cargador de arranque habilitada). Una clave derivada de una contraseña elegida por una persona tiene mucho menos secreto que su largo sugiere, y se ataca con diccionarios; la herramienta 1 calcula el espacio de una clave al azar, que es el mejor caso.

Fuerza bruta y su aritmética. Un atacante prueba claves una por una. Cuántas puede probar por segundo define cuántos bits hacen falta: contra un dispositivo en línea que responde en milisegundos, decenas por segundo; contra un archivo o una captura que se puede probar sin conexión, miles de millones por segundo con una tarjeta gráfica. Un PIN de 4 dígitos (13,3 bits) se agota en 17 minutos a diez intentos por segundo, y solo resiste si el dispositivo se bloquea tras pocos intentos fallidos (con un límite de 5, la probabilidad de acertar baja a 0,05 %). Una clave de 128 bits al azar (3,4 × 10³⁸ combinaciones) no se agota con ninguna tecnología imaginable: aun a un billón de intentos por segundo llevaría 10¹⁹ años. Las claves simétricas de 128 bits son hoy más que suficientes; 256 bits se usan por prudencia a largo plazo. El mejor atajo que tiene el atacante no es la fuerza bruta sino la clave del fabricante igual en todos los dispositivos, o la que está en el código.

El ciclo de vida de una clave.
EtapaQué hacerQué no hacer
GenerarAleatoria, con un generador criptográfico, de largo suficiente (128 bits o más para simétricas)Derivarla de la hora, de la dirección MAC, de una contraseña corta o de un random() sin entropía
Provisionar (cargar en el dispositivo)Una clave distinta por dispositivo, cargada en la fábrica o en la puesta en marcha, por un canal seguro (por ejemplo, un cable local) y no por la radio abiertaLa misma clave en todos los dispositivos del producto. Cuando una se descubre, se pierden todos
AlmacenarMemoria protegida: cifrado de la memoria flash y arranque seguro del ESP32, bloques de claves en eFuses, un elemento seguro dedicado. Lectura del puerto de depuración desactivadaConstantes en el código fuente (se suben al repositorio) o en la memoria flash sin cifrar (se lee con un programador en un minuto)
DerivarObtener una clave por uso (cifrado, autenticación, cada sesión) a partir de una maestra con una función de derivación (como el HMAC del taller). Un compromiso de una no compromete a las otrasUsar la misma clave para cifrar, autenticar y firmar
RotarCambiar las claves con regularidad y poder hacerlo a distancia, y tras un incidenteClaves eternas e imposibles de reemplazar sin ir a cada dispositivo
Revocar y retirarUna lista de dispositivos perdidos o comprometidos; borrar las claves de los equipos que se descartanTirar a la basura una placa con las claves de toda la instalación adentro

Un punto de diseño que ahorra mucho dolor: una clave por dispositivo, derivada de una maestra del fabricante mediante el identificador del dispositivo (K_dispositivo = HMAC(K_maestra, ID)). El servidor, que guarda la maestra, recalcula la clave de cualquier dispositivo sin tener una tabla enorme, y cada dispositivo guarda solo la suya: si se extrae la clave de uno, los demás siguen a salvo, y solo ese se revoca. La maestra, a su vez, vive en un único lugar muy protegido.

07 · El estándar de Internet

TLS y su costo

TLS (Transport Layer Security) es el protocolo que protege la mayor parte de Internet (el candado del navegador, HTTPS, MQTT seguro, el correo). Funciona sobre una conexión TCP y da, a cada extremo, lo que se vio en las secciones anteriores sin que la aplicación lo programe: cifrado, autenticación de cada registro y rechazo de repeticiones (un número de secuencia implícito). Lo que añade es la parte más difícil: acordar las claves con un desconocido y verificar con quién se habla, mediante criptografía asimétrica y certificados.

El apretón de manos de TLS 1.3 Una línea de tiempo entre el cliente (el dispositivo) y el servidor. Primero la conexión TCP, de una ida y vuelta. Después, el cliente envía el mensaje inicial con las versiones y su parte pública del intercambio de claves. El servidor responde con su parte pública, su certificado, una firma que prueba que posee la clave privada del certificado, y el mensaje de terminación. Desde ese momento todo va cifrado. El cliente verifica el certificado contra la autoridad en la que confía, comprueba el nombre y la fecha, envía su propia terminación y empieza a mandar los datos de la aplicación, ya cifrados y autenticados. Dispositivo (cliente)Servidor TCP: SYNTCP: SYN-ACKClientHello: versiones, cifrados, mi parte del intercambio de claves ServerHello (su parte) · Certificado · Firma · Finished — ya cifrado El cliente comprueba la cadena de certificados, el nombre y la fecha Finished del clienteDatos de la aplicación (cifrados y autenticados) TCP, una ida y vuelta; TLS 1.3, una más. TLS 1.2 necesita dos.

Qué verifica el cliente, y por qué es la parte que más se hace mal:

  • El certificado y su cadena. El servidor presenta un certificado: un documento firmado por una autoridad de certificación (CA) que liga su nombre con su clave pública. El cliente tiene guardada una lista de CA de confianza y verifica la firma del certificado, y la de cada certificado intermedio de la cadena, hasta una raíz que conoce. Un dispositivo pequeño no trae la lista completa del navegador: se le carga la raíz (o las raíces) del servicio al que se conecta.
  • El nombre. El certificado tiene que ser para el servidor con el que se quiere hablar (por ejemplo, broker.ejemplo.com). Sin esta comprobación, un atacante con cualquier certificado válido para otro nombre se hace pasar por el servidor.
  • La fecha. Un certificado vence. Para comprobarlo, el dispositivo tiene que saber la hora: un sensor que arranca sin reloj de tiempo real y sin red de tiempo falla al conectar con «certificado no válido» o, peor, el programador desactiva la comprobación para que «ande». Es uno de los problemas más comunes de TLS en dispositivos.
  • La prueba de posesión. El servidor firma el apretón de manos con la clave privada del certificado. Así se prueba que el que habla es el dueño del certificado y no alguien que lo copió.

El atajo peligroso se llama setInsecure() (en la biblioteca WiFiClientSecure del ESP32) o «verificar = falso» en otras: cifra, pero no autentica. El tráfico no se puede leer desde afuera, pero se le está hablando a quien responda, y un atacante entre el dispositivo y el servidor (un punto de acceso falso, un router comprometido) establece su propia conexión TLS con el dispositivo y otra con el servidor, y lee y modifica todo: un ataque de hombre en el medio. Una conexión TLS sin verificar el certificado es mejor que nada contra el curioso pasivo y equivale a nada contra el atacante activo. Se carga la raíz de la CA con setCACert() y se mantiene la hora correcta.

Con un cliente que también debe probar quién es (un sensor que se conecta a un servidor MQTT), hay dos caminos. Usuario y contraseña sobre TLS: simple, protegido del escucha por el cifrado, pero con las debilidades de cualquier contraseña. Certificado de cliente (autenticación mutua o mTLS): cada dispositivo tiene su par de claves y su certificado; el servidor verifica cuál es y puede revocarlo individualmente. Es lo más robusto, y lo que usan los servicios de IoT de las grandes plataformas. En redes restringidas existe también TLS con clave precompartida (PSK), sin certificados ni criptografía asimétrica cara, y DTLS, la versión de TLS para UDP, que usa CoAP.

El costo en un dispositivo pequeño

El apretón de manos cuesta tres cosas, y la herramienta 5 las calcula: idas y vueltas por la red (TCP, una; TLS 1.3, una más; TLS 1.2, dos más), bytes (la cadena de certificados es lo mayor, de 2 a 5 kB), y CPU para la criptografía asimétrica, que en un ESP32 son cientos de milisegundos con el procesador y la radio encendidos. Con 100 ms de latencia, 128 kbit/s y 2500 bytes de certificados, un TLS 1.2 nuevo tarda 1,1 s antes de poder enviar el primer dato (300 de red, 238 de transferencia y 600 de CPU) y mueve unos 3800 bytes. Después, cada mensaje solo paga el registro: 22 a 29 bytes más. La consecuencia para un dispositivo a batería: no se reconecta para cada lectura. Se mantiene la conexión abierta (en MQTT, con un keep-alive ajustado), o se reanuda la sesión (TLS guarda un secreto de la sesión anterior y evita certificados y casi toda la CPU: el mismo apretón con TLS 1.3 reanudado tarda 418 ms y mueve 600 bytes), o se usa un enlace que no admita TLS (LoRa, BLE, RS-485) con un cifrado y autenticación por mensaje, como el de la sección 9. El tema 36 mostró lo que cuesta cada segundo con la radio de Wi-Fi encendida.

08 · Lo que viene incluido y lo que no

Qué protege cada tecnología del curso

Seguridad de las tecnologías de comunicación vistas en el curso.
TecnologíaQué ofreceQué no cubreQué conviene hacer
UART, SPI, I²C, RS-485, Modbus RTU (temas 7 a 27)Nada: ni cifrado ni autenticación. Cualquiera conectado al cable lee y escribe. Modbus acepta cualquier orden de cualquier origenTodoSeguridad física del cable y de los puertos; si el bus sale de un recinto controlado, una puerta de enlace con autenticación; un módulo como el de la sección 9 para comandos críticos
Wi-Fi (temas 30 a 36)WPA2/WPA3 cifra y autentica el tramo entre el dispositivo y el punto de acceso. WPA3 (SAE) resiste el ataque de diccionario sobre una captura del apretón de manos que debilita al WPA2 con claves cortasLo que ocurre después del punto de acceso: la red local y el viaje a Internet. Quien conoce la contraseña de la red ve el tráfico de los demás en WPA2-PSKUna clave larga y al azar; TLS de extremo a extremo (tema 35); no confiar en que «la red es privada»
MQTT (tema 32)Usuario y contraseña, y listas de permisos por tema. En el puerto 1883, todo en claroSin TLS, la contraseña y los mensajes viajan en claroPuerto 8883 (TLS), verificación del certificado, un usuario o certificado por dispositivo con permisos solo sobre sus temas
Bluetooth Low Energy (temas 37 y 38)Emparejamiento y cifrado AES-CCM. Las conexiones seguras LE usan el acuerdo de claves ECDH de curva P-256El método «Just Works» no protege del hombre en el medio. La publicidad es pública. Un PIN de 4 dígitos es débil si se puede probar sin límiteComparación numérica o clave de acceso (passkey) para emparejar; cifrado y autenticación a nivel de aplicación para lo crítico; un contador contra repeticiones
LoRaWAN (tema 39)AES-128 en dos capas: una clave de red para la integridad del mensaje (un código de 4 bytes, AES-CMAC) y una clave de aplicación para cifrar los datos de extremo a extremo; contadores de trama contra repeticionesCon activación por personalización (ABP) y contadores que se reinician, el servidor rechaza o acepta mal; las claves que vienen en el código son un riesgoActivación por aire (OTAA), claves únicas por dispositivo, contador en memoria no volátil
RFID / NFC de baja gama, controles remotos de código fijo (tema 39)Nada, o un código fijo que se repiteQuien graba el código lo reproduceTarjetas con autenticación criptográfica; controles de código variable (rolling code) con contador autenticado
Actualización inalámbrica de firmware (OTA)Depende del sistemaUn firmware sin firmar permite a cualquiera instalar el suyo y tomar el control totalFirmware firmado (ECDSA) y verificado por el arranque seguro antes de instalar, con protección contra volver a versiones viejas

09 · Con el microcontrolador

Sellar y abrir paquetes con AES, HMAC y ventana

El programa siguiente es el módulo de seguridad completo para un ESP32, sobre las bibliotecas de mbedTLS que el chip ya trae (con aceleración de AES y SHA por hardware). Un paquete sellado tiene el formato [nodo 1][contador 4][datos cifrados][etiqueta 8]: AES-128 en modo contador cifra los datos con el par (nodo, contador) como nonce, un HMAC-SHA256 truncado a 8 bytes autentica todo el paquete, incluido el encabezado, y el receptor, antes de mirar nada, verifica la etiqueta en tiempo constante y recién entonces consulta la ventana de 32 contadores. Las dos claves (cifrado y autenticación) se derivan de una maestra, y el contador del emisor se reserva por bloques de 1000 en la memoria no volátil, para que ni un corte de energía repita un nonce. El setup() hace en una sola placa las seis pruebas de la sección 10: el paquete original, la grabación repetida, un bit alterado, un paquete desordenado, un contador viejo y una etiqueta inventada. No necesita una segunda placa ni conexiones.

#include <Preferences.h>
#include "mbedtls/md.h"
#include "mbedtls/aes.h"

// ---------- Configuración ----------
const uint8_t  ID_NODO = 1;
const uint8_t  LARGO_ETIQUETA = 8;                             // 64 bits de etiqueta
const uint32_t SALTO = 1000;                                   // contadores que se reservan de una vez en la memoria no volátil
const uint8_t  CAB = 5;                                        // nodo (1 byte) y contador (4 bytes)

// SOLO PARA EL EJEMPLO. Una clave real se genera al azar, es distinta en cada dispositivo
// y se guarda en memoria protegida (cifrado de flash, eFuses), nunca en el código fuente.
const uint8_t MAESTRA[16] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F };
uint8_t kMac[16], kEnc[16];                                    // una clave para cada uso, derivadas de la maestra

// ---------- HMAC-SHA256 con mbedTLS (viene con el ESP32) ----------
void hmac(const uint8_t *clave, size_t nc, const uint8_t *msg, size_t n, uint8_t salida[32]) {
    mbedtls_md_context_t ctx;
    mbedtls_md_init(&ctx);
    mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1);
    mbedtls_md_hmac_starts(&ctx, clave, nc);
    mbedtls_md_hmac_update(&ctx, msg, n);
    mbedtls_md_hmac_finish(&ctx, salida);
    mbedtls_md_free(&ctx);
}

void derivarClaves() {
    uint8_t t[32];
    hmac(MAESTRA, sizeof(MAESTRA), (const uint8_t *)"mac", 3, t);
    memcpy(kMac, t, 16);
    hmac(MAESTRA, sizeof(MAESTRA), (const uint8_t *)"enc", 3, t);
    memcpy(kEnc, t, 16);
}

// ---------- AES-128 en modo contador: el bloque de contador es [nodo][contador][ceros] ----------
void cifrarCtr(const uint8_t *cab, const uint8_t *entrada, uint8_t *salida, size_t n) {
    uint8_t bloque[16] = { 0 }, flujo[16];
    memcpy(bloque, cab, CAB);                                  // cada (nodo, contador) da un flujo de claves distinto
    size_t desplazamiento = 0;
    mbedtls_aes_context aes;
    mbedtls_aes_init(&aes);
    mbedtls_aes_setkey_enc(&aes, kEnc, 128);
    mbedtls_aes_crypt_ctr(&aes, n, &desplazamiento, bloque, flujo, entrada, salida);   // cifrar y descifrar son lo mismo
    mbedtls_aes_free(&aes);
}

bool iguales(const uint8_t *a, const uint8_t *b, size_t n) {   // en tiempo constante: no filtra dónde difieren
    uint8_t d = 0;
    for (size_t i = 0; i < n; i++) d |= a[i] ^ b[i];
    return d == 0;
}

// ---------- El contador del emisor sobrevive a los reinicios ----------
Preferences prefs;
uint32_t contador = 0, techo = 0;

void iniciarContador() {
    prefs.begin("seguridad", false);
    contador = prefs.getUInt("techo", 0);                      // se arranca desde el último techo reservado: nunca se repite uno
    techo = contador + SALTO;
    prefs.putUInt("techo", techo);
}

uint32_t siguienteContador() {
    if (contador >= techo) { techo += SALTO; prefs.putUInt("techo", techo); }   // una escritura cada SALTO mensajes, no en cada uno
    return contador++;
}

// ---------- Sellar: [nodo][contador][datos cifrados][etiqueta] ----------
size_t sellarCon(uint8_t *p, uint32_t c, const uint8_t *datos, size_t n) {
    p[0] = ID_NODO;
    p[1] = c >> 24; p[2] = c >> 16; p[3] = c >> 8; p[4] = c;
    cifrarCtr(p, datos, p + CAB, n);                           // primero se cifra...
    uint8_t mac[32];
    hmac(kMac, sizeof(kMac), p, CAB + n, mac);                 // ...y después se autentica todo, también el encabezado
    memcpy(p + CAB + n, mac, LARGO_ETIQUETA);
    return CAB + n + LARGO_ETIQUETA;
}
size_t sellar(uint8_t *p, const uint8_t *datos, size_t n) { return sellarCon(p, siguienteContador(), datos, n); }

// ---------- Abrir: ventana deslizante de 32 contadores ----------
struct Ventana { bool vacia = true; uint32_t maximo = 0, mapa = 0; };
Ventana ventana;                                               // con varios nodos, una por cada uno

bool aceptar(Ventana &v, uint32_t c) {
    if (v.vacia) { v.vacia = false; v.maximo = c; v.mapa = 1; return true; }
    if (c > v.maximo) {                                        // nuevo: la ventana avanza
        uint32_t d = c - v.maximo;
        v.mapa = d >= 32 ? 0 : v.mapa << d;
        v.mapa |= 1;
        v.maximo = c;
        return true;
    }
    uint32_t d = v.maximo - c;
    if (d >= 32) return false;                                 // demasiado viejo
    if (v.mapa & (1UL << d)) return false;                     // ya visto
    v.mapa |= 1UL << d;                                        // llegó desordenado y no se había visto
    return true;
}

const char *abrir(const uint8_t *p, size_t total, uint8_t *datos, size_t &n) {
    if (total < CAB + LARGO_ETIQUETA) return "demasiado corto";
    size_t m = total - CAB - LARGO_ETIQUETA;
    uint8_t mac[32];
    hmac(kMac, sizeof(kMac), p, CAB + m, mac);
    if (!iguales(mac, p + CAB + m, LARGO_ETIQUETA)) return "etiqueta incorrecta";   // PRIMERO la etiqueta
    if (p[0] != ID_NODO) return "nodo desconocido";
    uint32_t c = ((uint32_t)p[1] << 24) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 8) | p[4];
    if (!aceptar(ventana, c)) return "repetido o demasiado viejo";                  // recién ahora la ventana
    cifrarCtr(p, p + CAB, datos, m);
    n = m;
    return "ok";
}

void mostrar(const char *t, const uint8_t *p, size_t n) {
    Serial.printf("%s (%u bytes):", t, (unsigned)n);
    for (size_t i = 0; i < n; i++) Serial.printf(" %02X", p[i]);
    Serial.println();
}

void setup() {
    Serial.begin(115200);
    derivarClaves();
    iniciarContador();
    Serial.printf("contador persistente: arranca en %lu y reserva hasta %lu\n", contador, techo);

    const uint8_t registro[9] = { 0, 1, 7, 0x2E, 0x09, 0x7C, 0x15, 0xE4, 0x0C };   // el registro del tema 46
    uint8_t p[40], datos[32];
    size_t n = 0;

    size_t t = sellarCon(p, 100, registro, sizeof(registro));
    mostrar("paquete sellado", p, t);
    Serial.printf("1) original: %s\n", abrir(p, t, datos, n));
    mostrar("   datos recuperados", datos, n);
    Serial.printf("2) la misma grabación otra vez: %s\n", abrir(p, t, datos, n));

    uint8_t malo[40];
    memcpy(malo, p, t);
    malo[7] ^= 0x01;                                           // un bit alterado
    Serial.printf("3) un bit alterado: %s\n", abrir(malo, t, datos, n));

    t = sellarCon(p, 102, registro, sizeof(registro));
    Serial.printf("4) llega el 102: %s", abrir(p, t, datos, n));
    t = sellarCon(p, 101, registro, sizeof(registro));
    Serial.printf(", después el 101: %s\n", abrir(p, t, datos, n));

    t = sellarCon(p, 5, registro, sizeof(registro));
    Serial.printf("5) un emisor que se reinició y manda el 5: %s\n", abrir(p, t, datos, n));

    t = sellarCon(p, 103, registro, sizeof(registro));
    memset(p + t - LARGO_ETIQUETA, 0, LARGO_ETIQUETA);         // una etiqueta inventada
    Serial.printf("6) etiqueta inventada: %s\n", abrir(p, t, datos, n));

    uint32_t t0 = micros();
    for (int i = 0; i < 200; i++) { t = sellarCon(p, 1000 + i, registro, sizeof(registro)); abrir(p, t, datos, n); }
    Serial.printf("sellar y abrir un paquete de %u bytes: %.0f us en promedio\n", (unsigned)t, (micros() - t0) / 200.0);
}

void loop() {}

Qué hay que notar:

  • El orden dentro de abrir() es el importante: largo mínimo, etiqueta, nodo, ventana y, solo al final, el descifrado. Nada del paquete se interpreta antes de verificar la etiqueta, y la ventana no se toca con paquetes que no verifican.
  • La etiqueta cubre el encabezado y el cifrado (p hasta CAB + n): el nodo y el contador no se pueden cambiar. Se autentica el resultado del cifrado (encrypt-then-MAC), de modo que un paquete alterado se rechaza sin llegar a descifrarse.
  • Dos claves distintas, derivadas con hmac(MAESTRA, "mac") y hmac(MAESTRA, "enc"): usar una sola clave para dos funciones criptográficas puede producir interacciones indeseadas. Las constantes de las claves del ejemplo están a la vista a propósito. En un producto no se escriben en el código: se generan al azar, se cargan en cada dispositivo y se guardan con el cifrado de flash activado.
  • La comparación es en tiempo constante (iguales() acumula las diferencias y decide al final), para que el tiempo de respuesta no revele cuántos bytes de la etiqueta fueron correctos.
  • El contador persiste con reserva: iniciarContador() arranca desde el último techo guardado, y siguienteContador() escribe una vez cada 1000 mensajes. Tras un reinicio se saltan hasta 1000 números, pero ninguno se repite; la receptora, con su ventana, no tiene problema con un salto hacia adelante.
  • El bloque de contador de AES es [nodo][contador][ceros]; el contador de bloque interno de mbedTLS ocupa los bytes finales, de modo que ningún flujo se repite mientras el par (nodo, contador) sea único. Dos nodos que comparten clave nunca colisionan porque el byte de nodo los separa.
  • El tiempo que imprime al final (por paquete sellado y abierto, del orden de cientos de microsegundos o menos, según el chip y la aceleración por hardware) es despreciable frente a los 30 ms que un paquete de 22 bytes tarda en recorrer el cable a 9600 baudios dentro de una trama del tema 47. El costo de la seguridad por mensaje es el de los 13 bytes más (5 de encabezado y 8 de etiqueta): 13 × 10 / 9600 = 13,5 ms más de cable. En un enlace lento, eso, y no la CPU, es el precio.

10 · Analogía con programación

Paquetes seguros en Python

La biblioteca estándar de Python trae hashlib y hmac, con lo que se puede construir el mismo esquema y atacarlo. El modelo siguiente sella paquetes con el formato del módulo del ESP32, con un único cambio: Python no incluye AES, así que el flujo de claves se obtiene con HMAC como generador de números pseudoaleatorios (una construcción válida para ilustrar el mecanismo, no la que se pondría en un producto; en el dispositivo se usa AES-CTR). Después prueba lo que prueba el setup() del ESP32 y reproduce el desastre del nonce repetido.

Python en tu navegador. Los bloques son editables: cambiá los valores y presioná «Ejecutar Python». La primera vez tarda unos segundos en cargar.

import hashlib
import hmac

MAESTRA = bytes.fromhex("000102030405060708090a0b0c0d0e0f")     # solo para el ejemplo: una clave real no va en el código
ETIQUETA = 8                                                    # bytes de la etiqueta (64 bits)


def derivar(maestra, nombre):
    """Una clave distinta para cada uso, a partir de la maestra."""
    return hmac.new(maestra, nombre, hashlib.sha256).digest()[:16]


K_MAC, K_ENC = derivar(MAESTRA, b"mac"), derivar(MAESTRA, b"enc")


def flujo(nonce, n):
    """Flujo de claves de un solo uso por nonce. Didáctico: HMAC como generador;
    en el dispositivo se usa AES en modo contador (o ChaCha20)."""
    salida, i = b"", 0
    while len(salida) < n:
        salida += hmac.new(K_ENC, nonce + i.to_bytes(4, "big"), hashlib.sha256).digest()
        i += 1
    return salida[:n]


def cifrar(nonce, datos):                                       # cifrar y descifrar son la misma operación
    return bytes(a ^ b for a, b in zip(datos, flujo(nonce, len(datos))))


def sellar(nodo, contador, datos):
    """[nodo][contador de 4 bytes][datos cifrados][etiqueta]: se cifra y después se autentica todo."""
    cabecera = bytes([nodo]) + contador.to_bytes(4, "big")
    cifrado = cifrar(cabecera, datos)
    return cabecera + cifrado + hmac.new(K_MAC, cabecera + cifrado, hashlib.sha256).digest()[:ETIQUETA]


class Receptor:
    def __init__(self, ventana=32):
        self.ventana, self.maximo, self.vistos = ventana, None, set()

    def abrir(self, paquete):
        if len(paquete) < 5 + ETIQUETA:
            return None, "demasiado corto"
        cab, cifrado, etiqueta = paquete[:5], paquete[5:-ETIQUETA], paquete[-ETIQUETA:]
        esperada = hmac.new(K_MAC, cab + cifrado, hashlib.sha256).digest()[:ETIQUETA]
        if not hmac.compare_digest(etiqueta, esperada):         # primero la etiqueta, en tiempo constante
            return None, "etiqueta incorrecta"
        c = int.from_bytes(cab[1:], "big")
        if self.maximo is not None:                             # recién ahora se mira la ventana
            if c <= self.maximo - self.ventana:
                return None, "demasiado viejo"
            if c in self.vistos:
                return None, "repetido"
        self.maximo = c if self.maximo is None else max(self.maximo, c)
        self.vistos = {x for x in self.vistos | {c} if x > self.maximo - self.ventana}
        return cifrar(cab, cifrado), "ok"


datos = bytes([0, 1, 7, 0x2E, 0x09, 0x7C, 0x15, 0xE4, 0x0C])     # el registro del tema 46
rx = Receptor()
p = sellar(1, 100, datos)
print("paquete:", p.hex(" ").upper(), f"({len(p)} bytes: {len(datos)} de datos y {len(p) - len(datos)} de seguridad)")
print("los datos viajan en claro?", datos in p)
d, motivo = rx.abrir(p)
print("1) original:", motivo, "->", d.hex(" ").upper())
print("2) la misma grabación otra vez:", rx.abrir(p)[1])
alterado = bytearray(p)
alterado[7] ^= 0x01
print("3) un bit alterado:", rx.abrir(bytes(alterado))[1])
print("4) llega el 102 y después el 101:", rx.abrir(sellar(1, 102, datos))[1], "y", rx.abrir(sellar(1, 101, datos))[1])
print("5) el emisor se reinició y manda el 5:", rx.abrir(sellar(1, 5, datos))[1])
print("6) 8 bytes de etiqueta inventados:", rx.abrir(p[:-ETIQUETA] + bytes(ETIQUETA))[1])

a, b = b"ABRIR 01", b"CERRAR2!"
ca, cb = sellar(1, 200, a)[5:13], sellar(1, 200, b)[5:13]        # el mismo contador dos veces
print("mismo contador, XOR de los cifrados == XOR de los textos:",
      bytes(x ^ y for x, y in zip(ca, cb)) == bytes(x ^ y for x, y in zip(a, b)))

Resultado: el paquete sellado del registro de 9 bytes ocupa 22 bytes (los 9 de datos y 13 de seguridad): 01 00 00 00 64 61 29 F3 4F DE A3 51 23 65 DC C9 B8 2C F9 54 3A 79, donde 01 es el nodo, 00 00 00 64 el contador (100), los 9 bytes siguientes son los datos cifrados y los últimos 8, la etiqueta. Los datos no aparecen en claro. Las pruebas: (1) el original se acepta y devuelve 00 01 07 2E 09 7C 15 E4 0C; (2) la misma grabación otra vez, repetido; (3) con un bit alterado, etiqueta incorrecta; (4) llegan el 102 y después el 101: ok y ok (el desorden es legítimo); (5) un emisor que se reinició y manda el 5: demasiado viejo; (6) con una etiqueta inventada de ocho ceros, etiqueta incorrecta. Y con el mismo contador en dos mensajes distintos, el XOR de los cifrados es igual al XOR de los textos: True.

Tres lecciones del modelo. Primera: cada ataque lo frena una pieza distinta: el HMAC al alterado y al inventado, la ventana a la repetición y al viejo, y la ventana flexible deja pasar el desorden legítimo. Quitar una pieza abre un ataque. Segunda: el orden importa: el 102 y el 101 solo se aceptan bien porque la ventana se consulta después de la etiqueta y recuerda lo visto. Tercera: el último resultado es el que no tiene defensa en el protocolo: un nonce repetido por un error del emisor (un contador que se reinicia) anula el secreto sin que el receptor note nada; la defensa está en cómo el emisor guarda su contador.

11 · Exploración interactiva

Laboratorio: claves, HMAC, cifrado, repeticiones y TLS

El laboratorio tiene cinco herramientas. La primera calcula cuánto cuesta adivinar un secreto por fuerza bruta. La segunda calcula un HMAC-SHA256 (con un SHA-256 completo escrito en la página), lo trunca, altera un bit del mensaje y lo compara con un CRC. La tercera cifra con AES-128 en modo contador (también implementado completo) y muestra qué pasa cuando se repite el nonce. La cuarta aplica tres políticas contra mensajes repetidos a una secuencia de contadores. La quinta estima el costo de un apretón de manos TLS. El SHA-256, el HMAC y el AES de la página se comprobaron contra las implementaciones de referencia y contra los vectores de las normas (entre ellos, el de la norma FIPS-197 para AES y el caso 1 de la RFC 4231 para HMAC).

1. Fuerza bruta: cuánto cuesta adivinar un secreto

Combinaciones—
Entropía—
Recorrer todas—
En promedio—
Con bloqueo—
Veredicto—

Elegí un tipo de secreto.

2. HMAC-SHA256 frente al CRC

HMAC-SHA256 completo—
Etiqueta truncada—
Etiqueta del mensaje alterado—
Efecto avalancha—
CRC-16—
Falsificar la etiqueta—
Veredicto—

Escribí una clave y un mensaje.

3. AES-128 en modo contador y el nonce repetido

Cifrado 1—
Cifrado 2—
Cifrado 1 ⊕ cifrado 2—
Texto 1 ⊕ texto 2—
Lo que obtiene un atacante—
Veredicto—

Escribí la clave y los textos.

4. Mensajes repetidos: tres políticas

Decisión por mensaje—
Aceptados—
Rechazados—
Veredicto—

Escribí una lista de contadores.

5. El costo de un apretón de manos TLS

Idas y vueltas—
Bytes del apretón—
Hasta el primer dato—
Por mensaje—
Repartido entre los mensajes—
Veredicto—

Escribí los parámetros.

En la herramienta 1, el secreto se supone elegido al azar; las contraseñas de una persona tienen menos entropía que su largo. En la 2, la etiqueta es el comienzo del HMAC-SHA256 con una clave de texto (UTF-8); el CRC es el CRC-16/Modbus del tema 44. En la 3, el bloque de contador son 8 bytes de nonce (en orden de red) seguidos de 8 bytes con el número de bloque; el módulo del ESP32 usa otra disposición (nodo y contador de 5 bytes seguidos de ceros), pero el principio es el mismo. En la 4, la ventana se aplica como en el código del taller (acepta un número mayor que el máximo, y uno de los últimos N no visto). En la 5, los tamaños y tiempos de CPU son típicos de un ESP32 y valores de ejemplo: dependen del chip, de la biblioteca y del tipo de certificado.

Recorrido guiado

  1. En la herramienta 1, con un PIN de 4 dígitos a 10 intentos por segundo: 10 000 combinaciones, 13,3 bits, 16,7 min para recorrerlas y 8,3 min en promedio. Con un bloqueo tras 5 intentos, la probabilidad de acertar baja a 0,05 %. Con un PIN de 6 dígitos y bloqueo en 10: 19,9 bits y 0,001 %. Con 8 letras minúsculas a 1010 intentos por segundo (una tarjeta gráfica contra un archivo robado): 2,09 × 10¹¹ combinaciones, 37,6 bits y 10,4 s en promedio. Con 12 caracteres imprimibles: 78,7 bits y unos 754 000 años en promedio a ese ritmo. Con una clave binaria de 128 bits a 10¹² por segundo: 3,40 × 10³⁸ combinaciones y 1,08 × 10¹⁹ años.
  2. En la herramienta 2, con la clave «clave-secreta» y el registro del tema 47 (00 01 07 2E 09 7C 15 E4 0C): el HMAC completo es 17 11 68 F0 0A A5 B3 2F 48 ED … y la etiqueta de 8 bytes, 17 11 68 F0 0A A5 B3 2F. Alterando el bit 0 del byte 5, la etiqueta pasa a FC 60 E6 32 21 99 65 7D y cambian 123 de los 256 bits (efecto avalancha). El CRC pasa de 0xE224 a 0x2219, y cualquiera puede recalcularlo. Falsificar una etiqueta de 8 bytes tiene una probabilidad de 1 en 1,84 × 10¹⁹; con 1 byte es del 0,39 % por intento: se adivina en unos centenares de intentos.
  3. En la herramienta 3, con los mismos nonces (7 y 7): los dos cifrados son 54 4E 91 2E … y 56 49 91 35 …, y su XOR (02 07 00 1B …) es idéntico al XOR de los textos. El atacante que conoce «ABRIR VALVULA 01» obtiene «CERRAR VALVULA 2» sin clave. Con el segundo nonce en 8: el segundo cifrado cambia por completo (F4 02 37 08 …) y el XOR de los cifrados (A0 4C A6 26 …) ya no se parece al de los textos: no se filtra nada.
  4. En la herramienta 4, con la lista 1 2 3 2 5 4 4 40 41 9 42 y ventana 8: 8 de 11 aceptados; se rechazan el 2 repetido, el 4 repetido y el 9 (demasiado viejo), y se acepta el primer 4, que llegó desordenado. Con el contador estricto: solo 7 de 11, porque también descarta el primer 4, un mensaje legítimo que llegó tarde. Sin protección: 11 de 11, con 2 repetidos aceptados. Con la lista 40 41 42 1 2 (un emisor que se reinició): 3 de 5, el 1 y el 2 se rechazan por viejos, y seguirán rechazándose hasta que el contador supere el 42. Con 10 11 12 20 15 12 13 11 y ventana 8: 6 de 8 (el 12 y el 11 ya quedaron atrás de la ventana).
  5. En la herramienta 5, con 100 ms, 128 kbit/s, TLS 1.2 nuevo, 2500 bytes de certificados, 600 ms de CPU y 1 mensaje: 3 idas y vueltas, 3800 bytes y 1138 ms hasta el primer dato (300 de red, 238 de transferencia y 600 de CPU); cada mensaje paga todo el apretón. Con 20 mensajes: 219 bytes y 57 ms de seguridad por mensaje. Con TLS 1.3 reanudado y 100 mensajes: 2 idas y vueltas, 600 bytes, 418 ms, y 28 bytes y 4,2 ms por mensaje. Con 50 ms, 250 kbit/s, TLS 1.3 nuevo, 2000 bytes de certificados y 500 ms de CPU: 693 ms y, con 70 mensajes, 9,9 ms por mensaje.
Desafío: la cerradura Bluetooth que se abre con una grabación

Una cerradura abre al recibir por BLE el mensaje ABRIR + PIN de 4 dígitos, enviado por el teléfono. Un atacante cerca graba una vez la comunicación. (1) Qué obtiene: el PIN en claro (si no hay cifrado) o, aunque haya cifrado, el mismo mensaje cifrado, que reenviado abre la puerta: un ataque de repetición, sin necesidad de entender nada. (2) El PIN: con 10 000 combinaciones y sin límite de intentos, se prueba entero en 17 minutos a 10 intentos por segundo; el PIN solo resiste con un bloqueo (con 5 intentos, 0,05 % de acierto; y el bloqueo debe estar en la cerradura, no en la aplicación). (3) El rediseño: una clave de 128 bits al azar, distinta para cada teléfono autorizado, grabada en el emparejamiento y guardada en el almacén seguro del teléfono; la cerradura la guarda en memoria protegida. (4) Frescura: desafío y respuesta. Al conectarse, la cerradura envía 16 bytes al azar (N); el teléfono responde con la orden y el HMAC de la clave sobre N y la orden (sección 5). Una grabación no sirve, porque el siguiente desafío será otro. No hace falta reloj ni contador en el teléfono. (5) Cifrado: el canal BLE debe usar las conexiones seguras LE con comparación numérica o clave de acceso, no «Just Works»; el cifrado de la capa de enlace oculta el contenido, y la autenticación a nivel de aplicación protege aunque haya fallas en el emparejamiento. (6) Teléfono perdido: la cerradura mantiene una lista de claves autorizadas y permite revocar una desde una aplicación de administración. (7) Registro y límites: la cerradura cuenta los intentos fallidos y bloquea tras unos cuantos; los registra para que el dueño se entere. Es el contenido del tema 49 en un solo caso: modelo de amenazas, autenticación, frescura, claves por dispositivo, revocación y límite de intentos.

12 · Poner a prueba lo propio

Ataques típicos y lista de verificación

Una buena forma de aprender es atacar lo que se construyó. El protocolo del taller del tema 47 es un buen blanco: tiene CRC, confirmaciones y secuencia, y sin embargo cada uno de sus mecanismos tiene un hueco frente a un adversario:

Qué le falta al protocolo del tema 47 frente a un atacante.
AtaquePor qué funcionaDefensa
Lectura falsaEl atacante arma una trama con valores plausibles y calcula el CRC (no tiene secreto). La receptora la aceptaEtiqueta HMAC sobre la trama
Repetición de una lectura viejaLa receptora acepta toda secuencia distinta de la última. Una lectura grabada con la secuencia 17, reenviada cuando la última es la 40, se acepta (la secuencia de 8 bits además da la vuelta cada 256 mensajes)Contador de 32 bits dentro de lo autenticado, con ventana
Confirmaciones falsasEl ACK no está autenticado: un atacante que escuche la lectura puede responder con un ACK antes que la receptora (o con la receptora apagada) y el emisor cree que llegóEtiqueta en el ACK que cubra la secuencia y el contador
EscuchaLos datos van en claro (el sniffer del tema 48)Cifrado con nonce único (el módulo de la sección 9)
InundaciónEl atacante manda tramas con inicio y largo válidos para mantener ocupado al receptor, o interfiere el medioVerificación barata antes de la cara, límite de tasa; la interferencia solo se mitiga con el medio físico
Reinicio de contadoresUn corte de energía repite números: el receptor rechaza al emisor legítimo, o el nonce se repiteContador reservado por bloques en memoria no volátil

Lista de verificación para cualquier proyecto con comunicación (la mayoría de las respuestas son «sí» o hay una razón escrita para el «no»):

  • ¿Está escrito el modelo de amenazas (quién ataca, con qué acceso, qué se pierde)?
  • ¿Se usa un algoritmo estándar de una biblioteca reconocida, sin inventar nada?
  • ¿Cada mensaje de control lleva una etiqueta de autenticación, que cubre el encabezado y los contadores, y se verifica en tiempo constante antes de actuar?
  • ¿Cada mensaje es único (contador, desafío o marca de tiempo) y el receptor rechaza las repeticiones, sin actualizar su estado con mensajes no verificados?
  • ¿Los nonces no se pueden repetir ni tras un reinicio (contador en memoria no volátil con reserva)?
  • ¿Hay una clave distinta por dispositivo, generada con una fuente aleatoria buena y guardada fuera del código y protegida en el chip?
  • ¿Se puede rotar o revocar una clave a distancia? ¿Se borra al retirar el equipo?
  • ¿TLS verifica el certificado y el nombre, y el dispositivo tiene la hora correcta? ¿No queda ningún setInsecure()?
  • ¿Los puertos de depuración, los servicios de prueba y las contraseñas por defecto están desactivados en producción?
  • ¿El firmware está firmado, se verifica antes de instalarse, y se impide volver a una versión vieja vulnerable?
  • ¿Hay un límite de intentos para toda forma de adivinar (PIN, contraseña, clave de emparejamiento)?
  • ¿Se registran los rechazos (etiquetas incorrectas, repeticiones, bloqueos) para poder detectar un ataque? Los contadores del tema 47 sirven también para esto.

13 · Comprensión

Confusiones frecuentes

«Si cifro el mensaje, nadie puede modificarlo»

El cifrado oculta el contenido; no impide alterarlo. En muchos modos (el contador entre ellos) cambiar un bit del cifrado cambia el mismo bit del texto, y el atacante sabe cuál. Para impedir la modificación hace falta un código de autenticación (HMAC o un modo AEAD). Se autentica siempre; se cifra además cuando el contenido es confidencial.

«El CRC es una forma de autenticar, porque solo el emisor lo pone»

Cualquiera puede calcularlo: no tiene secreto. Un atacante modifica el mensaje y recalcula el CRC (la herramienta 2). El CRC detecta el azar; el HMAC, a una persona.

«Mi formato es propio y poco conocido, así que nadie lo va a entender»

Con un sniffer y un par de capturas, el formato de un protocolo sencillo se descubre en una tarde. La seguridad no puede apoyarse en que el atacante no conozca el método; se apoya en que no conozca la clave.

«Si el mensaje está cifrado, las repeticiones no sirven de nada»

Una repetición no necesita entender el mensaje: reenvía los mismos bytes y obtiene el mismo efecto. El cifrado y el HMAC se lo pasan por alto, porque son los mismos de siempre. La defensa es que cada mensaje valga una vez: contador, marca de tiempo o desafío.

«Puedo usar el mismo nonce si cambio el mensaje»

Es el error más grave del modo contador. Dos mensajes con el mismo nonce y la misma clave usan el mismo flujo, y el XOR de los cifrados es el XOR de los textos. Con un texto conocido se recupera el otro. El nonce no se repite jamás con la misma clave, y eso incluye los reinicios.

«Con TLS ya está todo resuelto»

TLS protege el tramo entre sus dos extremos, siempre que verifique el certificado y el nombre y que el reloj esté bien. No protege el dispositivo robado, ni la clave en el código, ni el servidor mal configurado, ni lo que ocurre después de que el mensaje llega. Y desactivar la verificación «para que funcione» lo deja cifrando hacia cualquiera.

«Mi red Wi-Fi tiene contraseña, así que mis datos están seguros»

WPA2 y WPA3 cifran el aire hasta el punto de acceso. Más allá, el tráfico va por la red local y por Internet, y quien comparte la contraseña de la red en WPA2-PSK puede descifrar el de los demás. La protección de extremo a extremo (TLS, o un cifrado a nivel de mensaje) es independiente.

«Una clave de 128 bits es débil frente a una de 256»

Con 128 bits al azar, probar todas las claves es inimaginable con cualquier tecnología previsible (la herramienta 1 da 10¹⁹ años a un billón por segundo). Los 256 bits son por margen a muy largo plazo. En la práctica, lo que se rompe no es el tamaño de la clave sino su manejo: la clave repetida en todos los equipos, la del código fuente, la derivada de algo predecible.

«Un PIN de 4 dígitos con bloqueo es tan seguro como una clave larga»

Mientras el bloqueo resista, limita al atacante a unas pocas pruebas, y 0,05 % de probabilidad con 5 intentos es poco. Pero depende de que el bloqueo esté en un lugar que el atacante no pueda saltar: si lo controla la aplicación del teléfono, o si el atacante graba la comunicación y la prueba fuera de línea, el PIN se rompe en minutos. Un secreto corto solo es seguro si alguien de confianza limita los intentos.

14 · Práctica guiada

Predecí antes de comprobar

1. El espacio de un PIN

Un PIN de 6 dígitos, probado a 10 intentos por segundo, ¿cuánto tarda en promedio en agotarse sin bloqueo? ¿Y con un bloqueo a los 10 intentos, qué probabilidad tiene un atacante de acertar?

Ver solución

106 combinaciones (19,9 bits). Sin bloqueo, el promedio es la mitad: 500 000 / 10 = 50 000 s = 13,9 horas. Con bloqueo a los 10 intentos: 10 / 1 000 000 = 0,001 %. Seis dígitos con un buen bloqueo es mucho más sólido que cuatro, pero sin bloqueo sigue siendo corto.

2. Etiquetas truncadas

Un diseño trunca el HMAC a 4 bytes. ¿Qué probabilidad tiene un atacante de acertar la etiqueta de un mensaje falso? Si puede intentar 10 mensajes por segundo, ¿cuánto tarda, en promedio, en tener éxito? ¿Qué cambiarías?

Ver solución

2−32, o 1 en 4 294 967 296 por intento. En promedio, la mitad de ese número de intentos: 2 147 483 648 / 10 ≈ 2,1 × 108 s ≈ 6,8 años. Si el receptor limita los intentos o el enlace es lento, es aceptable; si el atacante puede inundar a miles de mensajes por segundo (en una red rápida), baja a días. Con 8 bytes (2−64) el problema desaparece, a costa de 4 bytes más por mensaje, que a 9600 baudios son 4,2 ms.

3. El flujo de claves repetido

Dos comandos de 16 bytes se cifraron con la misma clave y el mismo nonce. El atacante sabe que el primero era «ABRIR VALVULA 01». ¿Cómo obtiene el segundo? ¿Necesita la clave?

Ver solución

No necesita la clave. Calcula C₁ ⊕ C₂ = P₁ ⊕ P₂ (el flujo se cancela) y luego P₂ = (C₁ ⊕ C₂) ⊕ P₁, byte a byte. Es lo que hace la herramienta 3 con los nonces iguales. La defensa es que el nonce no se repita jamás; si se repitió, hay que cambiar la clave.

4. Una ventana deslizante

Con una ventana de 8, el receptor ve los contadores 10 11 12 20 15 12 13 11. Decidí cuáles acepta.

Ver solución

10, 11, 12 y 20 se aceptan (cada uno es mayor que el máximo). 15: el máximo es 20, la diferencia es 5 < 8 y no se había visto: acepta (llegó desordenado). 12: la diferencia es 8, no menor que 8: rechaza (demasiado viejo, y además repetido). 13: diferencia 7, no visto: acepta. 11: diferencia 9: rechaza. Total: 6 de 8.

5. El orden de las comprobaciones

Un receptor actualiza su ventana (guarda el contador mayor) en cuanto ve un mensaje, y recién después verifica la etiqueta. ¿Qué ataque permite esto?

Ver solución

Un atacante sin la clave envía un solo mensaje falso con un contador enorme (por ejemplo 4 000 000 000). La etiqueta fallará, pero la ventana ya avanzó: todos los mensajes legítimos, con contadores menores, se rechazan como «viejos». Con un solo paquete, el atacante deja al dispositivo sordo. Por eso se verifica primero la etiqueta, y la ventana solo se toca con mensajes auténticos.

6. El costo de una conexión TLS

Un dispositivo se conecta con TLS 1.3 (sesión nueva) a un servidor a 50 ms de latencia, por un enlace de 250 kbit/s, con 2000 bytes de certificados y 500 ms de CPU. ¿Cuánto tarda en poder enviar? Si la conexión sirve para enviar 70 lecturas, ¿cuánto tiempo de seguridad paga cada una?

Ver solución

Idas y vueltas: TCP más una de TLS 1.3 = 2 × 50 = 100 ms. Bytes: 900 + 2000 = 2900; a 250 kbit/s: 2900 × 8 / 250 = 92,8 ms. CPU: 500 ms. Total: ≈ 693 ms. Repartido entre 70 lecturas: 693 / 70 ≈ 9,9 ms por lectura. Si en cambio se conectara para enviar una sola lectura cada vez, cada una pagaría los 693 ms (y la energía correspondiente): conviene mantener la conexión o reanudar la sesión.

7. Hasta dónde llega el Wi-Fi

Un sensor publica por MQTT en el puerto 1883 a un servidor en Internet, a través de una red Wi-Fi con WPA2. ¿Quién puede leer los datos y la contraseña de MQTT? ¿Qué cambiarías?

Ver solución

WPA2 cifra solo el tramo hasta el punto de acceso. Desde ahí, hasta el servidor, el tráfico MQTT viaja en claro: lo puede leer cualquiera en ese camino (el proveedor de Internet, un router intermedio) y, dentro de la red Wi-Fi, quien conozca la contraseña de la red. Se cambia al puerto 8883 con TLS verificando el certificado del servidor, y se da a cada sensor un usuario (o mejor, un certificado) propio con permiso solo sobre sus temas.

8. El contador y la memoria flash

Un nodo manda un mensaje por segundo y guarda su contador en la memoria flash en cada mensaje. La flash soporta unos 100 000 ciclos de escritura por celda. ¿Cuántas escrituras por año son, y qué se hace?

Ver solución

365 × 86 400 = 31 536 000 escrituras por año, muy por encima de los 100 000 ciclos (aunque el sistema reparta el desgaste entre páginas, no alcanza). La solución es reservar por bloques: se guarda un techo (contador + 1000), se cuenta en memoria y solo se vuelve a escribir al llegar al techo: 31 536 escrituras por año. Tras un reinicio se arranca desde el techo guardado; se pierden algunos números, nunca se repite uno.

15 · Síntesis

Ideas para recordar

  • El CRC, las confirmaciones y los reintentos defienden de la naturaleza; contra una persona hace falta criptografía. Se empieza por el modelo de amenazas: confidencialidad (cifrar), integridad y autenticidad (código de autenticación) y disponibilidad (casi sin remedio criptográfico). En el control, autenticar importa más que cifrar.
  • No se inventa criptografía: se usan algoritmos estándar de bibliotecas reconocidas (mbedTLS en el ESP32), y el secreto está en la clave, nunca en el formato. Cada capa protege su tramo (Wi-Fi hasta el punto de acceso, TLS de extremo a extremo, o el cifrado por mensaje).
  • El cifrado simétrico (AES) cifra los datos; los hash (SHA-256) dan huellas; la asimétrica acuerda claves y firma. AES en modo contador necesita un nonce que no se repita nunca con la misma clave: si se repite, el XOR de los cifrados es el XOR de los textos y se rompe sin atacar la clave. Un modo AEAD (GCM, ChaCha20-Poly1305) cifra y autentica a la vez.
  • Un HMAC es una etiqueta que depende del mensaje y de una clave secreta: cambiar un bit cambia la mitad de la etiqueta, y no se puede recalcular sin la clave, al contrario del CRC. Truncada a 8 bytes, un atacante acierta con probabilidad 2⁻⁶⁴. Se compara en tiempo constante y se verifica antes de usar el contenido; debe cubrir el encabezado y el contador.
  • Un mensaje auténtico grabado sigue siendo auténtico: las repeticiones se rechazan haciendo que cada mensaje valga una vez, con un contador (con ventana deslizante, que tolera el desorden), una marca de tiempo o un desafío con respuesta. La etiqueta se verifica antes de tocar la ventana, y el contador del emisor se guarda en memoria no volátil, reservándolo por bloques para no desgastar la flash.
  • La mayoría de las fallas reales son de gestión de claves: se generan al azar con un generador criptográfico, son distintas por dispositivo (derivadas de una maestra), se guardan fuera del código y protegidas por el chip, y se pueden rotar y revocar. Un PIN corto solo resiste con límite de intentos; una clave de 128 bits al azar no se fuerza.
  • TLS acuerda claves con criptografía asimétrica, verifica el certificado, el nombre y la fecha, y protege cada registro. Cuesta idas y vueltas (TLS 1.3 menos que 1.2), kilobytes de certificados y cientos de milisegundos de CPU, que se reparten entre los mensajes si se mantiene o reanuda la conexión. Sin verificar el certificado (setInsecure()) cifra hacia cualquiera.
  • Las tecnologías del curso traen poco: los buses y Modbus, nada; Wi-Fi, el tramo del aire; MQTT, TLS si se activa; BLE y LoRaWAN, cifrado y contadores que hay que configurar bien. Un protocolo propio como el del tema 47 necesita HMAC, contador de 32 bits con ventana, ACK autenticados y cifrado si el contenido lo pide.

¿Listo para el siguiente tema?

Antes de avanzar, explicá por qué un atacante puede modificar un mensaje protegido por CRC y no uno protegido por HMAC; calculá cuánto tarda en promedio en adivinarse una etiqueta de 4 bytes a 100 intentos por segundo; y decidí qué política contra las repeticiones usarías para un sensor de LoRa que manda un mensaje cada 10 minutos y por qué guardarías el contador en memoria no volátil. Si podés justificar las tres respuestas, entendés las bases de la seguridad en la comunicación.

En el próximo tema, elegir el protocolo adecuado, se junta todo lo del curso en una decisión de diseño: distancia, velocidad, cantidad de nodos, costo, consumo, ruido, y ahora también seguridad, con una tabla comparativa de las tecnologías vistas y los criterios para elegir entre ellas.