Cryptography Engineering, Teil 2: AES auf PCs und Servern

Seite 2: Teilfunktionen, Konstruktor & Key-Expansion

Inhaltsverzeichnis

Mit diesen Vorüberlegungen ist es ein Leichtes, die Teilfunktionen von AES in der Klasse AesEncryption in aes.cpp zu implementieren. (Die Sourcen liegen im Unterverzeichnis src des Tarballs.) Die Methoden subBytes() und invSubBytes() nutzen die inline-Methoden subByte() beziehungsweise invSubByte(), welche die S-Boxen über die Klassenattribute sbox beziehungsweise sboxinv verwenden. Die S-Boxen sind damit als Table-Lookup implementiert, der schnellsten, aber speicherintensivsten Implementierungsvariante. Eine andere Variante soll im nächsten Teil dieser Artikelreihe vorgestellt werden.

Die Implementierung der Methoden shiftRows() und invShiftRows() ist reine Tipparbeit. Die entsprechenden Operationen sind detailliert in [4] beschrieben (PDF). Ebenso verhält es sich mit den Methoden mixColumns() und invMixColumns(). Die verwenden für die "Mixoperationen" über den vier Spalten eines state die Methoden mixColumn() und invMixColumn(). Für ihre Operationen benötigen sie jedoch die Multiplikation in GF(2^8). Eine effiziente Variante wurde bereits im ersten Artikel dieser Reihe vorgestellt. Der entsprechende Algorithmus aus Abbildung 2 ist in der Methode gmul() 1 zu 1 implementiert.

Die Multiplikation in GF(2^8) mit xtime().

Bleibt zu guter Letzt noch die Methode addRoundKey(), um den Rundenschlüssel zu state zu addieren. Die Methode ist in der Implementierung ebenfalls trivial – sie bildet die notwendigen XOR-Operationen schlicht über den vier 32-Bit-Words aus state und aus dem Rundenschlüssel ab.

Auf dieser Basis implementieren die Methoden encipherState() und decipherState() schließlich die AES-Funktionen Cipher() beziehungsweise InvCipher().

Die Klasse AesEncryption ist so angelegt, dass jede Instanzierung eines Objekts untrennbar mit einem AES-Schlüssel verbunden ist. Die Konstruktoren akzeptieren den AES-Schlüssel als String – entweder als Objekt der C++-Klasse string, oder als C-String als char-Array. Der AES-Schlüssel ist dabei als hexadezimaler Wert als alphanumerischer String in Big-Endian-Notation repräsentiert. Zudem lassen sich die Schlüssel als uint8_t- und uint32_t-Array übergeben. Bei den String-Varianten ermittelt der Konstruktor selbst die AES-Variante – 128, 192 oder 256 Bit – anhand der Länge des übergebenen Strings. Bei den Integer-Arrays ist die Größe des Arrays explizit mit zu übergeben. Anhand dieser Array-Größe schließt der Konstruktor ebenfalls auf die AES-Variante. Werden Strings mit falscher Länge oder falschen Array-Größen übergeben, wirft der Konstruktor die Ausnahme AesBadKeyException.

Anhand der ermittelten Schlüssellänge setzen die Konstruktoren zunächst die Objektattribute knum und rnum, die den Werten Nk und Nr der AES-Spezifikation entsprechen (siehe [4], Seite 7). Anschließend übertragen die Konstruktoren den übergebenen Schlüssel in die unteren Bytes des key-Arrays, das als Objektattribut realisiert ist. Bei den String-Varianten erfolgt das durch die Methode keyHex2Arr(), die zugleich die Konvertierung vom String in Integer-Werte vornimmt. Bei den Integer-Array-Varianten des Konstruktors genügt eine einfache Schleife zum Kopieren der Werte nach key. Im Anschluss übernimmt bei jeder Konstruktorvariante die Methode expandKey() die AES-Key-Expansion. Sie verfügt, im Gegensatz zum in Abbildung 3 gezeigten Ablauf, nur über die zweiten Schleife der Expansion. Die erste Schleife wurde – passend für den Schlüssel als String oder Integer-Array – bereits im Konstruktor ausgeführt.

Die AES-Key-Expansion

Ist ein Objekt fehlerfrei instanziiert, ist es fortan untrennbar mit dem betreffenden AES-Schlüssel verbunden. Es repräsentiert ein voll funktionsfähiges Kryptosystem, bestehend aus Schlüssel und Algorithmus.