Der ESPressif ESP8266-Chipsatz macht drei-Dollar-“Internet der Entwicklung der Dinge” eine wirtschaftliche Realität. Nach Angaben der beliebten automatischen Firmware-Baustelle NoDemcu-Builds in den letzten 60 Tagen gab es 13.341 benutzerdefinierte Firmware-Builds für diese Plattform. Von diesen haben nur 19% SSL-Unterstützung, und 10% enthalten das Kryptographiemodul.
Wir sind oft kritisch für die mangelnde Sicherheit in der IOT-Sektor und decken häufig Botneten und andere Angriffe ab, aber wir halten unsere Projekte auf die gleichen Standards, die wir verlangen? Hören wir auf, das Problem zu identifizieren, oder können wir Teil der Lösung sein?
Dieser Artikel konzentriert sich auf das Anwenden von AES-Verschlüsselungs- und Hash-Berechtigungsfunktionen in das MQTT-Protokoll mit dem beliebten ESP8266-Chip-Firmware-Firmware. Unser Ziel ist es, kein Kopieren / Einfügen-Panacea bereitzustellen, sondern den Prozess Schritt für Schritt durchzuführen, um Herausforderungen und Lösungen auf dem Weg zu erkennen. Das Ergebnis ist ein System, das End-to-enden verschlüsselt und authentifiziert, verhindert, dass ein Abhören auf dem Weg hergestellt wird, und das Spoofing von gültigen Daten, ohne sich auf SSL zu verlassen.
Wir sind uns dessen bewusst, dass es auch leistungsfähigere Plattformen gibt, die SSL (z. B. Raspberry Pi, Orange PI, FILTIGRYARM) problemlos unterstützen können, aber beginnen wir mit der günstigsten Hardware, die der größte Teil von uns liegend hat, und ein Protokoll, das sich für viele unserer Projekte eignet . AES ist etwas, das Sie in einem AVR implementieren könnten, wenn Sie benötigt werden.
Theorie
MQTT ist ein leichte Messaging-Protokoll, das auf TCP / IP ausgeführt wird und häufig für IOT-Projekte verwendet wird. Client-Geräte abonnieren oder veröffentlichen auf Themen (z. B. Sensoren / Temperatur / Küche), und diese Nachrichten werden von einem MQTT-Broker weitergeleitet. Weitere Informationen zu MQTT finden Sie auf ihrer Webseite oder in unserer eigenen Ersteinführungsserie.
Das MQTT-Protokoll verfügt über keine integrierten Sicherheitsfunktionen außerhalb der Benutzername / Kennwortauthentifizierung, daher ist es üblich, über ein Netzwerk mit SSL über ein Netzwerk zu verschlüsseln und sich in einem Netzwerk zu authentifizieren. SSL kann jedoch eher anspruchsvoll für den ESP8266 sein, und wenn Sie aktiviert sind, bleiben Sie mit viel weniger Speicher für Ihre Anwendung. Als leichte Alternative können Sie nur die gesendete Daten-Nutzlast verschlüsseln und eine Sitzungs-ID und Hash-Funktion für die Authentifizierung verwenden.
Eine einfache Möglichkeit, dies zu tun, ist das Verwenden von Lua und dem NoDemcu Crypto-Modul, das den AES-Algorithmus im CBC-Modus sowie die HMAC-Hash-Funktion unterstützt. Die Verwendung der AES-Verschlüsselung erfordert korrekt drei Dinge, um Chiptext herzustellen: eine Nachricht, einen Schlüssel und einen Initialisierungsvektor (IV). Nachrichten und Schlüssel sind unkomplizierte Konzepte, aber der Initialisierungsvektor ist diskutiert.
Wenn Sie eine Nachricht in AES mit einem statischen Schlüssel codieren, wird immer dieselbe Ausgabe erzeugt. Beispielsweise könnte die mit der Taste “1234567890ABCDEF” verschlüsselte Meldung “Usernamepassword” ein Ergebnis wie “E40D86C04D723AFF” erzeugen. Wenn Sie die Verschlüsselung erneut mit demselben Schlüssel und derselben Nachricht ausführen, erhalten Sie dasselbe Ergebnis. Dies öffnet Sie zu mehreren gängigen Angriffsarten, insbesondere Musteranalysen und Wiedergabeangriffe.
Bei einem Musteranalyseangriff verwenden Sie das Wissen, dass ein bestimmtes Datenteil immer denselben Chiptext erzeugt, um zu erraten, was der Zweck oder Inhalt verschiedener Nachrichten eignen, ohne den geheimen Schlüssel kennenzulernen. Wenn beispielsweise die Meldung “E40D86C04D723AFF” vor allen anderen Kommunikationen gesendet wird, kann man schnell erraten, dass es sich um ein Login handelt. Wenn das Login-System einfach ist, kann das Senden dieses Pakets (ein Wiedergabeangriff) ausreichen, um sich als autorisierter Benutzer zu identifizieren, und Chaos folgt.
IVs machen Musteranalysen schwieriger. Ein IV ist ein Datenteil, das zusammen mit dem Schlüssel gesendet wurde, der das End-Chiffrtext-Ergebnis modifiziert. Wie der Name schon sagt, initialisiert er den Status des Verschlüsselungsalgorithmus, bevor die Daten eintreten. Die IV muss anders sein, damit jede gesendetes Nachricht so gesendet wird, dass wiederholte Daten in einem anderen Chiffretext verschlüsselt werden, und einige CIPHERS (wie AES-CBC) müssen unvorhersehbar sein – ein praktischer Weg, um dies zu erreichen, ist nur das Randomerisieren dieses Zeitpunkts. IVs müssen nicht geheim gehalten werden, aber es ist typisch, sie in irgendeiner Weise zu verschleiern.
Während dies vor der Musteranalyse schützt, hilft es nicht bei der Wiedergabeangriffe. Zum Beispiel wird das Neuübertragung eines bestimmten Satzes verschlüsselter Daten das Ergebnis immer noch doppelt. Um dies zu verhindern, müssen wir den Absender authentifizieren. Wir werden eine öffentliche, pseudorandomly generierte Sitzungs-ID für jede Nachricht verwenden. Diese Sitzungs-ID kann vom empfangenden Gerät erzeugt werden, indem er an ein MQTT-Thema veröffentlicht.
Die Verhinderung dieser Arten von Angriffen ist in einigen gemeinsamen Anwendungsfällen wichtig. Internet-kontrollierte Öfen existieren und fragwürdige Nutzen beiseite, es wäre schön, wenn sie keine unsicheren Befehle verwenden. Zweitens, wenn ich von hundert Sensoren datalloggen möchte, möchte ich nicht, dass jemand meine Datenbank mit Müll füllt.
Praktische Verschlüsselung
Das Implementieren des oben genannten Angaben auf dem NODEMCU erfordert einen gewissen Aufwand. Sie benötigen Firmware, um das Modul ‘Crypto’ zusätzlich zu anderen Angaben zu enthaltenIRE für Ihre Anwendung. SSL-Unterstützung ist nicht erforderlich.
Sehen Sie sich zunächst an, dass Sie mit einem MQTT-Broker mit so etwas wie folgt verbunden sind. Sie können dies als separate Funktion aus der Kryptografie umsetzen, um die Dinge sauber zu halten. Der Client abonniert einen SessionID-Kanal, der angemessen langen, Pseudorandom-Sitzungs-IDs veröffentlicht. Sie können sie verschlüsseln, aber es ist nicht notwendig.
1.
2.
3
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
fünfzehn
m = mqtt.Client (& quot; clientid & quot; 120)
M: Connect (& quot; myserver.com & quot; 1883, 0,
Funktion (Client)
drucken (& quot; angeschlossen “)
Client: Abonnieren (& quot; mytopic / sessioneid & quot;, 0,
Funktion (Client) -Druck (& quot; Subscribe-Erfolg & quot;) Ende
)
Ende,
Funktion (Kunde, Grund)
drucken (& quot; fehlgeschlagener Grund: & quot; .. Grund)
Ende
)
m: on (& quot; Nachricht & quot; Funktion (Client, Thema, SessionID) Ende)
Wenn Sie weitergehen, ist die Knoten-ID bequem, um Datenquellen zu erkennen. Sie können beliebige Zeichenfolge verwenden, die Sie wünschen: nodeID = node.chipid ().
Dann richten wir einen statischen Initialisierungsvektor und einen Schlüssel ein. Dies wird nur verwendet, um den mit jeder Nachricht gesendeten randomisierten Initialisierungsvektor zu verschleiern, der nicht für Daten verwendet wird. Wir wählen auch einen separaten Schlüssel für die Daten. Diese Tasten sind 16-Bit-SEX, ersetzen Sie sie einfach durch Ihre.
Endlich brauchen wir eine Passphrase für eine Hash-Funktion, die wir später verwenden werden. Eine Reihe angemessener Länge ist in Ordnung.
1.
2.
3
4.
staticiv = & quot; abcdef2345678901 & quot;
ivonkey = & quot; 2345678901abcdef & quot;
Datakey = & quot; 0123456789ABCDEF & quot;
Passphrase = & quot; MyPasshrase & quot;
Wir gehen auch davon aus, dass Sie eine Reihe von Daten haben. Für dieses Beispiel ist es ein Wert, der aus dem ADC gelesen wird. Daten = ADC.Read (0)
Nun erzeugen wir einen Pseudorandom-Initialisierungsvektor. Eine 16-stellige Hex-Nummer ist für die Funktion der Pseudorandom-Nummer zu groß, sodass wir sie in zwei Hälften (16 ^ 8 minus 1) erstellen und sie verketten.
1.
2.
3
4.
5.
halb1 = node.random (4294967295)
halb2 = node.random (4294967295)
I = string.Format (& quot;% 8x & quot ;, halb1)
V = string.format (& quot;% 8x & quot ;, halb2)
iv = ich .. v
Wir können jetzt die tatsächliche Verschlüsselung ausführen. Hier verschlüsseln wir den aktuellen Initialisierungsvektor, der Knoten-ID und ein Stück Sensordaten.
1.
2.
3
verschlüsselt_iv = crypto.encrypt (& quot; AES-CBC & quot; ivonkey, iv, staticiv)
verschlüsselt_nodeID = crypto.encrypt (& quot; AES-CBC & quot;, Datakey, NodeID, IV)
verschlüsselt_data = crypto.encrypt (& quot; aES-cbc & quot; datakey, data, iv)
Jetzt wenden wir die Hash-Funktion zur Authentifizierung an. Zuerst kombinieren wir die NODID, IV, die Daten und die Sitzungs-ID in eine einzige Nachricht, um dann einen HMAC SHA1-Hash mit der zuvor definierten Passphrase zu berechnen. Wir konvertieren es in SEX, um es für jedes Debugging etwas menschlicheres zu machen.
1.
2.
FullMessage = NodeID .. IV .. Daten .. SessionID
hmac = crypto.toehex (crypto.hmac (sha1 & quot;, Fullmessage, Passphrase))
Da sowohl Verschlüsselungs- als auch Authentifizierungsprüfungen vorhanden sind, können wir alle diese Informationen in einheitlicher Struktur eingeben und senden. Hier verwenden wir Kommas getrennte Werte, da es praktisch ist:
1.
2.
payload = table.concat ({verschlüsselt_iv, EID, Data1, HMAC}, & quot;, & quot;)
M: Publish (& quot; yourmqtttopic & quot;, Nutzlast, 2, 1, Funktion (Client) P = “Send & quot; drucken (p) enden)
Wenn wir den obigen Code auf einem eigentlichen NODEMCU ausführen, würden wir so etwas wie folgt ausgibt:
1D54DD1AF0F75A91A00D4DCD8F4AD28D,
d1a0b14d187c5adfc948dfd77c2b2b2b2EE5,
564633A4A053153BCBD6Ed25370346D5,
C66697DF7E7D467112757C841BFB6BCE051D6289.
Alles zusammen ist das Verschlüsselungsprogramm wie folgt (MQTT-Abschnitte für Klarheit):
1.
2.
3
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
fünfzehn
16.
17.
18.
19.
nodeID = node.chipid ()
staticiv = & quot; abcdef2345678901 & quot;
ivonkey = & quot; 2345678901abcdef & quot;
Datakey = & quot; 0123456789ABCDEF & quot;
Passphrase = & quot; MyPasshrase & quot;
Daten = ADC.Read (0)
halb1 = node.random (4294967295)
halb2 = node.random (4294967295)
I = string.Format (& quot;% 8x & quot ;, halb1)
V = string.format (& quot;% 8x & quot ;, halb2)
iv = ich .. v
verschlüsselt_iv = crypto.encrypt (& quot; AES-CBC & quot; ivonkey, iv, staticiv)
verschlüsselt_nodeID = crypto.encrypt (& quot; AES-CBC & quot;, Datakey, NodeID, IV)
verschlüsselt_data = crypto.encrypt (& quot; aES-cbc & quot; datakey, data, iv)
FullMessage = NodeID .. IV .. Daten .. SessionID
hmac = crypto.toehex (crypto.hmac (sha1 & quot;, FullMessage, Passphrase))
payload = table.concat ({verschlüsselt_iv, verschlüsselt_nodeD, verschlüsselt_Data, HMAC}, “,”, “,”, “)
Entschlüsselung
Jetzt weiß, dass Ihr MQTT-Broker nicht weiß oder sorgt, dass die Daten verschlüsselt sind, sondern nur weitergibt. Ihre anderen MQTT-Clients, die dem Thema abonniert haben, müssen wissen, wie er die Daten entschlüsselt. Bei NoDemcu ist das eher einfach. Teilen Sie einfach die empfangenen Daten in Saiten über die Kommas auf und tun Sie etwas wie folgt. HINWEIS Dieses Ende hat die Sitzungs-ID generiert, so dass es bereits weiß.
1.
2.
3
4.
5.
6.
7.
8.
9.
10.
staticiv = & quot; abcdef2345678901 & quot;
ivonkey = & quot; 2345678901abcdef & quot;
Datakey = & quot; 0123456789ABCDEF & quot;
Passphrase = & quot; MyPasshrase & quot;
iv = crypto.decrypt (& quot; aES-cbc & quot; ivonkey, verschlüsselt_iv, staticiv)
nodeID = crypto.decrypt (& quot; aES-cbc & quot;, datakey, encrypted_nodeid,iv)
data = crypto.decrypt("AES-CBC",datakey, encrypted_data,iv)
FullMessage = NodeID .. IV .. Daten .. SessionID
hmac = crypto.toehex (crypto.hmac (sha1 & quot;, FullMessage, Passphrase))
Then compare the received and computed HMAC, and regardless of the result, invalidate that session ID by generating a new one.
Once More, In Python
For a little variety, consider how we would handle decryption in Python, if we had an MQTT client on the same virtual machine as the broker that was analysing the data or storing it in a database. lets assume you’ve received the data as a string “payload”, from something like the excellent Paho MQTT client for Python.
In this case it’s convenient to hex encode the encrypted data on the NodeMCU before transmitting. So on the NodeMCU we convert all encrypted data to hex, for example: encrypted_iv = crypto.toHex(crypto.encrypt(“AES-CBC”, ivkey, iv, staticiv))
Publishing a randomized sessionID is not discussed below, but is easy enough using os.urandom() and the Paho MQTT Client. The decryption is handled as follows:
1.
2.
3
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
fünfzehn
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27
28
29.
30.
31.
32.
33.
34.
35.
from Crypto.Cipher import AES
import binascii
from Crypto.Hash import SHA, HMAC
# define all keys
ivkey = ‘2345678901abcdef’
datakey = ‘0123456789abcdef’
staticiv = ‘abcdef2345678901’
passphrase = ‘mypassphrase’
# convert the received string to a list
data = payload.split(",")
# extract list items
encrypted_iv = binascii.unhexlify(data[0])
encrypted_nodeid = binascii.unhexlify(data[1])
encrypted_data = binascii.unhexlify(data[2])
received_hash = binascii.unhexlify(data[3])
# decrypt the initialization vector
iv_decryption_suite = AES.new(ivkey,AES.MODE_CBC, staticiv)
iv = iv_decryption_suite.decrypt(encrypted_iv)
# decrypt the data using the initialization vector
id_decryption_suite = AES.new(datakey,AES.MODE_CBC, iv)
nodeid = id_decryption_suite.decrypt(encrypted_nodeid)
data_decryption_suite = AES.new(datakey,AES.MODE_CBC, iv)
sensordata = data_decryption_suite.decrypt(encrypted_data)
# compute hash function to compare to received_hash
fullmessage = s.join([nodeid,iv,sensordata,sessionID])
hmac = HMAC.new(passphrase,fullmessage,SHA)
computed_hash = hmac.hexdigest()
# see docs.python.org/2/library/hmac.html for how to compare hashes securely
The End, The Beginning
Now we have a system that sends encrypted, authenticated messages through an MQTT server to either another ESP8266 client or a larger system running Python. There are still important loose ends for you to tie up if you implement this yourself. The keys are all stored in the ESP8266s’ flash memory, so you will want to control access to these devices to prevent reverse engineering. The keys are also stored in the code on the computer receiving the data, here running Python. Further, you probably want each client to have a different key and passphrase. That’s a lot of secret material to keep safe and potentially update when necessary. Solving the key distribution problem is left as an exercise for the motivated reader.
And on a closing note, one of the dreadful things about writing an article involving cryptography is the possibility of being wrong on the Internet. This is a fairly straightforward application of the tested-and-true AES-CBC mode with HMAC, so it should be pretty solid. Nonetheless, if you find any interesting shortcomings in the above, please let us know in the comments.