Kryptographie für Arduino

17.04.2013 yahe arduino code hardware legacy security

Nachdem ich letztens meinen kleinen Arduino-Speichermanager fertig gestellt habe, geht es nun darum, etwas produktives damit anzufangen. In meinem Fall lag das erste Hauptaugenmerk auf kryptographischen Algorithmen, sprich das Verschlüsseln und Hashen. Dabei muss ich natürlich immer den Speicher im Auge behalten. Herausgekommen ist die Bibliothek Cryptuino.

Um mit etwas einfacherem zu beginnen, habe ich zuerst einmal eine Base64-Encodierung, da mit dieser bereits ein bisschen mehr Erfahrung habe. Normalerweise kann ein Computer nur eine begrenzte Menge an Zeichen, die sogenannten druckbaren Zeichen, ordentlich darstellen. Im Fall vom ASCII-Standard, den so gut wie alle Computer in abgewandelter Form beherrschen, sagt Wikipedia folgendes:

Die Zeichenkodierung definiert 128 Zeichen, bestehend aus 33 nicht druckbaren sowie 95 druckbaren.

Das Problem beim Hashen und Verschlüsseln ist jedoch, dass die Ausgabe im gesamten Bytebereich von 0 bis 255 verstreut ist. Computer können das in der Regel nicht ordentlich darstellen. Das Ergebnis sind Fehldarstellungen. Aus diesem Grund sorgt Base64 dafür, dass der nicht-druckbare Text in druckbaren Text umgewandelt wird. Statt aller 256 Werte, die mit einem Byte dargestellt werden können, werden nur 64 Zeichen verwendet. Damit wird das Ergebnis (um 33%) länger, aber dafür lesbar.

CHUNK encode_base64(CHUNK input);
CHUNK decode_base64(CHUNK input);

Die Anwendung ist relativ einfach. Mit "encode_base64()" enthält man den Base64-encodierten Text und mit "decode_base64()" erhält man wieder den Ursprungstext. Base64 wird übrigens zum Beispiel verwendet, um die Anhänge in E-Mails zu encodieren. Da es sich bei den Protokollen IMAP, POP3 und SMTP um Textprotokolle handelt, kann man dort nicht einfach irgendwelche Binärdaten hineinkippen. Aus diesem Grund enstand MIME, das u.a. Base64 als Datenencodierung vorsieht.

Gut, als nächstes brauchte ich einen Hashalgorithmus. Ich habe lange überlegt, welchen ich genau benutzen soll, habe mich dann aber schlussendlich für SHA-1 entschieden. Es gibt mit der SHA-2-Familie zwar inzwischen eine bessere Alternative, diese benötigen jedoch auch wesentlich mehr Speicher. Bei so einem Hashalgorithmus geht es darum, eine Eingabe mit belieber Länge in eine Ausgabe mit fixer Länge zu überführen. Dabei legt man besonderen Wert darauf, dass diese Überführung bestimmte Kriterien erfüllt. Eine der Anforderungen ist, dass man aus dem Ergebnis der Funktion keinen Rückschluss darauf ziehen kann, was die Eingabe oder Teile davon gewesen ist. Es soll sehr schwer sein, zwei Eingaben zu finden, die zur gleichen Ausgabe führen, die sogenannte Kollisionsresistenz. Und bei der kleinsten Änderung der Eingabe soll sich diese Änderung sehr deutlich in der Ausgabe widerspiegeln, der sogenannte Avalance-Effekt.

CHUNK hash_sha1(CHUNK input);
CHUNK hmac_sha1(CHUNK input, CHUNK password);

Die Verwendung der Hashfunktion ist recht einfach. Man übergibt der Funktion "hash_sha1()" seinen Input und erhält als Output die Hashsumme, die bei SHA-1 immer genau 20 Zeichen lang ist. Darüber hinaus habe ich mit "hmac_sha1()" noch den zugehörigen HMAC-Algorithmus implementiert. Bei einem MAC (Message Authentication Code) handelt es sich um eine Möglichkeit, die Korrektheit einer Nachricht zu überprüfen. Ein HMAC (Hash-based Message Authentication Code) macht dies, indem die Nachricht und ein geheimer Schlüssel genommen und beides zusammen gehasht wird. Jemand, der die Nachricht erhält und den geheimen Schlüssel kennt, kann so überprüfen, ob der HMAC zur erhaltenen Nachricht passt.

Auf Basis von HMACs werden zum Beispiel sehr gerne Challenge-Response-Verfahren realisiert. Dabei wird bei der Passwortabfrage nicht einfach das Passwort an den Server geschickt, sondern der Server schickt eine Challenge (eine zufällige Zahl). Derjenige, der sich einloggen will, erzeugt nun einen HMAC aus dieser zufälligen Zahl und seinem Passwort und schickt dem Server das Ergebnis zu. Da auch der Server das Passwort kennt, kann er aus der Zufallszahl und dem Passwort ebenfalls den HMAC bilden. Stimmen sein Ergebnis und das zugesandte Ergebnis des Nutzers überein, ist sichergestellt, dass dieser das richtige Passwort kennt, ohne, dass das eigentliche Passwort zwischen beiden Parteien übertragen werden musste.

Bleibt noch das Thema Verschlüsselung übrig. Hier habe ich mich nach langem hin und her für ARC4 (Alleged RC4) entschieden. Dabei handelt es sich um einen eigentlich proprietären Algorithmus, dessen Struktur jedoch Mitte der 90er Jahre im Usenet veröffentlicht wurde und seitdem unter dem Namen "ARC4" oder "ARCFOUR" weite Verbreitung gefunden hat. Dieser Algorithmus hat einen Nachteil, den ich gleich näher erläutere.

CHUNK encrypt_arc4(CHUNK input, CHUNK password);
CHUNK decrypt_arc4(CHUNK input, CHUNK password);

Auch hier sollte die Verwendung klar sein. "encrypt_arc4()" übergibt man seine Nachricht und sein Passwort und erhält anschließend seine verschlüsselte Nachricht. "decrypt_arc4()" überreicht man die verschlüsselte Nachricht und dasselbe Passwort und erhält wieder den Klartext. Wie jedoch schon gesagt, hat ARC4 ein Problem, das durch seine einfache Struktur zu erklären ist. Als Stromchiffre ist ARC4 im Grunde ein Pseudo-Zufallsgenerator, der anhand des Passworts einen endlosen Strom an "zufälligen" Bytes erzeugt. Diese Bytes werden mit den Bytes der zu verschlüsselnden Nachricht geXORt. Das Verfahren selbst ist nicht unsicher, weist jedoch eine Besonderheit auf: Verschlüsselt man zwei Nachrichten mit demselben Passwort und XORt die beiden verschlüsselten Nachrichten, so erhält man als Ergebnis das XOR-Ergebnis der beiden unverschlüsselten Nachrichten:

c1_i = m1_i XOR k_i
c2_i = m2_i XOR k_i

c1_i XOR c2_i = (m1_i XOR k_i) XOR (m2_i XOR k_i)
              = m1_i XOR k_i XOR m2_i XOR k_i
              = (m1_i XOR m2_i) XOR (k_i XOR k_i)
              = (m1_i XOR m2_i) XOR 0
              = m1_i XOR m2_i

Wenn man nun also eine verschlüsselte Nachricht "c1_i = m1_i XOR k_i" und eine verschlüsselte Nachricht "c2_i = m2_i XOR k_i" abgefangenhat und diese beiden XORt, dann folgt daraus "c1_i XOR c2_i = m1_i XOR m2_i". Der Schlüsselstrom k_i kürzt sich also einfach raus und übrig bleiben die beiden Ursprungsnachrichten. Das ist erstmal eine unerfreuliche Information. Das positive ist, dass man dieses Problem relativ einfach umgehen kann, indem man dafür sorgt, dass ein einmal verwendetes Passwörter nie ein zweites Mal verwendet wird. Das geht zum Beispiel über einen sogenannten Nonce ("number used once"), also eine zufällige Zahl, die nur ein einziges Mal verwendet werden darf. Wenn ihr nun ARC4 nicht direkt mit eurem Passwort verwendet, sondern erst per "hmac_sha1(Nonce, Passwort)" das eigentliche Passwort für ARC4 ermittelt, seid ihr auf der sicheren Seite.

Kryptographie in Aktion Kryptographie in Aktion


Search

Categories

administration (45)
arduino (12)
calcpw (3)
code (38)
hardware (20)
java (2)
legacy (113)
linux (31)
publicity (8)
raspberry (3)
review (2)
security (65)
thoughts (22)
update (11)
windows (17)
wordpress (19)