Criptografía práctica de IOT en el Espresivo ESP8266

El chipset ESPRESIF ESP8266 hace que los tableros de desarrollo de Internet de tres dólares sean una realidad económica. De acuerdo con el popular sitio de construcción de firmware automático, Nodemcu-Builds, en los últimos 60 días ha habido 13,341 compilaciones de firmware personalizadas para esa plataforma. De ellos, solo el 19% tiene soporte SSL, y el 10% incluye el módulo de criptografía.

A menudo estamos críticos con la falta de seguridad en el sector de IOT, y con frecuencia cubren botnets y otros ataques, ¡pero tendremos nuestros proyectos a los mismos estándares que exigimos. ¿Nos detendremos al identificar el problema, o podemos ser parte de la solución?

Este artículo se centrará en la aplicación de funciones de autorización de AES CIPTHT y ASHH al protocolo MQTT utilizando el popular FIRTHIPTU FIRTWARE FIRTWARE. Nuestro propósito no es proporcionar una panacea de copia / pasta, sino pasar por el proceso paso a paso, identificando desafíos y soluciones en el camino. El resultado es un sistema que es cifrado y autenticado de extremo a extremo, evitando a las escondidas a las escondidas a lo largo del camino, y la falsificación de datos válidos, sin confiar en SSL.

Somos conscientes de que también hay plataformas más poderosas que pueden soportar fácilmente SSL (por ejemplo, Raspberry Pi, Orange Pi, Felicitando), pero comencemos con el hardware más barato, la mayoría de nosotros hemos mentido, y un protocolo adecuado para muchos de nuestros proyectos. . AES es algo que podría implementar en un AVR si lo necesitabas.

Teoría

MQTT es un protocolo de mensajería ligero que se ejecuta en la parte superior de TCP / IP y se usa con frecuencia para proyectos IOT. Los dispositivos cliente se suscriben o publican a temas (por ejemplo, sensores / temperatura / cocina), y estos mensajes se transmiten por un agente MQTT. Más información sobre MQTT está disponible en su página web o en nuestra propia serie de inicio.

El protocolo MQTT no tiene características de seguridad incorporadas más allá de la autenticación de nombre de usuario / contraseña, por lo que es común encriptarse y autenticarse en una red con SSL. Sin embargo, SSL puede ser más bien exigente para el ESP8266 y cuando está habilitado, le queda con mucha menos memoria para su aplicación. Como alternativa liviana, puede cifrar solo la carga útil de datos que se está enviando, y usar una función de identificación de sesión y la función hash para la autenticación.

Una forma directa de hacerlo es usar LUA y el módulo Crypto ANDEMCU, que incluye soporte para el algoritmo AES en modo CBC, así como la función HMAC HASH. El uso de AES Ciftion requiere correctamente tres cosas para producir texto cifrado: un mensaje, una tecla y un vector de inicialización (IV). Los mensajes y las llaves son conceptos sencillos, pero el vector de inicialización vale alguna discusión.

Cuando codifica un mensaje en AES con una clave estática, siempre producirá la misma salida. Por ejemplo, el mensaje “UsernamePassword” encriptado con clave “1234567890ABCDEF” podría producir un resultado como “E40D86C04D723AFF”. Si vuelve a ejecutar el cifrado con la misma clave y el mensaje, obtendrá el mismo resultado. Esto lo abre a varios tipos comunes de ataque, especialmente el análisis de patrones y los ataques de reproducción.

En un ataque de análisis de patrones, utiliza el conocimiento de que una pieza de datos determinada siempre producirá el mismo texto cifrado para adivinar cuál es el propósito o el contenido de diferentes mensajes sin saber realmente la clave secreta. Por ejemplo, si el mensaje “E40D86C04D723AFF” se envía antes de todas las demás comunicaciones, uno podría adivinar rápidamente es un inicio de sesión. En resumen, si el sistema de inicio de sesión es simplista, el envío de ese paquete (un ataque de repetición) puede ser suficiente para identificarse como un usuario autorizado, y se produce el CHAOS.

IVS Haz que el análisis del patrón sea más difícil. Una IV es una pieza de datos enviados junto con la clave que modifica el resultado del texto del extremo. Como su nombre lo indica, inicializa el estado del algoritmo de cifrado antes de que entren los datos. El IV debe ser diferente para cada mensaje enviado para que los datos repetidos se cifren en diferentes textos cifrados, y algunos cifras (como AES-CBC) requieran que sea impredecible, una forma práctica de lograr esto es solo para aleatorizarlo cada vez. Las IVS no tienen que mantenerse en secreto, pero es típico que los ofuscan de alguna manera.

Si bien esto protege contra el análisis del patrón, no ayuda con los ataques de reproducción. Por ejemplo, la retransmisión de un conjunto dado de datos cifrados aún duplicará el resultado. Para evitar eso, necesitamos autenticar al remitente. Usaremos un ID de sesión público, pseudorandomlicamente generado para cada mensaje. Este ID de sesión puede ser generado por el dispositivo receptor publicando a un tema MQTT.

Prevención de estos tipos de ataques es importante en un par de casos de uso común. Existen estufas controladas por Internet, y una utilidad cuestionable a un lado, sería bueno si no usaran comandos inseguros. En segundo lugar, si estoy data de cien sensores, no quiero que nadie esté llenando mi base de datos con basura.

Cifrado práctico

Implementar lo anterior en el ANDEMCU requiere algún esfuerzo. Necesitará un firmware compilado para incluir el módulo ‘Crypto’ además de cualquier otro que haya enviadoire para su solicitud. No se requiere soporte SSL.

Primero, asumamos que está conectado a un agente MQTT con algo como lo siguiente. Puede implementar esto como una función separada de la criptografía para mantener las cosas limpias. El cliente se suscribe a un canal de sesiones, que publica ID de sesión adecuadamente largos y de pseudorandom. Podrías cifrarlos, pero no es necesario.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
m = mqtt.client (& quot; Clientid & quot;, 120)

M: Conectar (& quot; MyServer.com & quot;, 1883, 0,
función (cliente)
Imprimir (& quot; conectado & quot;)
Cliente: Suscribirse (& quot; MyTopic / SessionId & quot;, 0,
Función (cliente) Imprimir (& quot; Suscríbase el éxito & quot;) Fin

final,
Función (cliente, razón)
Imprimir (& quot; motivo fallado: & quot; Motivo)
final

M: ON (& quot; Message & quot; Función (Cliente, Tema, SessionId) Fin)

Moviéndose, la identificación del nodo es una forma conveniente de ayudar a identificar las fuentes de datos. Sin embargo, puede usar cualquier cadena que desee: Nodeid = node.chipid ().

Luego, configuramos un vector de inicialización estática y una clave. Esto solo se usa para ofuscar el vector de inicialización aleatorizado enviado con cada mensaje, no se usa para ningún dato. También elegimos una clave separada para los datos. Estas claves son hexagonales de 16 bits, solo reemplácelas con el tuyo.

Finalmente, necesitaremos una frase de contraseña para una función hash que usaremos más tarde. Una cadena de longitud razonable está bien.

1
2
3
4
staticiv = & quot; abcdef2345678901 & quot;
ivay = & quot; 2345678901ABCDEF & quot;
Datasky = & quot; 0123456789ABCDEF & quot;
Frase de contraseña = & quot; MyPassphrase & quot;

También asumiremos que tiene alguna fuente de datos. Para este ejemplo, será un valor leído del ADC. DATOS = ADC.Read (0)

Ahora, generamos un vector de inicialización de pseudorandom. Un número hexagonal de 16 dígitos es demasiado grande para la función de número de pseudorandoma, por lo que lo generamos en dos mitades (16 ^ 8 menos 1) y concatenarlos.

1
2
3
4
5
mitad1 = node.random (4294967295)
mitad2 = node.random (4294967295)
I = string.format (& quot;% 8x & quot;, mitad1)
V = string.format (& quot;% 8x & quot;, mitad2)
iv = i .. v

Ahora podemos ejecutar el cifrado real. Aquí estamos cifrando el vector de inicialización actual, la ID del nodo y una pieza de datos del sensor.

1
2
3
encrypted_iv = crypto.encrypt (& quot; AES-CBC & quot;, IVEY, IV, Staticiv)
encrypted_nodeid = crypto.encrypt (& quot; AES-CBC & quot;, Datakey, NodeID, IV)
encrypted_data = crypto.encrypt (& quot; AES-CBC & quot;, Datastaky, Data, IV)

Ahora aplicamos la función hash para la autenticación. Primero, combinamos el ID NODEID, IV, DATOS y SESIONES en un solo mensaje, luego calculamos un hash HMAC SHA1 usando la frase de contraseña que definimos anteriormente. Lo convierte a Hex para que sea un poco más legible para la depuración.

1
2
fullmessage = nodeid .. iv .. datos.
hmac = crypto.tohex (crypto.hmac (& quot; sha1 & quot;, fullmessage, passphrase))

Ahora que ambos controles de cifrado y autenticación están en su lugar, podemos colocar toda esta información en alguna estructura y enviarla. Aquí, usaremos valores separados por comas, ya que es conveniente:

1
2
Payload = Table.ConCat ({encrypted_iv, eid, data1, hmac}, & quot;, & quot;)
M: Publicar (& quot; yourmqttttopic & quot;, carga útil, 2, 1, función (cliente) p = & quot; enviado & quot; imprimir (p) final)

Cuando ejecutamos el código anterior en un Nodemcu real, obtendríamos algo así:

1D54DD1AF0F75A91A00D4DCD8F4AD28D,
d1a0b14d187c5adfc948dfd77c2b2ee5,
564633A4A053153BCBD6ED25370346D5,
C66697df7e7d467112757c841bfb6bce051d6289

Todos juntos, el programa de cifrado es el siguiente (secciones MQTT excluidas para mayor claridad):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
dieciséis
17
18
19
Nodeid = node.chipid ()
staticiv = & quot; abcdef2345678901 & quot;
ivay = & quot; 2345678901ABCDEF & quot;
Datasky = & quot; 0123456789ABCDEF & quot;
Frase de contraseña = & quot; MyPassphrase & quot;

DATOS = ADC.Read (0)
mitad1 = node.random (4294967295)
mitad2 = node.random (4294967295)
I = string.format (& quot;% 8x & quot;, mitad1)
V = string.format (& quot;% 8x & quot;, mitad2)
iv = i .. v

encrypted_iv = crypto.encrypt (& quot; AES-CBC & quot;, IVEY, IV, Staticiv)
encrypted_nodeid = crypto.encrypt (& quot; AES-CBC & quot;, Datakey, NodeID, IV)
encrypted_data = crypto.encrypt (& quot; AES-CBC & quot;, Datastaky, Data, IV)
fullmessage = nodeid .. iv .. datos.
hmac = crypto.tohex (crypto.hmac (& quot; sha1 & quot;, fullmessage, passphrase))
Payload = Table.ConCat ({encrypted_iv, encrypted_nodeid, encrypted_data, hmac}, & quot;, & quot;)

Descifrado

Ahora, su Broker MQTT no sabe o le importa que los datos estén encriptados, simplemente lo pasa. Por lo tanto, sus otros clientes MQTT se suscribieron al tema deberán saber cómo descifrar los datos. En Nodemcu esto es bastante fácil. Simplemente divida los datos recibidos en cadenas a través de las comas, y hagan algo como el siguiente. Nota Este final habrá generado el ID de sesión, así que ya lo sabe.

1
2
3
4
5
6
7
8
9
10
staticiv = & quot; abcdef2345678901 & quot;
ivay = & quot; 2345678901ABCDEF & quot;
Datasky = & quot; 0123456789ABCDEF & quot;
Frase de contraseña = & quot; MyPassphrase & quot;

iv = crypto.decrypt (& quot; aes-cbc & quot;, ithay, encrypted_iv, staticiv)
nodeid = crypto.decrypt (& quot; aes-cbc & quOT;, DIATOKEY, ENCRYPTED_NODEID, IV)
datos = crypto.decrypt (& quot; AES-CBC & quot;, DataKey, encrypted_Data, IV)
fullmessage = nodeid .. iv .. datos.
hmac = crypto.tohex (crypto.hmac (& quot; sha1 & quot;, fullmessage, passphrase))

Luego, compare el HMAC recibido y computado, y independientemente del resultado, invalida esa ID de sesión generando una nueva.

Una vez más, en Python.

Para una pequeña variedad, considere cómo manejaríamos la descifrado en Python, si tuviéramos un cliente MQTT en la misma máquina virtual que el intermediario que estaba analizando los datos o al almacenarlo en una base de datos. Asumamos que ha recibido los datos como una cadena “carga útil”, de algo como el excelente cliente de la OPS MQTT para Python.

En este caso, es conveniente para codificar los datos encriptados en el ANDEMCU antes de transmitir. Por lo tanto, en el ANDEMCU, convertimos todos los datos cifrados a HEX, por ejemplo: encrypted_iv = crypto.tohex (Crypto.Encrypt (“AES-CBC”, IVEY, IV, Staticiv))

La publicación de una sesión de sesiones aleatorias no se discute a continuación, pero es lo suficientemente fácil con el uso de OS.urandom () y el cliente de la OPS MQTT. El descifrado se maneja de la siguiente manera:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
dieciséis
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Desde Crypto.Cipher Import AES
Importar BINASSIII
Desde Crypto.Hash Import Sha, Hmac

# Definir todas las llaves
ivkey = ‘2345678901ABCDEF’
Datasky = ‘0123456789ABCDEF’
Staticiv = ‘abcdef2345678901’
Frase de contraseña = ‘MyPassphrase’

# convierte la cadena recibida a una lista
Data = Payload.Split (& quot;, & quot;)

# extracto de elementos de lista
encrypted_iv = binascii.unhexlify (datos [0])
encrypted_nodeid = binascii.unhexify (datos [1])
encrypted_data = binascii.unhexlify (datos [2])
recibido_hash = binascii.unhexify (datos [3])

# descifrar el vector de inicialización
iv_decryption_suite = AES.NEW (IVEY, AES.Mode_CBC, Staticiv)
iv = iv_decryption_suite.decrypt (encrypted_iv)

# descifrar los datos utilizando el vector de inicialización
id_decryption_suite = AES.New (Datasky, AES.Mode_CBC, IV)
Nodeid = id_decryption_suite.decrypt (encrypted_nodeid)
data_decryption_suite = aes.new (Datasky, AES.Mode_CBC, IV)
sensordata = data_decryption_suite.decrypt (encrypted_data)

# Compute la función hash para comparar con recibido_hash
fullmessage = s.join ([Nodeid, IV, Sensordata, SessionId])
hmac = hmac.new (Passphrase, Fullmessage, SHA)
computed_hash = hmac.hexdigest ()

# ver docs.python.org/2/library/hmac.html para cómo comparar los hashes de forma segura

El fin, el comienzo.

Ahora tenemos un sistema que envía mensajes autenticados cifrados a través de un servidor MQTT a otro cliente ESP8266 o un sistema más grande que ejecuta Python. Todavía hay importantes cabos sueltos para que usted vincule si lo implementa usted mismo. Todas las claves se almacenan en la memoria flash de ESP8266, por lo que querrá controlar el acceso a estos dispositivos para evitar la ingeniería inversa. Las claves también se almacenan en el código en la computadora que recibe los datos, aquí corriendo Python. Además, es probable que desee que cada cliente tenga una llave diferente y la frase de contraseña. Eso es mucho material secreto para mantenerse seguro y potencialmente actualizado cuando sea necesario. Resolver el problema de la distribución clave queda como un ejercicio para el lector motivado.

Y en una nota de cierre, una de las terribles cosas sobre escribir un artículo que involucre la criptografía es la posibilidad de estar equivocado en Internet. Esta es una aplicación bastante sencilla del modo AES-CBC probado y verdadero con HMAC, por lo que debe ser bastante sólido. No obstante, si encuentra alguna de las deficiencias interesantes en lo anterior, háganoslo saber en los comentarios.

Send your Comment

Your email address will not be published. Required fields are marked *