Verschlüsselung mit dem ESP8266: AES-128 im CBC-Modus

von cooper.bin
veröffentlicht am 28.08.2024 aktualisiert am 28.08.2024

In der vernetzten Welt spielt die Sicherheit von Daten eine entscheidende Rolle. Verschlüsselungstechniken sind unerlässlich, um die Vertraulichkeit von Informationen zu gewährleisten, sei es bei der Kommunikation zwischen Geräten im Internet oder beim Schutz sensibler Daten. Eine Verschlüsselung wandelt Klartext in eine unleserliche Form um, die nur von autorisierten Personen mit dem passenden Schlüssel wieder entschlüsselt werden kann. In diesem Beitrag zeigen wir, wie man die AES-128-Verschlüsselung im CBC-Modus auf dem ESP8266 implementieren kann, um Daten effektiv und sicher zu verschlüsseln.

Zum Kapitel springen Verschlüsselung mit AES-128 CBC

AES steht für Advanced Encryption Standard und ist einer der weltweit am häufigsten verwendeten Verschlüsselungsstandards. AES-128 bezieht sich dabei auf die Schlüsselgröße die für den Verschlüsselungsprozess verwendet wird - 128 Bit.

Der CBC-Modus - Cipher Block Chaining - ist ein Verschlüsselungsmodus, der sowohl einen Schlüssel als auch einen Initialisierungsvektor kurz IV verwendet. Der Schlüssel sorgt für die eigentliche Verschlüsselung, während der IV sicherstellt, dass gleiche Klartexte unterschiedliche verschlüsselte Ergebnisse erzeugen können.

Jeder zu verschlüsselnde Datenblock hängt von allen vorherigen Blöcken ab, was die Sicherheit erhöht. Da der CBC-Modus blockweise arbeitet und die Blöcke voneinander abhängig sind, muss immer der gesamte Datenblock vorliegen, bevor er verschlüsselt werden kann. Dies führt zu der Einschränkung, dass der CBC-Modus nicht für die Verschlüsselung von Streaming-Daten geeignet ist. Für Anwendungen, die eine Echtzeitverschlüsselung erfordern, wie eben bei einer Verschlüsselung von Streams, wären andere Verfahren wie der CFB-Modus - Cipher Feedback Mode - besser geeignet, da sie Zeichen für Zeichen verschlüsseln können.

Wenn wir den Text "quick brown fox jumps over the lazy dog" mit einem AES-128 CBC-Algorithmus verschlüsseln, erhalten wir eine scheinbar zufällige Zeichenkette, die ohne den passenden Schlüssel und Initialisierungsvektor nicht entschlüsselt werden kann:

ue6Js6Fg0pBbwm2lF8XgLAlNeCTs58A5rfVXGqg9e6SLGZeOHzpCLt11Wiu+bPlu

In unserer ersten Implementierung verwenden wir einen statischen IV, was bedeutet, dass gleiche Klartexte auch gleiche verschlüsselte Ausgaben produzieren. Dies ist für bestimmte Anwendungen ausreichend und vereinfacht die Handhabung, erfordert jedoch besondere Sorgfalt bei der Verwaltung des IVs. In der zweiten Implementierung arbeiten wir mit einem dynamischen IV um unterschiedliche Ergebnisse bei ein und dem selben Klartext zu erhalten.

Zum Kapitel springen Installation der Bibliothek

Unsere AES-128 CBC-Implementierung für den ESP8266 ist aktuell nicht in der Arduino IDE Bibliotheksverwaltung verfügbar und muss daher manuell hinzugefügt werden. Dies lässt sich mit ein paar Schritten aber sehr leicht manuell erledigen.

Produktempfehlungen und -suche in Verbindung mit dem Amazon Partnerprogramm:

¹ Angaben ohne Gewähr. Bei einem Kauf über den Link erhalten wir eine Provision.

  1. Lade dir zuerst die Bibliothek von meinem GitHub-Repository herunter.

  2. Entpacke danach die .zip-Datei

  3. Kopiere den entpackten Ordner in den entsprechenden Bibliothek-Ordner der Arduino IDE:

    • Windows: C:\Users\Benutzername\Documents\Arduino\libraries
    • macOS: ~/Documents/Arduino/libraries
    • Linux: ~/Arduino/libraries
  4. Starte die Arduino IDE neu, damit die Bibliothek erkannt wird

Die erfolgreiche Installation kann überprüft werden, indem man die Beispiele der Arduino IDE aufruft. Dort sollte nun ein neues Beispiel aus meiner Library verfügar sein:

Arduino IDE
└── Datei
    └── Beispiele
        └── esp8266-aes-128-cbc
            └── esp8266-aes-128-cbc

Zum Kapitel springen Ein einfaches Verschlüsselungsbeispiel

Der folgende Code zeigt, wie wir mit der Bibliothek einen Text verschlüsseln und anschließend wieder entschlüsseln können:

#include "AESCrypto.h"

// Key und Initialisierungsvecot als Strings
// 16 Zeichen lang
String aes_key_str = "73secret!JO?&2%n";  // 16 Zeichen
String aes_iv_str  = "vectorImmaBossYA";  // 16 Zeichen

AESCrypto aesCrypto(aes_key_str, aes_iv_str);

void setup() {

  Serial.begin(115200);

  // Daten verschlüsseln
  String encdata = aesCrypto.encrypt("quick brown fox jumps over the lazy dog");
  Serial.println("encrypted:");  
  Serial.println(encdata);  

  // Daten entschlüsseln
  String decdata = aesCrypto.decrypt(encdata);
  Serial.println("decrypted:");  
  Serial.println(decdata);
}

void loop() {
  // Keine Wiederholungen im Loop
}

Der Schlüssel (aes_key_str) ist eine 16-stellige Zeichenkette, welche für die Verschlüsselung und Entschlüsselung verwendet wird. Er sollte geheim gehalten werden, da er der zentrale Sicherheitsmechanismus der Verschlüsselung ist.

Der Initialisierungsvektor (aes_iv_str) ist eine 16-stellige Zeichenkette, die zur korrekten Initialisierung des CBC-Modus benötigt wird. Er sorgt dafür, dass gleiche Klartexte unterschiedliche verschlüsselte Ausgaben erzeugen, wenn er bei jedem Verschlüsselungsvorgang dynamisch generiert wird.

encrypted:
ue6Js6Fg0pBbwm2lF8XgLAlNeCTs58A5rfVXGqg9e6SLGZeOHzpCLt11Wiu+bPlu
decrypted:
quick brown fox jumps over the lazy dog

Dieser Code kann sowohl für den Sender- als auch für den Empfänger ESP8266 verwendet werden. Möchte man allerdings architekturübergreifend mit den Daten arbeiten, so benötigt man noch ein passenden Gegenstück in einer anderen Programmiersprache. Hier habe ich ein Code-Beispiel für Javascript innerhalb von Node.JS vorbereitet, welches direkt mit dem Codesnippet kompatibel ist. Es kommt ganz externe Bibliotheken aus.

Produktempfehlungen und -suche in Verbindung mit dem Amazon Partnerprogramm:

¹ Angaben ohne Gewähr. Bei einem Kauf über den Link erhalten wir eine Provision.

const crypto        = require('crypto');

// 16-character KEY & IV
const aes_key_str   = "73secret!JO?&2%n"; 
const aes_iv_str    = "vectorImmaBossYA";

const cipher_key    = Buffer.from(aes_key_str, 'utf8');
const cipher_iv     = Buffer.from(aes_iv_str, 'utf8');

function encrypt(data){
    const   cipher      = crypto.createCipheriv('aes-128-cbc', cipher_key, cipher_iv);
    let     crypted     = cipher.update(data, 'utf-8', 'base64');
            crypted     += cipher.final('base64');
    return  crypted;
}

function decrypt(data){
    const   decipher    = crypto.createDecipheriv('aes-128-cbc', cipher_key, cipher_iv);
    let     dec         = decipher.update(data, 'base64', 'utf-8');
            dec         += decipher.final();
    return  dec;
}

// Example Usage
// let encryptedData = encrypt("quick brown fox jumps over the lazy dog");
// let decryptedData = decrypt("ue6Js6Fg0pBbwm2lF8XgLAlNeCTs58A5rfVXGqg9e6SLGZeOHzpCLt11Wiu+bPlu");

Zum Kapitel springen Sicherheit und Praxisrelevanz

AES-128 gilt als sicherer Algorithmus, der auch heute noch weit verbreitet ist. In Kombination mit dem CBC-Modus bietet er eine starke Verschlüsselung, die für viele Anwendungen ausreichend ist. Allerdings sollte man sich bewusst sein, dass die Sicherheit auch stark von der Geheimhaltung des Schlüssels und IVs abhängt. Werden diese kompromittiert, ist die Sicherheit der gesamten Kommunikation gefährdet.

Auf dem ESP8266, einem Mikrocontroller mit begrenzten Ressourcen, bietet AES-128 CBC eine gute Balance zwischen Sicherheit und Leistung. Für einfache IoT-Anwendungen ist diese Art der Verschlüsselung praxistauglich. Bei Anwendungen, die höhere Sicherheitsanforderungen haben, sollten jedoch weitere Maßnahmen, wie z.B. die regelmäßige Änderung von Schlüsseln, in Betracht gezogen werden.

Vorteile:

  • Sicherheit: AES-128 CBC bietet eine robuste Verschlüsselung, die für viele Anwendungen mehr als ausreichend ist.
  • Performance: Auf dem ESP8266 ist die Implementierung effizient genug, um in Echtzeitanwendungen eingesetzt zu werden.
  • Einfachheit: Mit unserer AESCrypto-Bibliothek lässt sich die Verschlüsselung leicht implementieren.

Einschränkungen:

  • Ressourcenverbrauch: Auf einem ESP8266, der nur begrenzte Ressourcen hat, kann die Verschlüsselung von großen Datenmengen zu Performanceproblemen führen.
  • Erhöhte Payload-Größe: Durch die Verschlüsselung wird die Größe der übertragenen Daten erhöht. Bei Anwendungen mit strikten Größenbeschränkungen, wie ESP-NOW, sollte dies berücksichtigt werden. Verwende gerne unseren Byte-Counter um die Größe deines Payloads berechnen zu lassen
  • Schlüsselmanagement: Die sichere Verwaltung von Schlüsseln und IVs bleibt eine Herausforderung und sollte nicht unterschätzt werden.

Zum Kapitel springen Dynamischer IV: Ein gängiges Verfahren für erhöhte Sicherheit

Zum Abschluss dieses Beitrags möchten wir noch auf den Einsatz eines dynamischen Initialisierungsvektors eingehen, was in vielen Anwendungsfällen eine gängige und empfohlene Praxis ist, um die Sicherheit der Verschlüsselung zu erhöhen. Ein dynamischer IV wird bei jeder Verschlüsselung neu generiert und zusammen mit den verschlüsselten Daten gespeichert oder übertragen. Dies stellt sicher, dass selbst identische Klartexte bei jedem Verschlüsselungsvorgang unterschiedliche Ausgaben erzeugen:

encrypted (IV + data): M6SGwPPSJzMD7Nhf1Htn0YnBX6I968u36mGNeOZQsZNkAFnLXdLuKQSxGab3zOwPv7wa55nWXSU0gBGB
decrypted: quick brown fox jumps over the lazy dog

encrypted (IV + data): fqTCgsdGaW7JaJ5JjX/jemkfvD5UWZmgAUBFTGYTcrmlVGLWT6HFtxtyG4rDcGE+7Bvzm/gy+6FRiTEf
decrypted: quick brown fox jumps over the lazy dog

encrypted (IV + data): L3bwmeXU7sS4mkVgMoLt5BbH5mJ4+YHMU+y8lelqtNaE70q0zo7t6HUEjekk4AWOGz2A9N6c4qA8gNXR
decrypted: quick brown fox jumps over the lazy dog

Da der IV bei jedem Verschlüsselungsvorgang anders ist, entstehen dadurch keine wiederkehrenden Muster in den verschlüsselten Daten, was die Anfälligkeit für bestimmte Arten von Angriffen verringert. Dies ist besonders wichtig, wenn ähnliche oder identische Daten mehrmals verschlüsselt werden müssen. Im folgenden Beispielcode wird der IV dynamisch generiert und kann zusammen mit den verschlüsselten Daten gesendet werden. Vor dem Entschlüsseln wird der IV anhand seiner Länge von 16 Bytes extrahiert.

#include "AESCrypto.h"


// Funktion um einen zufälligen IV zu generieren
String generateRandomIV() {
    String iv = "";
    for (int i = 0; i < 16; i++) {
        iv += char(random(32, 127));  // Generiert zufällige ASCII Zeichen
    }
    return iv;
}

// Key als String; 16 Zeichen lang
String aes_key_str = "73secret!JO?&2%n";  // 16 Zeichen

void setup() {

    Serial.begin(115200);

    /////
    ///// Hier findet die Verschlüsselung statt
    /////

    // Generiert zufälligen IV für jede Verschlüsselung
    String aes_iv_str = generateRandomIV();

    AESCrypto aesCrypto(aes_key_str, aes_iv_str);

    // Daten verschlüsseln mit dem zufälligen IV
    String plaintext = "quick brown fox jumps over the lazy dog";
    String encdata = aesCrypto.encrypt(plaintext);

    // Zufälligen IV vor die verschlüsselten Daten anhängen
    String combined_data = aes_iv_str + encdata;
    Serial.print("encrypted (IV + data):");
    Serial.println(combined_data);  



    /////
    ///// Hier findet die Entschlüsselung statt
    /////

    // IV aus den verschlüsselten Daten extrahieren
    String aes_iv_str_2 = combined_data.substring(0, 16);
    String encdata_2    = combined_data.substring(16);

    AESCrypto aesCrypto2(aes_key_str, aes_iv_str_2);

    // Daten entschlüsseln mit dem extrahierten IV
    String decdata = aesCrypto2.decrypt(encdata_2);
    Serial.print("decrypted:");
    Serial.println(decdata);


}

void loop() {
  // Keine Wiederholungen im Loop
}

Auch hier habe ich wieder ein passenden Gegenstück geschrieben in Javascript, welches nahtlos mit dem Code kompatibel ist.

const crypto = require('crypto');

// 16-stelliger Schlüssel
const aes_key_str   = "73secret!JO?&2%n"; 
const cipher_key    = Buffer.from(aes_key_str, 'utf8');


// Funktion um einen zufälligen 16-Byte-IV zu generieren
function generateRandomIV() {
    let iv = '';
    for (let i = 0; i < 16; i++) {
        iv += String.fromCharCode(Math.floor(Math.random() * (126 - 32 + 1)) + 32);  // Generiert zufällige ASCII Zeichen
    }
    return iv;
}


function encrypt(data) {

    // Generiere einen zufälligen 16-Byte-IV mit Base64-kompatiblen Zeichen
    const iv        = generateRandomIV();
    const aes_iv    = Buffer.from(iv, 'utf8');
    
    // Erstelle den Verschlüsselungsalgorithmus mit dem generierten IV
    const cipher    = crypto.createCipheriv('aes-128-cbc', cipher_key, aes_iv);
    
    // Verschlüssele die Daten
    let crypted     = cipher.update(data, 'utf8', 'base64');
    crypted         += cipher.final('base64');
    
    // Rückgabe: IV (als String) vor den verschlüsselten Daten
    return iv + crypted;
}

function decrypt(data) {

    // Extrahiere den IV (die ersten 16 Zeichen) und die verschlüsselten Daten
    const aes_iv_str        = data.substring(0, 16);
    const aes_iv            = Buffer.from(aes_iv_str, 'utf8');
    const encrypted_data    = data.substring(16);
    
    // Erstelle den Entschlüsselungsalgorithmus mit dem extrahierten IV
    const decipher          = crypto.createDecipheriv('aes-128-cbc', cipher_key, aes_iv);
    
    // Entschlüssele die Daten
    let dec     = decipher.update(encrypted_data, 'base64', 'utf8');
    dec         += decipher.final('utf8');
    
    return dec;
}

// Beispielanwendung
// let encryptedData = encrypt("quick brown fox jumps over the lazy dog");
// console.log("Verschlüsselt:", encryptedData);

// let decryptedData = decrypt("9BerKNNbmZ;uP8k_Y7FTiavYSRIu3Zk5Iv0g7inxG6jO7g8OvI0KoAEGW23s1SnCMBgbmof+1DwjQeXv");
// console.log("Entschlüsselt:", decryptedData);

Mögliche Nachteile und Herausforderungen

  • Komplexität in der Datenverarbeitung:
    Da der IV bei jedem Verschlüsselungsvorgang neu generiert wird, muss er gemeinsam mit den verschlüsselten Daten zusammen gespeichert oder übertragen werden. Dies kann die Datenverarbeitungskomplexität leicht erhöhen, da zusätzliche Schritte erforderlich sind, um den IV korrekt zu handhaben.
  • Erhöhter Overhead:
    Das Speichern oder Übertragen des IV zusammen mit den verschlüsselten Daten erhöht die Gesamtdatenmenge, was bei stark limitierten Bandbreiten oder Speicherplatz kritisch sein könnte.

In der Praxis ist die Verwendung eines dynamischen IVs eine bewährte Methode, um die Sicherheit von Verschlüsselungslösungen erheblich zu verbessern. Obwohl die Handhabung eines dynamischen IVs zusätzlichen Aufwand bedeuten kann, überwiegen die Sicherheitsvorteile in den meisten Fällen, insbesondere in sicherheitskritischen Anwendungen.


cooper.bin Avatar

cooper.bin

Unterstütze mich und meine Arbeit, so kann ich weiter meiner Leidenschaft nachgehen. Ich lege viel Wert auf Qualität und stecke daher sehr viel Zeit in meine Beiträge. Wenn sie dir gefallen kannst du dir gerne auch meine anderen Artikel anschauen.

Mit PayPal unterstützen

Ich bin auf dem makesmart Discord-Server aktiv. Dort bin ich auch relativ gut erreichbar.

Teile diesen Beitrag



Diese Artikel könnten dich auch interessieren

Der DHT22 am ESP8266 - Wie man die Temperatur und Luftfeuchtigkeit misst

In diesem Tutorial lernst du, wie du mit einem DHT22 und einem ESP8266 D1 Mini die Luftfeuchtigkeit und die Temperatur einfach messen kannst.

cooper.bin am 12.03.2024

ESP8266 - Ein einfacher Webserver mit mDNS

Während Webserver wie Apache2 oder NGNIX auf Rechnern laufen, kann man auch auf einem ESP8266 einen Webserver verwenden. In diesem Tutorial werden wir ein Grundgerüst implementieren.

cooper.bin am 13.02.2024

Der DS18B20 Temperatursensor am ESP8266 D1 Mini - Einfache Temperaturmessung

Lerne, wie du mit der Arduino IDE, dem ESP8266 und einem DS18B20 Temperatursensor einfache und schnelle Temperaturmessungen durchführen kannst.

cooper.bin am 05.03.2024

ESP8266 programmieren - Der Start mit der Arduino IDE

Erfahre, wie du den ESP8266 D1 Mini mit der Arduino IDE programmierst. Dank den kostenlosen Entwicklungswerkzeugen und der microUSB-Buchse ist das für jeden ein Kinderspiel.

cooper.bin am 10.02.2024

HC-SR04 und JSN-SR04T - Entfernungen messen mit Ultraschallsensoren am ESP8266

Lerne in diesem Tutorial, wie die Ultraschallsensoren HC-SR04 und JSN-SR04T mit ESP8266 D1 Mini für Distanzmessungen eingesetzt werden, die Grundlagen, Anwendungen und mehr.

cooper.bin am 09.03.2024