27.2 Verschlüsselung – PyCrypto 

Das Drittanbieterpaket PyCrypto[ 113 ](https://pypi.python.org/pypi/pycrypto) implementiert eine Reihe von kryptografischen Algorithmen zur symmetrischen und asymmetrischen Verschlüsselung. Im letzten Abschnitt werden die asymmetrischen Verfahren auch zum Signieren von Dokumenten eingesetzt.
27.2.1 Symmetrische Verschlüsselungsverfahren 

Bei einem symmetrischen Verschlüsselungsverfahren wird ein Klartext mithilfe eines Schlüssels zu einem Chiffrat verschlüsselt. Derselbe Schlüssel kann danach verwendet werden, um das Chiffrat wieder zu einem Klartext zu entschlüsseln. Eine wichtige Bedeutung hat dabei der Raum der möglichen Schlüssel: Ist dieser groß genug und ist das Verschlüsselungsverfahren an sich sicher, steigt der Rechenaufwand zum Brechen der Verschlüsselung enorm.
Symmetrische Verschlüsselungsverfahren lassen sich in zwei Klassen unterteilen:
- Stromchiffren verschlüsseln einen Datenstrom Bit für Bit.
- Blockchiffren verschlüsseln einen Datenstrom in Blöcken einer bestimmten Blockgröße.
Stromchiffren
Stromchiffren nehmen eine bitweise Verschlüsselung eines kontinuierlich eingehenden Datenstroms vor. Im Gegensatz zu Blockchiffren muss keine Mindestmenge an Daten vorliegen, damit die Verschlüsselung funktioniert. Sie eignen sich daher besonders für die Echtzeitübertragung von Daten.
Eine Stromchiffre ist ein symmetrisches Verschlüsselungsverfahren und basiert daher auf einem Schlüssel. In der Regel wird der Datenstrom mit einem aus dem Schlüssel berechneten Schlüsselstrom bitweise verrechnet, beispielsweise über ein exklusives ODER (XOR).
Im Modul PyCrypto.Cipher sind zwei Typen von Stromchiffren implementiert: XOR und ARC4. Beide verfügen über die gleiche Schnittstelle und werden hier anhand von XOR exemplarisch vorgestellt:
>>> from Crypto.Cipher import XOR
>>> sender = XOR.new("Passwort")
>>> chiffrat = sender.encrypt("Das ist mein geheimer Text")
>>> chiffrat
b'\x14\x00\x00S\x1e\x1c\x06T=\x04\x1a\x1dW\x08\x17\x1c5\x08\x1e\x16\x05O&\x11(\x15'
Über die Methode new wird eine neue XOR-Chiffre mit dem angegebenen Schlüssel erzeugt. Diese lässt sich jetzt mithilfe der Methoden encrypt und decrypt zum Verschlüsseln bzw. Entschlüsseln von Daten verwenden. Die Gegenseite verfährt analog und entschlüsselt den Datenstrom mittels decrypt:
>>> empfaenger = XOR.new("Passwort")
>>> klartext = empfaenger.decrypt(chiffrat)
>>> klartext
b'Das ist mein geheimer Text'
Beachten Sie, dass eine Stromchiffre aufgrund des Schlüsselstroms einen internen Zustand hat. Ein weiterer Aufruf von XOR.encrypt auf den gleichen Eingabedaten würde daher ein unterschiedliches Chiffrat ergeben. Das bedeutet insbesondere, dass ein eingehender verschlüsselter Datenstrom von Beginn an entschlüsselt werden muss, damit die Schlüsselströme auf beiden Seiten synchron sind.
Es genügt, im obigen Beispiel die Klasse XOR durch ARC4 auszutauschen, um eine ARC4-Chiffre zu verwenden. Die Schlüsselgrößen sind bei beiden Verfahren variabel.
Blockchiffren
Im Gegensatz zu einer Stromchiffre verschlüsselt eine Blockchiffre einen Datenblock fester Größe. Auch das entstehende Chiffrat hat eine feste Größe. Um beliebig große Datenmengen zu verschlüsseln, gibt es verschiedene Verfahren, sogenannte kryptografische Betriebsmodi, die durch eine kombinierte Chiffrierung der einzelnen Datenblöcke eine sichere Gesamtchiffrierung erreichen. Zwei Beispiele für standardisierte Betriebsmodi sind:
- Electronic Code Book (ECB); die Datenblöcke werden unabhängig voneinander verschlüsselt.[ 114 ](Vorsicht, dieser Betriebsmodus führt dazu, dass gleiche Klartextblöcke auch gleiche Chiffratblöcke ergeben, was ein für einen Angreifer interessanter Ansatzpunkt ist. )
- Cipher Block Chaining (CBC); ein Datenblock wird vor der Verschlüsselung mit dem Chiffrat des vorangegangenen Datenblocks verrechnet.
Es existiert eine Reihe weiterer Betriebsmodi mit jeweils eigenen Besonderheiten, die wir an dieser Stelle nicht erschöpfend behandeln können.
Im Modul PyCrypto.Cipher sind die in Tabelle 27.2 aufgeführten Typen von Blockchiffren implementiert.
Name | Schlüsselgröße | Blockgröße |
---|---|---|
AES | 16, 24 oder 32 Byte | 16 Byte |
ARC2 | variabel | 8 Byte |
Blowfish | variabel | 8 Byte |
CAST | variabel | 8 Byte |
DES | 8 Byte | 8 Byte |
DES3 | 16 Byte | 8 Byte |
IDEA | 16 Byte | 8 Byte |
RC5 | variabel | 8 Byte |
Tabelle 27.2 Blockchiffren im Paket PyCrypto
Die in PyCrypto.Cipher enthaltenen Blockchiffren können alle gleich verwendet werden, weswegen wir sie Ihnen exemplarisch am Beispiel von AES vorstellen:
>>> from Crypto.Cipher import AES
>>> chiffre = AES.new("PasswortPasswort", AES.MODE_ECB)
>>> chiffrat = chiffre.encrypt("Python - das umfassende Handbuch")
>>> chiffrat
b"\xd1'\x86\xe3\xd0\xd0\x94V<\xfe\x14Z\x16\x85*\xc6\x16}\xe0\xbd\x04g\xbe\xb5 \xbc\xf2;\xf2\xe0\xcd\nn"
>>> chiffre.decrypt(chiffrat)
b'Python - das umfassende Handbuch'
Ähnlich wie bei den Stromchiffren wird eine Blockchiffre über die Methode new erzeugt. Dabei wird ein Schlüssel angegeben, der in diesem Fall eine Länge von 16 Zeichen haben muss, sowie der gewünschte kryptografische Betriebsmodus, in diesem Fall ECB. Die anschließende Ver- bzw. Entschlüsselung über die Methoden encrypt bzw. decrypt unterscheidet sich nicht von den Stromchiffren.
Beachten Sie, dass eine Blockchiffre im Gegensatz zu einer Stromchiffre keinen internen Zustand hat. Jede Eingabe wird als eigenständiger Datenblock neu verschlüsselt. Daher führen zwei aufeinanderfolgende Aufrufe von encrypt mit den gleichen Eingabedaten auch zum gleichen Chiffrat.
Für die kryptografischen Betriebsmodi CBC und CFB muss beim Erzeugen der Chiffre ein zusätzliches Argument für den Initialisierungsvektor übergeben werden. Diese Betriebsmodi beziehen bei der Verschlüsselung eines Datenblocks den vorangegangenen Datenblock mit ein und benötigen daher einen Initialisierungsvektor zur Verschlüsselung des ersten Datenblocks. Dabei handelt es sich um einen String von Blockgröße:
chiffre = AES.new("PasswortPasswort", AES.MODE_CBC, "ABCDEFGHabcdefgh")
27.2.2 Asymmetrische Verschlüsselungsverfahren 

Ein asymmetrisches Verschlüsselungsverfahren, auch Public-Key-Verschlüsselungsverfahren, umgeht die größte Schwachstelle symmetrischer Verfahren: den Schlüsselaustausch. Bei verschlüsselter Kommunikation müssen beide Kommunikationspartner den gleichen Schlüssel besitzen. Sollte ein Angreifer den Schlüssel beim Schlüsselaustausch[ 115 ](Auch dafür gibt es kryptografische Verfahren, beispielsweise das Diffie-Hellman-Protokoll. ) in Erfahrung bringen können, ist das Entschlüsseln der nachfolgenden Kommunikation sehr einfach.
Bei den erst in den 1970er-Jahren entwickelten asymmetrischen Verschlüsselungsverfahren gibt es keinen gemeinsamen geheimen Schlüssel, sondern jeder Kommunikationsteilnehmer besitzt einen privaten Schlüssel, der geheim gehalten wird, und einen dazu passenden öffentlichen Schlüssel, der allen Kommunikationspartnern – und damit auch allen potenziellen Angreifern – bekannt ist. Der Sender verschlüsselt seine Nachricht mit dem öffentlichen Schlüssel des Empfängers in einer Weise, dass sie nur mithilfe des privaten Schlüssels wieder entschlüsselt werden kann. Wichtig ist dabei, dass sich der private Schlüssel nicht aus dem öffentlichen Schlüssel berechnen lässt.
Ein Beispiel für ein asymmetrisches Verschlüsselungsverfahren ist das 1977 entwickelte RSA-Verfahren (für Rivest, Shamir und Adleman). Vereinfacht gesagt basiert RSA auf der Annahme, dass die Zerlegung einer sehr großen Zahl in ihre Primfaktoren eine kaum zu berechnende Operation ist, wobei die Betonung hier auf sehr groß liegt. Die umgekehrte Operation, nämlich das Erzeugen dieser sehr großen Zahl aus ihren Primfaktoren, ist hingegen simpel. Jeder Kommunikationsteilnehmer erzeugt sich zwei Primzahlen und hält diese geheim. Das Produkt dieser Zahlen ist der öffentliche Schlüssel und wird zusammen mit einer weiteren benötigten Zusatzinformation veröffentlicht. Der öffentliche Schlüssel reicht aus, um eine Nachricht zu verschlüsseln, wer sie aber wieder entschlüsseln möchte, benötigt die Primfaktorzerlegung des öffentlichen Schlüssels.
Obwohl die Sicherheit von RSA bis heute nicht bewiesen ist, erfreut sich das Verfahren großer Beliebtheit und wird in modifizierter Form vielfach eingesetzt.
Asymmetrische Verschlüsselungsverfahren können häufig nicht nur zum Verschlüsseln, sondern auch zum Signieren von Nachrichten angewandt werden. Die in PyCrypto.PublicKey enthaltenen asymmetrischen Verfahren werden in Tabelle 27.3 vorgestellt. Daran anschließend werden beide Anwendungsfälle anhand von RSA vorgestellt.
Name | Verschlüsseln | Signieren |
---|---|---|
RSA | ja | ja |
ElGamal | ja | ja |
DSA | nein | ja |
qNEW | nein | ja |
Tabelle 27.3 Asymmetrische Verschlüsselungsverfahren in PyCrypto
Einen Schlüssel erzeugen
Bevor ein asymmetrisches Verfahren verwendet werden kann, muss ein Schlüsselpaar erzeugt werden. Das geschieht über die Methode generate des eingebundenen Verfahrens:
>>> from Crypto.PublicKey import RSA
>>> key = RSA.generate(1024)
Das erzeugte Schlüsselobjekt key enthält zunächst sowohl den öffentlichen als auch den privaten Schlüssel. Dieser Umstand ist über die Methode has_private erkennbar. Die Methode publickey erzeugt ein Schlüsselobjekt, das nur den öffentlichen Schlüssel enthält:
>>> key.has_private()
True
>>> public_key = key.publickey()
>>> public_key.has_private()
False
Die Methode exportKey erlaubt es, die Schlüssel in Form eines Strings zu exportieren. Über die Methode RSA.importKey kann ein exportierter Schlüssel wieder geladen werden:
>>> key_txt = key.exportKey()
>>> key_txt
b'-----BEGIN RSA PRIVATE KEY----- […] -----END RSA PRIVATE KEY-----'
>>> key = RSA.importKey(key_txt)
Beim Exportieren des Schlüsselpaars key werden sowohl der öffentliche als auch der private Schlüssel exportiert. Wird nur der öffentliche Schlüssel public_key exportiert, ist der private Schlüssel nicht enthalten. Die exportierten Daten können also veröffentlicht werden:
>>> public_key_txt = public_key.exportKey()
>>> public_key_txt
b'-----BEGIN PUBLIC KEY----- […] -----END PUBLIC KEY-----'
>>> public_key = RSA.importKey(public_key_txt)
Verschlüsseln
Die Schlüsselobjekte key und public_key besitzen die Methoden encrypt und decrypt. Es ist aber nicht ratsam, diese direkt zu verwenden, da sie das unmodifizierte RSA-Verfahren durchführen, das sogenannte Textbook-RSA. Diese Variante wird allgemein als unsicher angesehen und sollte nicht verwendet werden. Stattdessen gibt es sichere Varianten von RSA, die auch in PyCrypto implementiert sind, darunter PKCS1_OAEP für die Verschlüsselung und PKCS1_v1_5 für das Signieren.[ 116 ](Diese kryptischen Namen stehen für die standardisierten Protokolle PKCS#1 OAEP bzw. PKCS#1 v1.5. )
Im folgenden Beispiel wird eine sichere RSA-Verschlüsselung mit den im vorangegangenen Abschnitt erzeugten Schlüsseln durchgeführt:
>>> from Crypto.Cipher import PKCS1_OAEP
>>> chiffre = PKCS1_OAEP.new(public_key)
>>> chiffrat = chiffre.encrypt(b"Dies ist der geheime Text")
Der Empfänger kann das Chiffrat mithilfe seines privaten Schlüssels entschlüsseln:
>>> chiffre = PKCS1_OAEP.new(key)
>>> chiffre.decrypt(chiffrat)
b'Dies ist der geheime Text'
Signieren
Viele asymmetrische Verschlüsselungsverfahren lassen sich zum Signieren von Dokumenten einsetzen. Dabei geht es darum zu beweisen, dass ein Dokument in der Form vorliegt, wie es vom Urheber erstellt wurde.
Im Falle einer RSA-basierten Signatur wird eine Entschlüsselungsoperation mit dem privaten Schlüssel des Autors auf einem Hash-Wert des Dokuments durchgeführt. Der resultierende Wert wird als Signatur gemeinsam mit dem Dokument versendet. Jeder Empfänger des Dokuments kann die Signatur mit dem öffentlichen Schlüssel des Autors »verschlüsseln« und erhält dann den ursprünglichen Hash-Wert des Dokuments. Damit kann er prüfen, ob die vorliegende Version des Dokuments mit der ursprünglichen Version übereinstimmt.
Im folgenden Beispiel wird ein Dokument mithilfe der im Vorhinein erzeugten Schlüssel signiert und verifiziert. Dabei bedienen wir uns der angesprochenen RSA-Variante PKCS1_v1_5.
>>> from Crypto.Signature import PKCS1_v1_5
>>> from Crypto.Hash import SHA
>>> nachricht = b"Dies ist mein Dokument"
>>> hash = SHA.new(nachricht)
>>> signatur = PKCS1_v1_5.new(key).sign(hash)
Die erzeugte Signatur kann jetzt mithilfe des öffentlichen Schlüssels – und mit dem Hash-Wert des Dokuments, den auch der Empfänger erzeugen kann – verifiziert werden:
>>> PKCS1_v1_5.new(public_key).verify(hash, signatur)
True
Im folgenden Beispiel wurde die Signatur verändert, was die Verifikation fehlschlagen lässt:
>>> PKCS1_v1_5.new(public_key).verify(hash, signatur[:-1])
0