communityWir suchen ständig neue Tutorials und Artikel! Habt ihr selbst schonmal einen Artikel verfasst und seid bereit dieses Wissen mit der Community zu teilen? Oder würdet ihr gerne einmal über ein Thema schreiben das euch besonders auf dem Herzen liegt? Dann habt ihr nun die Gelegenheit eure Arbeit zu veröffentlichen und den Ruhm dafür zu ernten. Schreibt uns einfach eine Nachricht mit dem Betreff „Community Articles“ und helft mit das Angebot an guten Artikeln zu vergrößern. Als Autor werdet ihr für den internen Bereich freigeschaltet und könnt dort eurer literarischen Ader freien Lauf lassen.

Blockchiffre Operationsmodi Drucken E-Mail
Benutzerbewertung: / 27
SchwachPerfekt 
Samstag, den 29. März 2008 um 21:11 Uhr

Inhaltsverzeichnis

  1. Einführung in die Blockchiffre Betriebsarten
  2. Der Initialisierungsvektor (IV)
  3. Padding
  4. Grundlagen zu den Betriebsmodi:
    1. Electronic Codebook (ECB)
    2. Cipher-Block Chaining (CBC)
    3. Cipher feedback (CFB)
    4. Output feedback (OFB)
  5. Implementierung:
    1. Implementierung: Cipher-Block Chaining (CBC)
    2. Implementierung: Cipher feedback (CFB)
    3. Implementierung: Output feedback (OFB)
  6. Alles zusammenfügen
    1. Passwort
    2. Dateien verschlüsseln
    3. Bytes verschlüsseln
  7. Testvektoren
    1. Liste von Testvektoren für den AES/ECB Operationsmodi
    2. Liste von Testvektoren für den AES/CBC Operationsmodi
    3. Liste von Testvektoren für den AES/CFB Operationsmodi
    4. Liste von Testvektoren für den AES/OFB Operationsmodi
  8. Weitergehende Links

Einführung in die Blockchiffre Betriebsarten

In der Kryptographie operieren Blockchiffre auf einem Datenblock fester Länge, oft 64 oder 128 Bits. Da eine zu verschlüsselnde Nachricht eine beliebige Länge haben kann, und weil die Verschlüsselung desselben Klartextes mit demselben Schlüssel immer denselben Geheimtext produziert, wurden verschiedene Betriebsarten entwickelt, um die Vertraulichkeit beliebig langer Nachrichten bei den Blockverschlüsselungen sicherzustellen.

Die frühesten Betriebsarten, die in der Literatur (z.B. ECB, CBC, OFB und CFB) beschrieben wurden, gewährleisten nur Vertraulichkeit, nicht jedoch die Datenintegrität. Andere Betriebsarten wurden seitdem entwickelt um beides, Vertraulichkeit und Datenintegrität sicherzustellen, beispielsweise der IAPM, CCM, EAX, GCM und OCB-Modus.

In dem Tutorial über den Advanced Encryption Standard (AES) haben Sie erfahren, wie der Algorithmus funktioniert und man damit Blockdaten verschlüsseln kann. Nun werden Sie erfahren, wie sich bekannte Blockchiffre Operationsmodi auf den AES anwenden lassen, so dass Sie auch in die Lage versetzt werden, Nachrichten beliebiger Länge zu verschlüsseln. Dazu sehen wir uns fünf bekannte Betriebsarten, namentlich den ECB, CBC, OFB und CFB genauer an und implementieren einige davon in C.

Der Initialisierungsvektor (IV)

Der Initialisierungsvektor bezeichnet einen Block von Zufallsdaten, der in bestimmten Modi einiger Blockchiffren verwendet wird, wie dem Cipher Block Chaining Mode, kurz CBC.

Beim Verschlüsseln von Nachrichten muss vermieden werden, dass gleiche Klartextblöcke immer wieder gleiche Geheimtextblöcke ergeben. Ein förmlicher Brief fängt im Deutschen in der Regel mit „Sehr geehrter Herr/Frau“ gefolgt vom Namen an. Aus diesem Wissen könnte ein Angreifer versuchen Rückschlüsse auf den verwendeten Schlüssel zu ziehen. Um das zu vermeiden, wird der erste Klartextblock mit einem IV XOR-verknüpft. Da der IV zufällig erzeugt wurde, unterscheiden sich die entstehenden Geheimtexte auch dann, wenn die Klartexte mit identischen Daten beginnen.

Da bei den Verschlüsselungsalgorithmen in der Regel Modi gewählt werden, bei denen der Geheimtext eines Blocks vom Geheimtext seines Vorgängerblocks abhängt, muss der IV nicht geheim gehalten werden. Sie werden jedoch schon sehr bald erkennen, das ein öffentlicher IV Informationen preisgibt, die bei Nutzung mit ständig gleichem Schlüssel sicherheitsrelevant sein können. Bedenken Sie, dass der IV ein Initialisierungsblock, so wie unser Chiffreblock ist (128 Bit beim AES).

Padding

Das Padding (von engl. to pad für auffüllen) dient dazu einen vorhandener Datenbestand zu vergrößern. Die Füllbytes werden auch Pad-Bytes genannt. Da Blockchiffre mit einer festen Blockgröße arbeiten, die Nachrichten allerdings variabel sind, benötigen einige Modi Pad-Bytes um den letzten Block ggf. aufzufüllen. Wie Sie in dem Artikel erfahren werden, existieren diverse Paddingschemen. Die einfachste Methode (die auch wir implementieren werden) ist es, den Klartext mit Null-Bytes aufzufüllen um auf diese Weise ein Vielfaches der Länge, der Blockgröße zu erhalten. Nur ein wenig komplexer ist das Verfahren beim alten DES, bei dem zunächst ein einzelnes Bit ‘1’ hinzugefügt wird, um anschließend den Block mit Nullen ‘0’ aufzufüllen. Falls die Nachricht exakt an einer Blockgrenze endet, wird ein vollständig neuer Block hinzugefügt.

f8
26
a1
90
7b
6d
de
a3
48
d2
01
00
00
00
00
00

Tabelle 1. Darstellung eines aufgefüllten 128-Bit Blocks (Padding)

Für die ECB, CBC und CFB Modi, muss der Klartext eine Sequenz von vollständigen Datenblöcken (im CFB Modus Datensegmente) sein. Mit anderen Worten, für alle drei Modi muss die Anzahl an Bits im Klartext dem positiven Vielfachen der Rijndael Block- bzw. Segmentgröße entsprechen.

Falls die Größe der Datenzeichenkette dieser Anforderung nicht genügt, muss der Klartext in der Anzahl an Bits vergrößert werden, d.h gepaddet werden. Beim Padding können mit der oben genannten Null-Byte-Methode, bei Übertragungen Datenvolumen eingespart werden, indem die aufgefüllten Nullen verworfen werden. Das setzt allerdings voraus, dass dem Empfänger bekannt ist das die Nachricht verkürzt übertragen wurde und nachträglich wieder aufgefüllt werden muss. Das kann beispielsweise durch protokollspezifische Nachrichtengrenzen erreicht werden, ist für unseren Artikel allerdings nicht weiter relevant.

Betriebsmodi:

Nachfolgend werden die theoretischen Grundlagen zu den verschiedenen Betriebsarten detailliert erklärt. Wir beginnen mit dem einfachsten und zugleich unsichersten Modus, dem ECB.

Electronic Codebook (ECB)

Beim ECB werden die Klartextblöcke nacheinander und unabhängig voneinander einfach in den Geheimtextblock überführt, nachdem die Nachricht in gleich große, für den Algorithmus geeignete Blöcke aufgeteilt wurde. Der Name ECB rührt daher, dass Codebücher über die Zuordnung von Chiffretexten und Klartexten erstellt werden können. Der ECB birgt große Gefahren, da durch den Modus Klartextmuster nicht verwischt werden. Gleiche Klartextblöcke ergeben bei gleichen Schlüssel auch immer den gleichen Geheimtextblock, wodurch man bei hinreichend vielen Geheimtextblöcken und partiellen Annahmen über den Klartext Rückschlüsse auf den geheimen Schlüssel ziehen kann. Aus verschiedenen Gründen ist der Modus nicht sonderlich sicher, und sollte vermieden werden. Es wird dringend davon abgeraten diesen Modus dennoch zu verwenden. Der Vollständigkeit halber möchten wir den Modus dennoch auflisten:

Ecb_mode

Der Electronic Codebook (ECB) Modus ist, wie folgt definiert:

ECB Verschlüsselung: Cj = CIPHK(Pj)     für j = 1 … n.
ECB Entschlüsselung: Pj = CIPH-1K(Cj)   für j = 1 … n.

Wenn man eine Grafik damit verschlüsselt, die nur aus einigen schwarzen Linien besteht, und dabei 0 (Bit) für Weiß und 1 (Bit) für Schwarz steht, wird man sehr viele Blöcke finden, die nur aus 0 bestehen. Alle Geheimtextblöcke die dann anders sind, enthalten min. eine 1 (Bit). Dadurch könnte man die Zeichnung bis auf ein paar Millimeter Abweichung rekonstruieren, ohne den Schlüssel zu kennen. Das zweite Bild zeigt diese Schwäche deutlich auf:

ecb_tux ecb_tux_enc Tux_secure

Darüberhinaus kann man beim ECB Geheimtextblöcke austauschen, wodurch sich zum Beispiel die Summe oder der Empfänger einer Überweisung ändern könnte. Das dritte Bild zeigt dasselbe Bild, bei dem allerdings der deutlich sicherere CBC-Modus verwendet wurde.

Cipher-Block Chaining (CBC)

Der Cipher Block Chaining (CBC) Modus ist ein auf Vertraulichkeit abzielender Modus, dessen Verschlüsselungsprozess den Klartextblock mit dem im letzten Schritt erzeugten Geheimtextblock per XOR (exklusives Oder) verknüpft. Daher der Name “chaining”, was für verketten steht. Der CBC-Modus benötigt einen Initialisierungsvektor, um diesen mit dem ersten Block zu verknüpfen. Der IV muss nicht geheim bleiben, aber er sollte zufällig gewählt sein. Er wird oft entweder durch einen Zeitstempel gebildet oder durch eine zufällige Zahlenfolge.

Cbc_mode

Der Cipher Block Chaining (CBC) Modus ist, wie folgt definiert:

CBC Verschlüsselung: C1 = CIPHK(P1 ⊕ IV);
                     Cj = CIPHK(Pj ⊕ Cj-1)    für j = 2 … n.
CBC Entschlüsselung: P1 = CIPH-1K(C1) ⊕ IV;
                     Pj = CIPH-1K(Cj) ⊕ Cj-1  für j = 2 … n.

Der CBC-Mode hat einige wichtige Vorteile:

  • Klartextmuster werden zerstört.
  • Jeder Geheimtextblock hängt von allen vorherigen Klartextblöcken ab.
  • Identische Klartextblöcke ergeben unterschiedliche Geheimtexte.
  • Verschiedene Angriffe (Time-Memory-Tradeoff und Klartextangriffe) werden erschwert.

Da ein Geheimtextblock nur von dem vorherigen Blöck abhängt, verursacht ein beschädigter Geheimtextblock, wie beispielsweise ein Bitfehler bei der Datenübertragung, beim Entschlüsseln keinen allzugroßen Schaden, denn es werden nur der betroffene Klartextblock zu 50% und im darauffolgenden Klartextblock ein Bit falsch im Klartext dechiffriert. Dies ist unmittelbar aus der Definition der Entschlüsselung und obiger Abbildung ersichtlich, da ein beschädigter Geheimtextblock Cj nur die Klartextblöcke Pj und Pj+1 beeinflusst und sich nicht unbeschränkt weiter verbreitet. Trotzdem kann diese beschränkte Vervielfachung nur eines einzigen Bitfehlers im Chiffrat bei CBC eine Vorwärtsfehlerkorrektur des Klartextes erschweren bzw. unmöglichen machen. Genauso verursacht ein beschädigter Initialisierungsvektor beim Entschlüsseln keinen allzugroßen Schaden, da dadurch nur der Klartextblock P1 beschädigt wird.

Der CBC-Modus ist wesentlich sicherer als der ECB-Modus, vor allem wenn man keine zufälligen Texte hat. Unsere Sprache und andere Dateien, wie z. B. Video-Dateien, sind keinesfalls zufällig, weswegen der ECB-Mode im Gegensatz zum CBC-Mode Gefahren birgt. Generell sollte ein Blockchiffre immer im CBC-Modus betrieben werden - Ausnahmen sollten gut begründet sein.

Ausnahmen bestätigen bekanntlich die Regel. So hat der CBC-Modus einen entscheidenden Nachteil. Die Nachricht muss auf Vielfache der Chiffreblockgröße aufgefüllt werden. Daher wird der CBC-Modus zunehmend von synchronen Stromchiffren abgelöst, wie dem CFB oder OFB.

Cipher feedback (CFB)

In diesem Modus wird, wie in der Abbildung unten dargestellt, die Ausgabe der Blockchiffre mit dem Klartext bitweise XOR (exklusives ODER) verknüpft um daraus den Geheimtext zu bilden. Diese Betriebsart bzw. dieser Modus ergibt damit eine sogenannte Stromchiffre.

Der Cipher Feedback (CFB) Modus zielt auf Vertraulickeit ab, da auch in diesem Modus Klartextmuster verwischt werden. Auch bei diesem Modus wird ein IV benötigt.

Darüberhinaus benötigt der CFB einen ganzzahligen Parameter, s bezeichnet, der mit 1 ≤ s ≤ b festgelegt ist. In der Spezifikation besteht jedes Klartextsegment (P#j) und Geheimtextsegment (C#j) aus s Bits. Die Zahl der Bits wird oft in den Namen des Modus eingearbeitet, z.B. der 1-Bit CFB Modus, der 8-Bit CFB Modus, der 64-Bit CFB Modus oder der 128-Bit CFB Modus.

Cfb_mode

Die Entschlüsselung beim Empfänger, wie in obiger Abbildung dargestellt, funktioniert wie die Verschlüsselung, erzeugt also bei gleichem Initialisierungsvektor und gleichem Schlüssel die gleiche binäre Datenfolge mit der die XOR-Operation des Sender rückgängig gemacht werden kann. Die Grafik zeigt auch den wesentlichen Nachteil dieser Stromchiffre: Durch nur einen einzigen Bitfehler der bei der Übertragung auftreten kann, wird im aktuellen Klartextdatenblock genau ein Bitfehler erzeugt und zusätzlich im nachfolgenden Datenblock im Mittel 50% der Datenbits zerstört. Diese Fehlerfortpflanzung ist ähnlich wie bei der Betriebsart Cipher Block Chaining (CBC) und erschwert die Entschlüsselung des Klartextes.

Der Cipher feedback (CFB) Modus ist folgendermaßen definiert:

CFB Verschlüsselung: I1 = IV;
                     Ij = LSBb-s(Ij–1) | C#j–1  für j = 2 … n.
                     Oj = CIPHK(Ij)            für j = 1, 2 … n;
                     C#j = P#j ⊕ MSBs(Oj)      für j = 1, 2 … n.
CFB Entschlüsselung: I1 = IV;
                     Ij = LSBb-s(Ij–1) | C#j–1  für j = 2 … n.
                     Oj = CIPHK(Ij)            für j = 1, 2 … n;
                     P#j = C#j ⊕ MSBs(Oj)      für j = 1, 2 … n.

Bei der CFB Verschlüsselung ist der erste Eingabeblock, der IV und die vorwärts gerichtete Chiffreoperation wird auf den IV angewendet um den ersten Ausgabeblock zu produzieren. Das erste Geheimtextsegment wird durch eine exlusiv-ODER Verknüpfung des ersten Klartextsegmentes mit den s meist signifikanten Bits (MSB) des ersten Ausgabeblocks generiert. (Die übriggebliebenen b-s Bits des ersten Ausgabeblocks werden verworfen.) Die letzten signifikanten Bits (LSB) des IV werden anschließend mit den s Bits des ersten Geheimtextsegmentes verkettet um den nächsten Eingabeblock zu formieren. Eine alternative Erklärung, wie der nächste Eingabeblock erzeugt wird, ist, sich die Operation als zyklischen Links-Shift um s Stellen vorzustellen und anschließend ersetzt das Geheimtextsegment die s letzten signifikanten Bits des Ergebnisses.

Der Vorgang wird mit allen aufeinanderfolgenden Eingabeblöcken wiederholt bis für jedes Klartextsegment ein Geheimtextsegment erzeugt wurde. Allgemein gesprochen wird jeder Eingabeblock verschlüsselt um einen Ausgabeblock zu produzieren. Die s meist signifikanten Bits jedes Ausgabeblocks werden mit dem korrespondierenden Klartextsegment exklusiv-ODER verknüpft, um das Geheimtextsegment zu generieren. Jedes Geheimtextsegment (außer dem letzen) wird “rückgekoppelt” in den vorangegangenen Eingabeblock, so wie oben beschrieben, um einen neuen Eingabeblock zu formieren. Dieser Feedback kann in Form von den individuellen Bits in den Zeichenketten folgendermaßen beschrieben werden: wenn i1i2…ib der j-te Eingabeblock ist und c1c2…cs das j-te Geheimtextsegment, dann ist der (j+1)-te Eingabeblock is+1is+2…ib c1c2…cs.

Bei der CFB Verschlüsselung kann, wie beim CBC-Modus, die Chiffreoperation nicht parallel durchgeführt werden, da der Eingabeblock von dem vorangegangenen Ergebnis abhängt. Bei der CFB Entschlüsselung kann die Operation allerdings parallelisiert werden, falls die Eingabeblöcke zuerst aus dem IV und dem Geheimtext konstruiert werden.

Trotz des Vorteils der Selbstsynchronisation wird der CFB in der Praxis nur selten eingesetzt: Spielt die Fehlerfortpflanzung auf den nächsten Block in einer bestimmten Anwendung keine Rolle bzw. wird durch geeignete zusätzliche Verfahren kompensiert, kommt meist der CBC zur Anwendung. Wird eine Stromchiffre ohne Fehlerfortpflanzung in einer Anwendung benötigt, kommt meist der Modus OFB zu Anwendung.

Output feedback (OFB)

Wenn Sie den CFB Modus verstanden haben, sollten Sie auch keine Probleme mit dem OFB Modus haben. Der Output Feedback (OFB) Modus verhält sich wie der CFB, mit dem Unterschied das er vor der XOR Operation den AES Ausgabeblock als Eingabe für die nächste Iteration verwendet. Der OFB ist folgendermaßen definiert:

CFB Verschlüsselung: I1 = IV;
                     Ij = Oj-1                  für j = 2 … n.
                     Oj = CIPHK(Ij)           für j = 1, 2 … n;
                     Cj = Pj ⊕ Oj             für j = 1, 2 … n-1.
                     C*j = P*j ⊕ MSBu(Ou)
CFB Entschlüsselung: I1 = IV;
                     Ij = Oj-1                  für j = 2 … n.
                     Oj = CIPHK(Ij)           für j = 1, 2 … n;
                     Pj = Cj ⊕ Oj             für j = 1, 2 … n-1.
                     P*j = C*j ⊕ MSBu(Ou)
Ofb_mode

Sowohl bei der OFB-Verschlüsselung, als auch der OFB-Entschlüsselung, hängt jede vorwärts gerichtete Chiffreoperation, mit Ausnahme der ersten Operation, von der vorigen Chiffreoperation ab. Daher können multiple Vorwärtsverschlüsselungen nicht parallel durchgeführt werden. Allerdings kann bei bekannten IV der Ausgabeblock generiert werden, bevor der Klartext oder der Geheimtext verfügbar sind. Der OFB Modus benötigt einen eindeutigen IV für jede Nachricht unter einem gegebenen Schlüssel. Falls entgegen dieser Notwendigkeit, derselbe IV für die Verschlüsselung genutzt wird, dann ist die Vertraulichkeit der Nachricht nicht mehr gewährleistet.

Implementierung:

Bevor wir damit beginnen die verschiedenen Betriebsarten zu implementieren, werden wir zunächst eine Struktur definieren, die es uns ermöglichen wird den entsprechenden Modus auszuwählen, sowie den Prototypen der Verschlüsselungsfunktion:

// Block cipher modes of operation
typedef enum
{
    // Output feedback
    OFB,
    // Cipher feedback
    CFB,
    // Cipher-block chaining
    CBC
} CIPHERMODE;
int32_t encryptFile(FILE* in, FILE* out, KEYSIZE keySize, CIPHERMODE mode, const uint8_t* password, uint32_t passwordLength);

Zu einem späteren Zeitpunkt, wenn Sie verschiedene Blockchiffre implementiert haben, können Sie diesen Prototypen abändern, damit dieser dann auf einen Blockchiffre zeigt, der dann intern vom Operationsmodi verwendet wird.

Ich habe mich dazu entschlossen verschiedene Blöcke, der Größe 128 Bit zu nutzen, die alle einem eigenen Zweck dienen:

  • plaintext: dieser korrespondiert mit dem 128-Bit großen Klartextblock, der bei jeder Iteration verschlüsselt wird.
  • input: der Input stellt die Eingabe des Blockchiffre dar.
  • output: korrespondiert mit der Ausgabe des Blockchiffre.
  • ciphertext: korrespondiert mit der Ausgabe des Betriebsmodi bei einer Iteration.

Wie Sie später sehen werden, kann man auch weniger Blöcke verwenden, aber da jede Operation eine eigene spezielle Struktur hat, kann man den Code durch die Einführung von ein wenig Overhead sauber halten. Ich verwende darüberhinaus eine zusätzliche Variable, die anzeigt ob wir uns gegenwärtig in der ersten Runde (hier brauchen wir den IV) oder in einer anderen Runde befinden.
Um den exakten Klartext wiederherzustellen, auch mit unserem Paddingschema, habe ich mich dazu entschlossen zuerst den verwendeten Operationsmodi und die exakte Dateigröße in die Ausgabedatei zu schreiben:

fseek(in, 0, SEEK_END);
fileSize = ftell(in);
fseek(in, 0, SEEK_SET);
 
// Add the file header
fwrite(&mode, sizeof(CIPHERMODE), 1, out);
fwrite(&fileSize, sizeof(fileSize), 1, out);

Da wir mit Binärdateien arbeiten, benutze ich fread() und fwrite() um die zwei Dateien zu verarbeiten. Unser Algorithmus liest 16 Bytes ein (der Returnwert von fread() wird gespeichert, falls wir weniger als 16 Bytes einlesen und wir padden müssen), dann führen wir bei Bedarf das Padding durch, XORieren den Klartext mit dem IV oder der Ausgabe (in CBC ist die Ausgabe gleich dem Chiffretext), wenden die AES Verschlüsselung an und schreiben das Resultat in die Datei.

Implementierung: Cipher-Block Chaining (CBC)

while ((read = fread(plaintext, sizeof(uint8_t), 16, in)) > 0) {
    // Padd with 0 bytes
    if (read < 16) {
        for (i = read; i < 16; i++) {
            plaintext[i] = 0;
        }
    }
 
    for (i = 0; i < 16; i++) {
        input[i] = plaintext[i] ^ ((firstRound) ? IV[i] : ciphertext[i]);
    }
 
    firstRound = 0;
    cipher(input, ciphertext, &context);
 
    // Always 16 bytes because of the padding for CBC
    fwrite(ciphertext, sizeof(uint8_t), 16, out);
}

Wie Sie sehen können, benötigen wir momentan nicht den Ausgabeblock, da der Chiffretext identisch mit der Ausgabe ist. Die CBC Entschlüsselung ist ähnlich, ausgenommen die Tatsache das wir auf die originale Dateigröße acht geben müssen, die wir aus der Eingabedatei wiederhergestellt haben und für den Fall das wir weniger als 16 Byte für die Entschlüsselung übrig haben, schreiben wir die übrigen Bytes anstatt die 16 Bytes, die wir von der Datei gelesen haben:

while ((read = fread(ciphertext, sizeof(uint8_t), 16, in)) > 0) {
    decipher(ciphertext, output, &context);
    for (i = 0; i < 16; i++) {
        plaintext[i] = ((firstRound) ? IV[i] : input[i]) ^ output[i];
    }
 
    firstRound = 0;
 
    if (originalFileSize < 16) {
        fwrite(plaintext, sizeof(uint8_t), originalFileSize, out);
    } else {
        fwrite(plaintext, sizeof(uint8_t), read, out);
        originalFileSize -= 16;
    }
 
    memcpy(input, ciphertext, 16 * sizeof(uint8_t));
}

Sie haben wahrscheinlich bemerkt das ich den Inhalt des Chiffretextes in den Eingabeblock kopiert habe. Das liegt daran das der Chiffretext während der nächsten Iteration durch den neuen Chiffretext Block überschrieben wird, aber wir brauchen nach wie vor den Chiffretext der vorherigen Iteration um die XOR-Verknüpfung durchzuführen. In diesem Fall, verwende ich nicht die Eingabe für den AES Blockchiffre, sondern für die XOR-Verknüpfung. Machen Sie sich keine Gedanken, falls Sie diesen Code nicht in ein funktionierendes Beispiel implementieren können, ich werden den vollständigen Code am Ende dieses Artikels nachliefern.

Implementierung: Cipher Feedback (CFB)

CFB besitzt zusammen mit dem Streamchiffre OFB und CTR zwei Vorteile gegenüber dem CBC Modus: der Blockchiffre wird nur in der Verschlüsselungsrichtung verwendet und die Nachricht muss nicht auf ein Vielfaches der Blockgröße gepaddet werden. Ich persönlich bin der Ansicht das der Modus relativ leicht zu implementieren ist. Deshalb zeige ich Ihnen ohne Umschweife den fertigen Code für die Verschlüsselung:

while ((read = fread(plaintext, sizeof(uint8_t), 16, in)) > 0) {
    if (firstRound) {
        cipher(IV, output, &context);
        firstRound = 0;
    } else {
        cipher(input, output, &context);
    }
 
    for (i = 0; i < 16; i++) {
        plaintext[i] = output[i] ^ ciphertext[i];
    }
 
    fwrite(plaintext, sizeof(uint8_t), read, out);
    memcpy(input, ciphertext, 16 * sizeof(uint8_t));
}

In diesem Fall, haben Sie vielleicht bemerkt das ich den Chiffretext sofort als Eingabe für die AES Verschlüsselung verwenden könnte. Allerdings habe ich das aufgrund der Namenskonvention als unlogisch angesehen, weshalb ich den Inhalt einfach vom Chiffretext in die Eingabe kopiert habe. Falls Sie die Geschwindigkeit verbessern möchten, können Sie das selbstverständlich ändern.
Die Entschlüsselung ist ähnlich zur Verschlüsselung (sie benutzt sogar die AES Verschlüsselung) und hier ist der Code:

while ((read = fread(ciphertext, sizeof(uint8_t), 16, in)) > 0) {
    if (firstRound) {
        cipher(IV, output, &context);
        firstRound = 0;
    } else {
        cipher(input, output, &context);
    }
 
    for (i = 0; i < 16; i++) {
        plaintext[i] = output[i] ^ ciphertext[i];
    }
 
    fwrite(plaintext, sizeof(uint8_t), read, out);
    memcpy(input, ciphertext, 16 * sizeof(uint8_t));
}

Implementierung: Output feedback (OFB)

Hier ist der Code für die Verschlüsselung:

while ((read = fread(plaintext, sizeof(uint8_t), 16, in)) > 0) {
    if (firstRound) {
        cipher(IV, output, &context);
        firstRound = 0;
    } else {
        cipher(input, output, &context);
    }
 
    for (i = 0; i < 16; i++) {
        ciphertext[i] = plaintext[i] ^ output[i];
    }
 
    fwrite(ciphertext, sizeof(uint8_t), read, out);
    memcpy(input, output, 16 * sizeof(uint8_t));
}

Der einzige Unterschied zum CFB-Modus ist das ich den Inhalt der Ausgabe in die Eingabe kopiert habe und nicht den Inhalt des Chiffretextes. Der folgende Code zeigt die Entschlüsselung:

while ((read = fread(ciphertext, sizeof(uint8_t), 16, in)) > 0) {
    if (firstRound) {
        cipher(IV, output, &context);
        firstRound = 0;
    } else {
        cipher(input, output, &context);
    }
 
    for (i = 0; i < 16; i++) {
        plaintext[i] = output[i] ^ ciphertext[i];
    }
    fwrite(plaintext, sizeof(uint8_t), read, out);
    memcpy(input, output, 16 * sizeof(uint8_t));
}

Alles zusammenfügen

Nun da wir alle drei Modi erklärt und implementiert haben, ist alles was noch zu tun ist, die Teile des Puzzles zu einem Gesamtbild zusammenzufügen.

Passwort

In der Informatik wird die Stärke von Passwörtern anhand der Entropie bewertet. Die Entropie ist ein Maß für den mittleren Informationsgehalt oder auch Informationsdichte eines Zeichensystems. Ein gutes Passwort muss demnach maximal unerwartet sein, um die hohe Sicherheit zu erreichen. Zufall und Entropie werden oft verwechselt und das ist bei der Wahl des Passwortes entscheidend. Eine zufällige Zahlenfolge ist per Definition nur eine Folge ohne ein auftretendes Muster. Diese Eigenschaft trifft unter anderem auch auf die Kreiszahl π zu. Dennoch ist die Kreiszahl kein geeignetes Passwort. Der Grund dafür ist ihre geringe Entropie. Die Kreiszahl ist deterministisch berechenbar und lässt sich jederzeit in Suchtabellen abgleichen. Die Entropie ist demzufolge ein Gradmaß für das überraschende Auftreten von Zeichen. Für einen Chinesen weisen deutsche Wörter eine hohe Entropie auf, weil ihm sowohl das Vokabular, als auch die Syntax der deutschen Sprache unbekannt sind. Man könnte auch vereinfacht davon sprechen, dass die Passwortstärke umso besser ist, je länger und für den Angreifer sinnloser das Passwort ist. Das Passwort "Papiereimer" ist nicht sicher, da es im deutschen Raum leicht durch Wörterbuchattacken geknackt werden kann. Dagegen erscheint uns "Karatasi ndoo" sicherer, obwohl es nur die Übersetzung von Papiereimer auf Swahili ist.

Da der AES nur Schlüsselgrößen von 128, 192 und 256 Bit verwerten kann, muss das Passwort in der Initialisierungsfunktion auf die richtige Größe gebracht werden. Passwörter über 256 Bit tragen demnach im AES nicht zur Sicherheit bei. Viel wichtiger ist das der 256 Bit lange Schlüssel so gewählt wird, dass er eine hohe Entropie aufweist. Die Passwortfunktion nimmt eine wichtige Rolle im gesamten Sicherheitskonzept ein. Die Anwendung muss darüber entscheiden was sie mit zu kurzen oder zu langen Passwörtern macht, die der Anwender eingibt. Hier gibt es viele unterschiedliche Ansätze, vom einfachen Auffüllen mit Nullen und Abschneiden zu langer Chiffreschlüssel bis hin zur Bildung von Hashwerten etc. Für den Initialisierungsvektor verwenden Programme, wie TrueCrypt, sogar Mauszeigerbewegungen zu Steigerung der Entropie.

In unserer Anwendung werden Passwörter mit der richtigen Länge vollständig übernommen, zu lange Passwörter werden abgeschnitten und zu kurze Passwörter mit einem festen Algorithmus aufgefüllt. Diesen Teil übernimmt die Initialisierungsfunktion. In der Funktion create wird die Struktur AESCONTEXT mit erzeugten Daten gefüllt. Zu diesen Daten gehören der Cipher Key, der Operationsmodi, die Schlüsselgröße und weitere notwendige Daten.

/// <summary>
/// Creates a new cipher instance, initializes AES context. The AES
/// context stores Nk, Nr, Nb, cipher mode, key size and reserves space
/// for the key and expanded cipher key. Call this function always first,
/// before using the AES algorithm.
/// </summary>
/// <param name="context">A reference to the context to fill</praram>
/// <param name="keySize">The AES key size (128, 192, 256)</praram>
/// <param name="mode">The cipher mode, e.g. OFB</praram>
/// <param name="key">A byte array with the key (password)</praram>
/// <param name="keyLength">The key length</praram>
/// <returns>A positive number or 0 if no error occurs</returns>
int32_t create(AESCONTEXT* context, KEYSIZE keySize, CIPHERMODE mode, const uint8_t* key, uint32_t keyLength)
{
    uint32_t i;
 
    // The expanded keySize
    uint32_t expandedKeySize;
 
    // Set the number of rounds, etc.
    switch (keySize) {
        case Bits128:
            context->Nk = 4;     // key size = 4 words = 16 bytes = 128 bits
            context->Nr = 10;
            break;
        case Bits192:
            context->Nk = 6;     // 6 words = 24 bytes = 192 bits
            context->Nr = 12;
            break;
        case Bits256:
            context->Nk = 8;     // 8 words = 32 bytes = 256 bits
            context->Nr = 14;
            break;
        default:
            return UNKNOWN_KEYSIZE;
            break;
    }
 
    // Reserve space for the cipher key 16, 24, 32 bytes
    if ((context->cipherKey = (uint8_t *)malloc(context->Nk * 4 * sizeof(uint8_t))) == NULL) {
        return MEMORY_ALLOCATION_PROBLEM;
    }
 
    // Check if the password has the right size
    if (keyLength == context->Nk * 4) {
        for(i = 0; i < context->Nk * 4; i++) {
            context->cipherKey[i] = key[i]; // Just copy the array
        }
    } else { 
        // Password is different size, do a manual copy
        for (i = 0; i < context->Nk * 4; i++) {
            // Make sure we can use the keyBytes
            if (i < keyLength) {
                context->cipherKey[i] = key[i];
            } else {
                // We need to add some extra bytes with a *fixed* algorithm
                context->cipherKey[i] = i ^ (context->Nk << 14) % 256;
            }
        }
    }
 
    // Set the key size and the cipher mode
    context->keySize = keySize;
    context->mode = mode;
 
    // Create the expanded key
    expandedKeySize = (16 * (context->Nr + 1));
 
    if ((context->expandedKey = (uint8_t *)malloc(expandedKeySize * sizeof(uint8_t))) == NULL) {
        return MEMORY_ALLOCATION_PROBLEM;
    }
 
    // Expand the key into an 176, 208, 240 bytes key
    expandKey(context->expandedKey, expandedKeySize, context->cipherKey, context->keySize);
 
    return 0;
}

Wie genau mit einem (unpassenden) Passwort verfahren wird, ist wie bereits erwähnt abhängig von der Implementierung. Wichtig ist nur, dass sowohl die Ver- als auch die Entschlüsselungsfunktion denselben Algorithmus verwenden. Werden bei der Verschlüsselung zu kurze Passwörter mit Nullen aufgefüllt, so muss natürlich auch bei der Entschlüsselung genauso vorgegangen werden, sonst würde der Cipher Key nicht mehr derselbe sein.

Ein bekanntes Standardverfahren zur Konvertierung von Passwörtern bzw. beliebiger Bit-Strings in einen kryptographischen Schlüssel, ist der PKCS#5 (engl. Password-based Encryption Standard) aus dem RSA. PKCS#5 ist mit der Version 2 seit mehr als 10 Jahren unverändert geblieben und wurde im RFC 2898 dokumentiert. PKCS#5 definiert eine generische Funktion, die PBKDF (engl. Password-Based Key Derivation Function), um einen kryptographischen Schlüssel aus einem Passwort zu generieren.

Key = PBKDF(salt, password, iteration count, size);

PBKDF nimmt einen Bit-String und einen Salt entgegen und wendet eine pseudozufällige Funktion darauf an, z.B. eine Hash-Funktion, um den Schlüssel passender Länge zu generieren.

Die Anzahl an angewendeten Hash-Funktionen wird mit der Iterationszahl angegeben. Mit dem Salt (dt. Salz) wird eine zufällig gewählte Zeichenfolge übergeben. Der Salt erschwert einen Angriff mit Regenbogentabellen, in denen vorberechnete Passwort-Schlüssel-Paare hinterlegt werden. Die Zahl der Iterationen erhöht die Rechenzeit beim Erraten des Passwortes. PKCS#5 empfielt mindestens 1.000 Iterationen. Zusätzlich eingefügte Operationen, die die Rechenzeit zu Gunsten der Sicherheit erhöhen, werden Spin genannt.

Dateien verschlüsseln

Relativ einfach gestaltet sich die Verschlüsselung von Dateien.

Die Verschlüsselung:

/// <summary>
/// Encrypts a file, filling the output file with the encrypted
/// bytes. Uses the password to encrypt. This function adds a byte header, 
/// storing the original file size and the cipher mode.
/// </summary>
/// <param name="in">File pointer of the plaintext file</praram>
/// <param name="out">File pointer of the encrypted output file</praram>
/// <param name="keySize">The AES key size (128, 192, 256)</praram>
/// <param name="mode">The cipher mode, e.g. OFB</praram>
/// <param name="password">A byte array with the password</praram>
/// <param name="passwordLength">The password length</praram>
/// <returns>A positive number if no error occurs</returns>
int32_t encryptFile(FILE* in, 
                    FILE* out, 
                    KEYSIZE keySize, 
                    CIPHERMODE mode, 
                    const uint8_t* password, 
                    uint32_t passwordLength)
{
    // The AES context
    AESCONTEXT context;
 
    // The AES input/output
    uint8_t plaintext[16] = { 0 };
    uint8_t input[16] = { 0 };
    uint8_t output[16] = { 0 };
    uint8_t ciphertext[16] = { 0 };
    uint8_t IV[16] = { 0 };
 
    // Char firstRound
    uint8_t firstRound = 1;
 
    uint32_t i, read, fileSize;
 
    // Check paramaters
    if(in == NULL || in == NULL || passwordLength < 1) {
        return INVALID_ARGUMENT;
    }
 
    // Create the AES context
    create(&context, keySize, mode, password, passwordLength);
 
    fseek(in, 0, SEEK_END);
    fileSize = ftell(in);
    fseek(in, 0, SEEK_SET);
 
    // Add the file header
    fwrite(&mode, sizeof(CIPHERMODE), 1, out);
    fwrite(&fileSize, sizeof(fileSize), 1, out);
 
    while ((read = fread(plaintext, sizeof(uint8_t), 16, in)) > 0) {
        if (mode == CFB) {
            if (firstRound) {
                cipher(IV, output, &context);
                firstRound = 0;
            } else {
                cipher(input, output, &context);
            }
 
            for (i = 0; i < 16; i++) {
                ciphertext[i] = plaintext[i] ^ output[i];
            }
 
            fwrite(ciphertext, sizeof(uint8_t), read, out);
            memcpy(input, ciphertext, 16 * sizeof(uint8_t));
        } else if (mode == OFB) {
            if (firstRound) {
                cipher(IV, output, &context);
                firstRound = 0;
            } else {
                cipher(input, output, &context);
            }
 
            for (i = 0; i < 16; i++) {
                ciphertext[i] = plaintext[i] ^ output[i];
            }
 
            fwrite(ciphertext, sizeof(uint8_t), read, out);
            memcpy(input, output, 16 * sizeof(uint8_t));
        } else if (mode == CBC) {
            // Padd with 0 bytes
            if (read < 16) {
                for (i = read; i < 16; i++) {
                    plaintext[i] = 0;
                }
            }
 
            for (i = 0; i < 16; i++) {
                input[i] = plaintext[i] ^ ((firstRound) ? IV[i] : ciphertext[i]);
            }
 
            firstRound = 0;
            cipher(input, ciphertext, &context);
 
            // Always 16 bytes because of the padding for CBC
            fwrite(ciphertext, sizeof(uint8_t), 16, out);
        }
    }
 
    // Shut down the AES instance
    shutdown(&context);
 
    return 1;
}

Sie können erkennen das hier der AESCONTEXT initialisiert wird, bevor mit der Verschlüsselung begonnen wird.

Die Entschlüsselung:

/// <summary>
/// Decrypts an encrypted file, filling the output file with the 
/// decrypted bytes. Uses the password to decrypt.
/// </summary>
/// <param name="in">File pointer of the enrypted file</praram>
/// <param name="out">File pointer of the decrypted output file</praram>
/// <param name="keySize">The AES key size (128, 192, 256)</praram>
/// <param name="password">A byte array with the password</praram>
/// <param name="passwordLength">The password length</praram>
/// <returns>A positive number if no error occurs</returns>
int32_t decryptFile(FILE* in, 
                    FILE* out, 
                    KEYSIZE keySize, 
                    const uint8_t* password, 
                    uint32_t passwordLength)
{
    // The AES context
    AESCONTEXT context;
 
    // The AES input/output
    uint8_t ciphertext[16] = { 0 };
    uint8_t input[16] = { 0 };
    uint8_t output[16] = { 0 };
    uint8_t plaintext[16] = { 0 };
    uint8_t IV[16] = { 0 };
 
    // Char firstRound
    char firstRound = 1;
 
    // The cipher mode will be extracted from the header
    CIPHERMODE mode;
 
    uint32_t i, read, originalFileSize = 0;
 
    // Check paramaters
    if(in == NULL || in == NULL || passwordLength < 1) {
        return INVALID_ARGUMENT;
    }
 
    fread(&mode, sizeof(CIPHERMODE), 1, in);
    fread(&originalFileSize, sizeof(originalFileSize), 1, in);
 
    // Create the AES context
    create(&context, keySize, mode, password, passwordLength);
 
    while ((read = fread(ciphertext, sizeof(uint8_t), 16, in)) > 0) {
        if (mode == CFB) {
            if (firstRound) {
                cipher(IV, output, &context);
                firstRound = 0;
            } else {
                cipher(input, output, &context);
            }
 
            for (i = 0; i < 16; i++) {
                plaintext[i] = output[i] ^ ciphertext[i];
            }
 
            fwrite(plaintext, sizeof(uint8_t), read, out);
            memcpy(input, ciphertext, 16 * sizeof(uint8_t));
        } else if (mode == OFB) {
            if (firstRound) {
                cipher(IV, output, &context);
                firstRound = 0;
            } else {
                cipher(input, output, &context);
            }
 
            for (i = 0; i < 16; i++) {
                plaintext[i] = output[i] ^ ciphertext[i];
            }
            fwrite(plaintext, sizeof(uint8_t), read, out);
            memcpy(input, output, 16 * sizeof(uint8_t));
        } else if(mode == CBC) {
            decipher(ciphertext, output, &context);
            for (i = 0; i < 16; i++) {
                plaintext[i] = ((firstRound) ? IV[i] : input[i]) ^ output[i];
            }
 
            firstRound = 0;
 
            if (originalFileSize < 16) {
                fwrite(plaintext, sizeof(uint8_t), originalFileSize, out);
            } else {
                fwrite(plaintext, sizeof(uint8_t), read, out);
                originalFileSize -= 16;
            }
 
            memcpy(input, ciphertext, 16 * sizeof(uint8_t));
        }
    }
 
    // Shut down the AES instance
    shutdown(&context);
 
    return 1;
}

Bytes verschlüsseln

Selbstverständlich lassen sich auch noch weitere Funktionen definieren. Der Rijndael-Algorithmus verschlüsselt immer nur Bytes. Deshalb ist es sinnvoll eine Methode zur Verfügung zu stellen, die genau nur das macht, also einen Bytestrom verschlüsseln. Die folgende, leicht abgeänderte, Funktion macht das.

/// <summary>
/// Encrypts a given byte array with the given password. The 
/// resulting encrypted buffer array will be automatically allocated by 
/// this function with the necessary size. It is up to the caller
/// to release the allocated memory later. This function adds a 8-byte
/// header, storing the original size and the cipher mode.
/// </summary>
/// <param name="inBuffer">The byte array to encrypt</praram>
/// <param name="inBufferLength">The length of the byte array</praram>
/// <param name="outBuffer">A pointer to a byte array, not reserved yet</praram>
/// <param name="IV">A 16-byte long initialization vector</praram>
/// <param name="keySize">The AES key size (128, 192, 256)</praram>
/// <param name="mode">The block cipher mode of operation, e.g. CBC</praram>
/// <param name="password">A byte array with the password</praram>
/// <param name="passwordLength">The password length in bytes</praram>
/// <returns>The positive number of encrypted bytes, if error occurs negative</returns>
/// <remarks>Always call this funtion with valid arguments.</remarks>
int32_t encryptBytes(const uint8_t* inBuffer, 
                     const uint32_t inBufferLength, 
                     uint8_t** outBuffer,
                     const uint8_t* IV,
                     KEYSIZE keySize, 
                     CIPHERMODE mode, 
                     const uint8_t* password, 
                     uint32_t passwordLength)
{
    // The AES context
    AESCONTEXT context;
 
    // The AES input/output
    uint8_t plaintext[16] = { 0 };
    uint8_t input[16] = { 0 };
    uint8_t output[16] = { 0 };
    uint8_t ciphertext[16] = { 0 };
 
    // Char firstRound
    char firstRound = 1;
 
    uint32_t i, position, outBufferLength, read;
    const uint32_t headerSize = sizeof(CIPHERMODE) + sizeof(inBufferLength);
 
    // Check paramaters
    if(inBufferLength < 16 || inBuffer == NULL || password == NULL || passwordLength < 1 || IV == NULL) {
        return INVALID_ARGUMENT;
    }
 
    // Compute output buffer length, since CBC buffer is padded
    if(mode == CBC && inBufferLength % 16 != 0) {
        outBufferLength = inBufferLength + 16 - inBufferLength % 16;
    } else {
        outBufferLength = inBufferLength;
    }
 
    // Add the header size to buffer size
    outBufferLength += headerSize;
 
    // Now reserve space for the buffer
    if (((*outBuffer) = (uint8_t *)malloc(outBufferLength * sizeof(uint8_t))) == NULL) {
        return MEMORY_ALLOCATION_PROBLEM;
    }
 
    // Create the AES context
    create(&context, keySize, mode, password, passwordLength);
 
    // Add a header with the cipher mode and the original buffer length
    memcpy((*outBuffer), &mode, sizeof(CIPHERMODE));
    memcpy((*outBuffer)  + sizeof(CIPHERMODE), &inBufferLength, sizeof(inBufferLength));
 
    // Start reading from input buffer and writing encrypted bytes to output buffer
    for(position = 0; position < inBufferLength; position += read) {
 
        // We were always reading 16 bytes. If there are less than 16 bytes left, read the rest
        read = (inBufferLength - position) >= 16 ? 16 : (inBufferLength % 16);
 
        // Copy plaintext bytes into plaintext
        memcpy(plaintext, inBuffer + position, read * sizeof(uint8_t));   
 
        if (mode == CFB) {
            if (firstRound) {
                cipher(IV, output, &context);
                firstRound = 0;
            } else {
                cipher(input, output, &context);
            }
 
            for (i = 0; i < 16; i++) {
                ciphertext[i] = plaintext[i] ^ output[i];
            }
 
            memcpy((*outBuffer) + headerSize + position, ciphertext, read * sizeof(uint8_t));
            memcpy(input, ciphertext, 16 * sizeof(uint8_t));
        } else if (mode == OFB) {
            if (firstRound) {
                cipher(IV, output, &context);
                firstRound = 0;
            } else {
                cipher(input, output, &context);
            }
 
            for (i = 0; i < 16; i++) {
                ciphertext[i] = plaintext[i] ^ output[i];
            }
 
            memcpy((*outBuffer) + headerSize + position, ciphertext, read * sizeof(uint8_t));
            memcpy(input, ciphertext, 16 * sizeof(uint8_t));
        } else if (mode == CBC) {
            // Padd with 0 bytes
            if (read < 16) {
                for (i = read; i < 16; i++) {
                    plaintext[i] = 0;
                }
            }
 
            for (i = 0; i < 16; i++) {
                input[i] = plaintext[i] ^ ((firstRound) ? IV[i] : ciphertext[i]);
            }
 
            firstRound = 0;
            cipher(input, ciphertext, &context);
 
            // Always copy 16 bytes, because of the padding for CBC
            memcpy((*outBuffer) + headerSize + position, ciphertext, 16 * sizeof(uint8_t));
        }
    }
 
    // Shut down the AES instance
    shutdown(&context);
 
    return outBufferLength;
}

Die zugehörige Entschlüsselungsfunktion können Sie gerne selbst entwerfen oder Sie laden sich in unserer Download-Rubrik den kompletten Quellcode mit dem implementierten AES samt Betriebsmodi herunter. Das zur Verfügung gestellte Programm erlaubt es über die Konsole Dateien zu verschlüsseln und demonstriert die Nutzung, der oben vorgestellten Funktionen in eigenen Programmen.

Testvektoren

Um die eigene Implementierung des AES mit den Operationsmodi auf Korrektheit überprüfen zu können, werden sogenannte Testvektoren (engl. Test Vectors) verwendet. Diese Testvektoren bestehen aus einem Satz fester Klar- und Geheimtexte, denen ein geheimer Schlüssel zugeordnet ist. Auf diese Weise lässt sich ein Klartext mit einem Schlüssel verschlüsseln und anschließend überprüfen, ob das Programm den korrespondierenden Geheimtext produziert. Für den AES wurden die nachfolgenden Testvektoren im Dokument "NIST Special Publication 800-38A" offiziell publiziert.

Liste von Testvektoren für den AES/ECB Operationsmodi

AES ECB 128-bit Verschlüsselung

Schlüssel: 2B7E151628AED2A6ABF7158809CF4F3C

Initialisierungsvektor: nicht erforderlich

Testvektor Geheimtext
6BC1BEE22E409F96E93D7E117393172A 3AD77BB40D7A3660A89ECAF32466EF97
AE2D8A571E03AC9C9EB76FAC45AF8E51 F5D3D58503B9699DE785895A96FDBAAF
30C81C46A35CE411E5FBC1191A0A52EF 43B1CD7F598ECE23881B00E3ED030688
F69F2445DF4F9B17AD2B417BE66C3710 7B0C785E27E8AD3F8223207104725DD4

 

AES ECB 192-bit Verschlüsselung

Schlüssel: 8E73B0F7DA0E6452C810F32B809079E562F8EAD2522C6B7B

Initialisierungsvektor: nicht erforderlich

Testvektor Geheimtext
6BC1BEE22E409F96E93D7E117393172A BD334F1D6E45F25FF712A214571FA5CC
AE2D8A571E03AC9C9EB76FAC45AF8E51 974104846D0AD3AD7734ECB3ECEE4EEF
30C81C46A35CE411E5FBC1191A0A52EF EF7AFD2270E2E60ADCE0BA2FACE6444E
F69F2445DF4F9B17AD2B417BE66C3710 9A4B41BA738D6C72FB16691603C18E0E

 

AES ECB 256-bit Verschlüsselung

Schlüssel: 603DEB1015CA71BE2B73AEF0857D77811F352C073B6108D72D9810A30914DFF4

Initialisierungsvektor: nicht erforderlich

Testvektor Geheimtext
6BC1BEE22E409F96E93D7E117393172A F3EED1BDB5D2A03C064B5A7E3DB181F8
AE2D8A571E03AC9C9EB76FAC45AF8E51 591CCB10D410ED26DC5BA74A31362870
30C81C46A35CE411E5FBC1191A0A52EF B6ED21B99CA6F4F9F153E7B1BEAFED1D
F69F2445DF4F9B17AD2B417BE66C3710 23304B7A39F9F3FF067D8D8F9E24ECC7

 

Liste von Testvektoren für den AES/CBC Operationsmodi

AES CBC 128-bit Verschlüsselung

Schlüssel: 2B7E151628AED2A6ABF7158809CF4F3C

Initialisierungsvektor
000102030405060708090A0B0C0D0E0F
7649ABAC8119B246CEE98E9B12E9197D
5086CB9B507219EE95DB113A917678B2
73BED6B8E3C1743B7116E69E22229516

 

Testvektor Geheimtext
6BC1BEE22E409F96E93D7E117393172A 7649ABAC8119B246CEE98E9B12E9197D
AE2D8A571E03AC9C9EB76FAC45AF8E51 5086CB9B507219EE95DB113A917678B2
30C81C46A35CE411E5FBC1191A0A52EF 73BED6B8E3C1743B7116E69E22229516
F69F2445DF4F9B17AD2B417BE66C3710 3FF1CAA1681FAC09120ECA307586E1A7

 

AES CBC 192-bit Verschlüsselung

Schlüssel: 8E73B0F7DA0E6452C810F32B809079E562F8EAD2522C6B7B

Initialisierungsvektor
000102030405060708090A0B0C0D0E0F
4F021DB243BC633D7178183A9FA071E8
B4D9ADA9AD7DEDF4E5E738763F69145A
571B242012FB7AE07FA9BAAC3DF102E0

 

Testvektor Geheimtext
6BC1BEE22E409F96E93D7E117393172A 4F021DB243BC633D7178183A9FA071E8
AE2D8A571E03AC9C9EB76FAC45AF8E51 B4D9ADA9AD7DEDF4E5E738763F69145A
30C81C46A35CE411E5FBC1191A0A52EF 571B242012FB7AE07FA9BAAC3DF102E0
F69F2445DF4F9B17AD2B417BE66C3710 08B0E27988598881D920A9E64F5615CD

 

AES CBC 256-bit Verschlüsselung

Schlüssel: 603DEB1015CA71BE2B73AEF0857D77811F352C073B6108D72D9810A30914DFF4

Initialisierungsvektor
000102030405060708090A0B0C0D0E0F
F58C4C04D6E5F1BA779EABFB5F7BFBD6
9CFC4E967EDB808D679F777BC6702C7D
39F23369A9D9BACFA530E26304231461

 

Testvektor Geheimtext
6BC1BEE22E409F96E93D7E117393172A F58C4C04D6E5F1BA779EABFB5F7BFBD6
AE2D8A571E03AC9C9EB76FAC45AF8E51 9CFC4E967EDB808D679F777BC6702C7D
30C81C46A35CE411E5FBC1191A0A52EF 39F23369A9D9BACFA530E26304231461
F69F2445DF4F9B17AD2B417BE66C3710 B2EB05E2C39BE9FCDA6C19078C6A9D1B

 

Liste von Testvektoren für den AES/CFB Operationsmodi

AES CFB 128-bit Verschlüsselung

Schlüssel: 2B7E151628AED2A6ABF7158809CF4F3C

Initialisierungsvektor
000102030405060708090A0B0C0D0E0F
3B3FD92EB72DAD20333449F8E83CFB4A
C8A64537A0B3A93FCDE3CDAD9F1CE58B
26751F67A3CBB140B1808CF187A4F4DF

 

Testvektor Geheimtext
6BC1BEE22E409F96E93D7E117393172A 3B3FD92EB72DAD20333449F8E83CFB4A
AE2D8A571E03AC9C9EB76FAC45AF8E51 C8A64537A0B3A93FCDE3CDAD9F1CE58B
30C81C46A35CE411E5FBC1191A0A52EF 26751F67A3CBB140B1808CF187A4F4DF
F69F2445DF4F9B17AD2B417BE66C3710 C04B05357C5D1C0EEAC4C66F9FF7F2E6

 

AES CFB 192-bit Verschlüsselung

Schlüssel: 8E73B0F7DA0E6452C810F32B809079E562F8EAD2522C6B7B

Initialisierungsvektor
000102030405060708090A0B0C0D0E0F
CDC80D6FDDF18CAB34C25909C99A4174
67CE7F7F81173621961A2B70171D3D7A
2E1E8A1DD59B88B1C8E60FED1EFAC4C9

 

Testvektor Geheimtext
6BC1BEE22E409F96E93D7E117393172A CDC80D6FDDF18CAB34C25909C99A4174
AE2D8A571E03AC9C9EB76FAC45AF8E51 67CE7F7F81173621961A2B70171D3D7A
30C81C46A35CE411E5FBC1191A0A52EF 2E1E8A1DD59B88B1C8E60FED1EFAC4C9
F69F2445DF4F9B17AD2B417BE66C3710 C05F9F9CA9834FA042AE8FBA584B09FF

 

AES CFB 256-bit Verschlüsselung

Schlüssel: 603DEB1015CA71BE2B73AEF0857D77811F352C073B6108D72D9810A30914DFF4

Initialisierungsvektor
000102030405060708090A0B0C0D0E0F
DC7E84BFDA79164B7ECD8486985D3860
39FFED143B28B1C832113C6331E5407B
DF10132415E54B92A13ED0A8267AE2F9

 

Testvektor Geheimtext
6BC1BEE22E409F96E93D7E117393172A DC7E84BFDA79164B7ECD8486985D3860
AE2D8A571E03AC9C9EB76FAC45AF8E51 39FFED143B28B1C832113C6331E5407B
30C81C46A35CE411E5FBC1191A0A52EF DF10132415E54B92A13ED0A8267AE2F9
F69F2445DF4F9B17AD2B417BE66C3710 75A385741AB9CEF82031623D55B1E471

 

Liste von Testvektoren für den AES/OFB Operationsmodi

AES OFB 128-bit Verschlüsselung

Schlüssel: 2B7E151628AED2A6ABF7158809CF4F3C

Initialisierungsvektor
000102030405060708090A0B0C0D0E0F
50FE67CC996D32B6DA0937E99BAFEC60
D9A4DADA0892239F6B8B3D7680E15674
A78819583F0308E7A6BF36B1386ABF23

 

Testvektor Geheimtext
6BC1BEE22E409F96E93D7E117393172A 3B3FD92EB72DAD20333449F8E83CFB4A
AE2D8A571E03AC9C9EB76FAC45AF8E51 7789508D16918F03F53C52DAC54ED825
30C81C46A35CE411E5FBC1191A0A52EF 9740051E9C5FECF64344F7A82260EDCC
F69F2445DF4F9B17AD2B417BE66C3710 304C6528F659C77866A510D9C1D6AE5E

 

AES OFB 192-bit Verschlüsselung

Schlüssel: 8E73B0F7DA0E6452C810F32B809079E562F8EAD2522C6B7B

Initialisierungsvektor
000102030405060708090A0B0C0D0E0F
A609B38DF3B1133DDDFF2718BA09565E
52EF01DA52602FE0975F78AC84BF8A50
BD5286AC63AABD7EB067AC54B553F71D

 

Testvektor Geheimtext
6BC1BEE22E409F96E93D7E117393172A CDC80D6FDDF18CAB34C25909C99A4174
AE2D8A571E03AC9C9EB76FAC45AF8E51 FCC28B8D4C63837C09E81700C1100401
30C81C46A35CE411E5FBC1191A0A52EF 8D9A9AEAC0F6596F559C6D4DAF59A5F2
F69F2445DF4F9B17AD2B417BE66C3710 6D9F200857CA6C3E9CAC524BD9ACC92A

 

AES OFB 256-bit Verschlüsselung

Schlüssel: 603DEB1015CA71BE2B73AEF0857D77811F352C073B6108D72D9810A30914DFF4

Initialisierungsvektor
000102030405060708090A0B0C0D0E0F
B7BF3A5DF43989DD97F0FA97EBCE2F4A
E1C656305ED1A7A6563805746FE03EDC
41635BE625B48AFC1666DD42A09D96E7

 

Testvektor Geheimtext
6BC1BEE22E409F96E93D7E117393172A DC7E84BFDA79164B7ECD8486985D3860
AE2D8A571E03AC9C9EB76FAC45AF8E51 4FEBDC6740D20B3AC88F6AD82A4FB08D
30C81C46A35CE411E5FBC1191A0A52EF 71AB47A086E86EEDF39D1C5BBA97C408
F69F2445DF4F9B17AD2B417BE66C3710 0126141D67F37BE8538F5A8BE740E484

 

Weitergehende Links

  • NIST Special Publication 800-38A: Recommendation for Block Cipher Modes of Operation, Methods and Techniques - NIST (National Insitute of Standards and Technology)
Zuletzt aktualisiert am Donnerstag, den 19. Mai 2011 um 09:02 Uhr
 
AUSWAHLMENÜ