Public-Key-Verfahren: RSA/Rabin Henning Heitkötter

Werbung
Westfälische Wilhelms-Universität Münster
Ausarbeitung
Public-Key-Verfahren: RSA/Rabin
im Rahmen des Seminars Multimedia
Henning Heitkötter
Themensteller: Prof. Dr. Herbert Kuchen
Betreuer: Christian Arndt
Institut für Wirtschaftsinformatik
Praktische Informatik in der Wirtschaft
Inhaltsverzeichnis
1 Einleitung
1
2 Grundlagen
2
2.1
Public-Key-Kryptographie . . . . . . . . . . . . . . . . . . . . . . . .
2.2
Anforderungen an die Sicherheit von Verschlüsselungsverfahren und
2.3
2
Angriffstypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3
Einführung des Beispiels . . . . . . . . . . . . . . . . . . . . . . . . .
5
3 RSA
6
3.1
Schlüsselerzeugung . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6
3.2
Ver- und Entschlüsselung . . . . . . . . . . . . . . . . . . . . . . . . .
7
3.3
Sicherheit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8
3.3.1
Bezug zum Faktorisierungsproblem . . . . . . . . . . . . . . .
8
3.3.2
Angriffe auf RSA . . . . . . . . . . . . . . . . . . . . . . . . . 10
3.4
Effizienz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
3.5
RSA in Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
4 Rabin
17
4.1
Einleitung und Erläuterung . . . . . . . . . . . . . . . . . . . . . . . 17
4.2
Sicherheit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
4.3
4.2.1
Bezug zum Faktorisierungsproblem . . . . . . . . . . . . . . . 19
4.2.2
Angriffe auf das Rabin-Verfahren . . . . . . . . . . . . . . . . 19
Effizienz und Beurteilung . . . . . . . . . . . . . . . . . . . . . . . . . 20
5 Zusammenfassung
20
A Mathematische Hintergründe und Beweise
22
A.1 Restklassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
A.2 Hilfssätze . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
A.2.1 Hilfssatz 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
A.2.2 Hilfssatz 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
A.3 Invertierung von Restklassen und der erweiterte euklidische Algorithmus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
A.4 Chinesischer Restsatz . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
A.5 Korrektheit von Ver- und Entschlüsselung in RSA . . . . . . . . . . . 24
A.6 Beweis der Korrektheit des Entschlüsselungsalgorithmus für das RabinVerfahren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
ii
B Quelltext zum Kapitel RSA in Java“
25
”
B.1 Klasse HybridCipher . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
B.2 Sonstige Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Literaturverzeichnis
36
iii
Kapitel 1: Einleitung
1
Einleitung
Die Kommunikation über öffentliche Computernetzwerke wie das Internet wird heute
von Privathaushalten und Unternehmen intensiv genutzt. Aktuelle Studien zeigen,
dass 79 % der deutschen Unternehmen das Internet nutzen [IKT07, Kap. 2.2], noch
größer ist dieser Anteil bei Privatpersonen. Ein bedeutender Teil dieser Kommunikation umfasst geschäftskritische oder sonstwie geheimhaltungsbedürftige Informationen, sodass sichere Verfahren zum Schutz dieser Informationen benötigt werden,
auch wenn sie über ein potenziell unsicheres Medium wie öffentliche Netzwerke ausgetauscht werden. Dieser Bedarf zeigt sich auch darin, dass 31 % der Unternehmen
verschlüsselte Datenübertragung nutzen [IKT07, Kap. 2.5].
Neben der Geheimhaltung der Informationen, die oftmals oberste Priorität hat,
bestehen weitere Sicherheitsanforderungen wie Integrität, Authentizität und Nichtabstreitbarkeit. Nach der Übertragung einer Nachricht soll feststellbar sein, ob es
sich tatsächlich um die Nachricht handelt, die abgeschickt wurde, und weiterhin,
ob der Sender der Nachricht wirklich die Person ist, die er vorgibt zu sein. Insbesondere hinsichtlich des Zustandekommens rechtlich abgesicherter Verträge soll der
Sender im Nachhinein nicht abstreiten können, dass er die entsprechende Nachricht
versandt hat.
Bis weit in die zweite Hälfte des 20. Jahrhunderts wurden ausschließlich Verfahren verwendet, die Nachrichten mit einem geheimen Schlüssel verschlüsselten,
den die Kommunikationspartner vereinbaren mussten. Der Austausch dieses geheim
zu haltenden Schlüssels (Private Key) vor Beginn der Kommmunikation stellt ein
großes Problem dar. Dieses Problem umgeht die Public-Key-Kryptographie, das
Thema dieser Arbeit. Definition und Prinzipien von Public-Key-Systemen im Allgemeinen erläutert Kapitel 2.1.
Die Betrachtung zweier Public-Key-Verfahren, RSA und Rabin, in Kapitel 3,
bzw. 4 bildet den Hauptteil der Arbeit. Jedes Verfahren wird insbesondere im Hinblick auf seine Sicherheit analysiert, in Kapitel 2.2 des Grundlagenteils werden hierzu
die entsprechenden Anforderungen definiert und wichtige Angriffstypen auf PublicKey-Verfahren eingeführt. In der Anwendung der Verfahren, erläutert in Kapitel 3.5
am Beispiel von RSA in Java, oder ihrer Implementierung ergibt sich oftmals die
Notwendigkeit, einen Kompromiss zwischen Sicherheit und Aufwand zu finden. Deshalb wird jeweils auch die Effizienz der Verfahren untersucht. Das letzte Kapitel fasst
die Ergebnisse vergleichend zusammen und gibt einen Ausblick.
1
Kapitel 2: Grundlagen
2
Grundlagen
2.1
Public-Key-Kryptographie
Private-Key-Verfahren sind symmetrisch in der Art, dass zum Entschlüsseln dieselbe
Information benötigt wird, die auch zum Verschlüsseln verwendet wurde, nämlich
der geheime Schlüssel. Ein Public-Key-Verfahren wird asymmetrisch genannt, weil
Sender und Empfänger nicht die gleiche Information benötigen. Der Sender verschlüsselt die Nachricht mit dem öffentlichen Schlüssel des Empfängers, dieser muss
seinen geheimen Schlüssel zur Entschlüsselung nutzen.
In Anlehnung an [Bu04, Kap 4.1] wird ein Public-Key-Kryptosystem hier definiert als ein Tupel (P, C, (Kprv , Kpub ) , E, D), dessen Elemente folgendermaßen bestimmt sind:
1. P ist eine Menge von Klartexten und heißt Klartextraum, C heißt analog
Chiffretextraum
2. Kprv ist die Menge der Private Keys, Kpub die der Public Keys
3. E : P × Kpub → C ist die von einem Public Key abhängige Verschlüsselungsfunktion.
4. D : C × Kprv → P ist die Entschlüsselungsfunktion.
5. Für jeden öffentlichen Schlüssel e ∈ Kpub gibt es einen privaten Schlüssel
d ∈ Kprv , sodass gilt: D(E(p, e), d) = p ∀p ∈ P. (e, d) heißt Schlüsselpaar.
Symmetrische und asymmetrische Verfahren unterscheiden sich im Wesentlichen im
letzten Punkt. Während d und e bei Private-Key-Kryptosystemen identisch oder
leicht auseinander berechenbar sind, gilt dies bei Public-Key-Kryptosystemen nicht.
Bei Verwendung eines Public-Key-Verfahrens müssen die Kommunikationspartner keinen geheimen Schlüssel im Voraus austauschen. Der Empfänger hinterlegt
seinen Public Key in einem frei zugänglichen Schlüsselverzeichnis, aus dem der Sender diesen lädt und zur Verschlüsselung verwendet. Aus dem entstandenen Chiffretext sollte die ursprüngliche Nachricht ohne Kenntnis des privaten Schlüssels nicht
mehr rekonstruierbar sein. Damit ist zwar der bei symmetrischen Verfahren notwendige problematische Austausch eines geheimen Schlüssels unnötig, stattdessen
muss aber die Echtheit des Public Key gewährleistet sein: der Sender muss sich sicher sein, dass der Schlüssel aus dem Verzeichnis tatsächlich dem Empfänger gehört.
Ansonsten kann ein böswilliger Angreifer versuchen, den öffentlichen Schlüssel eines
selbst generierten Schlüsselpaars unter dem Namen des Empfängers zu platzieren,
um in der Folge alle an diese Person versandten Nachrichten entschlüsseln zu können.
2
Kapitel 2: Grundlagen
Die Echtheit des öffentlichen Schlüssels sollen Public-Key-Infrastrukturen sicherstellen [Bu04, Kap. 17].
Da alle bekannten Public-Key-Verfahren deutlich aufwändiger als Private-KeyVerfahren sind, werden sie kaum zur Verschlüsselung vollständiger Nachrichten verwendet. Vielmehr kombiniert man in Hybridverfahren ihren Vorteil hinsichtlich des
Schlüsselaustauschs mit der Schnelligkeit und Sicherheit symmetrischer Verfahren.
Um eine Nachricht p zu versenden, verschlüsselt der Sender diese mit einem gängigen
Private-Key-Algorithmus unter Verwendung eines einmaligen Sitzungsschlüssels s
und erhält den Chiffretext c. Er verschlüsselt s mit dem Public Key des Empfängers
und sendet diesem das Ergebnis zusammen mit c. Mit seinem privaten Schlüssel
kann der Empfänger daraus den ihm unbekannten Sitzungsschlüssel s ermitteln und
c entschlüsseln.
Neben dem Einsatz als Verschlüsselungsverfahren, d.h. zur Geheimhaltung von
Informationen können viele Public-Key-Verfahren auch zur Signierung von Nachrichten verwendet werden, um Integrität, Authentizität und Nicht-Abstreitbarkeit
zu gewährleisten. Dazu muss die Reihenfolge der Anwendung von Ver- und Entschlüsselungsfunktion vertauschbar sein, d.h. für ein Schlüsselpaar (e, d) muss gelten:
p = E(D(p, d), e) = D(E(p, e), d) ∀p ∈ P. Außerdem wird eine kollisionsresistente
Einweg-Hashfunktion1 h benötigt. Der Sender signiert seine Nachricht n, indem er
den Hashwert h(n) dieser Nachricht mit seinem privaten Schlüssel d verschlüsselt.
Diese Signatur s = D(h(n), d) übermittelt er zusammen mit der Nachricht an den
Empfänger, der die Signatur verifiziert, indem er sie mit dem öffentlichen Schlüssel
e des Senders entschlüsselt und das Ergebnis x = E(s, e) mit h(n) vergleicht. Sind
beide Werte identisch, so kann der Empfänger sich sicher sein, dass die Nachricht
vom behaupteten Sender stammt und nicht verändert wurde [MOV96, S. 425]. Des
Weiteren kann der Sender nicht abstreiten, dass er die Nachricht verfasst hat, sofern
er nicht belegt, dass ein Angreifer Zugriff auf seinen privaten Schlüssel hatte.
2.2
Anforderungen an die Sicherheit von Verschlüsselungsverfahren und Angriffstypen
Die Sicherheit eines Public-Key-Verschlüsselungsverfahrens hängt zum Einen davon ab, wie schwierig es für einen Angreifer ist, den geheimen Schlüssel zu finden
(Schlüsselproblem), insbesondere da der öffentliche Schlüssel frei verfügbar ist. Unter
1
Eine Einweg-Hashfunktion bildet eine Nachricht beliebiger Länge auf einen Hashwert fester
Länge ab, wobei die Umkehrung praktisch unmöglich ist [Schn96, Kap. 18]. Eine Hashfunktion h
ist kollisionsresistent, wenn es praktisch unmöglich ist, ein Paar (x, x0 ), x 6= x0 zu finden, für dass
h(x) = h(x0 ) gilt [Bu04, S.192f.].
3
Kapitel 2: Grundlagen
Umständen lässt sich aber auch ohne den geheimen Schlüssel der Klartext zu einem
Chiffretext finden, sodass die Sicherheit zum Anderen auch von der Schwierigkeit
abhängt, zu einem Chiffretext den zugehörigen Klartext zu ermitteln (im Folgenden
Entzifferungsproblem).
Die Sicherheit eines Kryptosystems kann besser beurteilt werden, wenn diese
kryptographischen Probleme auf mathematische Probleme zurückgeführt werden
können. Die kryptographischen Probleme entstammen oftmals einem speziellen Forschungsgebiet, das wenig Aufmerksamkeit erlangt, und sind wenig erforscht. Die
Wahrscheinlichkeit überraschender Erkenntnisse, die die Sicherheit des Verfahrens
unterminieren, ist in solchen Gebieten besonders groß. Gelingt jedoch eine sogenannte Sicherheitsreduktion auf mathematische Probleme, die auf breiteres Interesse stoßen, besser erforscht sind und eine größere Gemeinschaft an Forschern beschäftigen,
sind solche Überraschungen weniger wahrscheinlich, wenn auch nicht ausgeschlossen. Auch in diesem Fall hofft man aber, dass wichtige Erkenntnisse bezüglich dieser
Probleme schnell bekannt werden und reagiert werden kann, während weitreichende
Fortschritte in der Lösung spezieller kryptographischer Probleme unter Umständen
im Geheimen von einzelnen Institutionen oder Personen erzielt werden können.
Im Rahmen der Sicherheitsreduktion auf ein Problem P ist zu zeigen, dass die
kryptographischen Probleme hinsichtlich ihrer theoretischen Schwierigkeit äquivalent
zu P sind [Bu04, Kap. 9.2.4]. Ein Problem A ist in diesem Sinn äquivalent zu einem
Problem B, wenn es einen effizienten Algorithmus RA gibt, der A mit Hilfe eines
Algorithmus RB zur Lösung von B löst, und die Umkehrung ebenfalls gilt.2 Dann
kann A nicht schwerer sein als B, da man es immer auf B zurückführen und auf diese
Weise lösen kann, und wegen der Gültigkeit der Umkehrung B nicht schwerer als A.
Die Sicherheitsreduktion sollte auf vermutlich schwere, d.h. nicht in Polynomialzeit lösbare Probleme erfolgen. Solange für die zugrunde liegenden Probleme kein
effizienter Algorithmus existiert, kann das Verfahren dann als sicher im Hinblick auf
Schlüssel- und Entzifferungsproblem gesehen werden.
Ein Problem, für das kein Algorithmus mit in der Länge der Eingabe polynomiellem Aufwand bekannt ist, ist die Faktorisierung natürlicher Zahlen3 : für eine
zusammengesetzte Zahl n der Länge k sind die Primfaktoren zu bestimmen. Hierbei
handelt es sich um ein mathematisches Problem von allgemeinem Interesse, sodass
es sich als Ziel einer Sicherheitsreduktion eignet. Die Sicherheit des RSA- und des
Rabinverfahrens beruht zu großen Teilen auf dem Faktorisierungsproblem.
2
Effizient heißt in diesem Fall, dass RA ohne Berücksichtigung des Aufrufs von RB maximal in
der gleichen Aufwandsklasse wie RB liegt, sodass sich der Aufwand zusammen in der Aufwandsklasse von RB befindet.
3
Für hypothetische Quantencomputer existiert ein solcher Algorithmus [Sh94].
4
Kapitel 2: Grundlagen
Bei der Beurteilung der Sicherheit eines Verschlüsselungsverfahrens ist des Weiteren seine Robustheit gegen bestimmte Typen von Angriffen zu betrachten, mit denen
ein Angreifer die Kommunikation zwischen Sender und Empfänger zu stören versucht. Bei einer Chosen-Plaintext-Attacke wird angenommen, dass ein aktiver Angreifer selbst gewählte Klartexte verschlüsseln lassen kann. Bei Public-Key-Verfahren
ist diese Attacke immer möglich, da der öffentliche Schlüssel der Teilnehmer allgemein verfügbar ist. Bei kleinem Klartextraum kann der Angreifer durch simples Ausprobieren ermitteln, welcher Klartext verschlüsselt wurde, indem er alle möglichen
Klartexte verschlüsselt und diese Chiffretexte mit dem zu entschlüsselnden Chiffretext vergleicht. Public-Key-Verfahren sollten wegen ihrer potentiellen Anfälligkeit
für Plaintext-Attacken immer randomisiert werden: in die Verschlüsselung sollte ein
Zufallselement einfließen, sodass derselbe Klartext bei mehrfacher Verschlüsselung
jedes Mal einen anderen Chiffretext ergibt und Chosen-Ciphertext-Attacken verhindert werden.
Ein weiterer aktiver Angriff ist die Chosen-Ciphertext-Attacke. Bei dieser Angriffsart ist der Angreifer in der Lage, selbst gewählte Chiffretexte entschlüsseln zu
lassen, obwohl er den geheimen Schlüssel nicht kennt, z.B. weil er sich temporär
Zugang zum Verschlüsselungssystem verschafft oder sich als Kollege des Opfers ausgibt.
Weitere Angriffstypen wie Ciphertext-Only und Known-Plaintext [Bu04, Kap.
4.3.1] sind im Folgenden nicht von Belang, im Rahmen der Sicherheitsanalyse ist aber
zu berücksichtigen, dass ein Angreifer statt der Entschlüsselung eines Chiffretextes
auch das Ziel haben kann, den Chiffretext in seinem Sinn zu verändern.
2.3
Einführung des Beispiels
Dieser Abschnitt führt kurz in das im weiteren Verlauf der Arbeit verwendete Beispiel ein.
Alice will als Antwort auf eine Frage von Bob eine Nachricht mit dem Inhalt
Ja“ an ihn schicken. Da die vorgestellten Verfahren auf natürlichen Zahlen arbeiten,
”
muss sie die Textnachricht umwandeln. Zu diesem Zweck einigen sich Alice und Bob
auf folgendes Kodierungsverfahren: Alice hängt die ASCII-Werte der Zeichen als
Hexadezimalzahl aneinander und erhält für das Beispiel 4A6116 = 1904110 als zu
verschlüsselnde Nachricht. Nachdem Bob die Nachricht von Alice entschlüsselt hat,
kann er sie dekodieren, indem er die natürliche Zahl als Hexadezimalzahl schreibt
und die Byteblöcke anhand des ASCII-Codes wieder in Zeichen umwandelt.
5
Kapitel 3: RSA
3
RSA
Das RSA-Verfahren wurde 1977 von seinen Erfindern Ron Rivest, Adi Shamir und
Len Adleman vorgestellt [RSA77] und war eines der ersten Public-Key-Verfahren. Es
ist bis heute eines der wichtigsten Verschlüsselungsverfahren nach dem Public-KeyPrinzip [Schn96, S.466 f.], da es vergleichsweise einfach verständlich ist und keine
allgemeingültige Schwachstelle, mit der das Verfahren bei korrekter Implementierung
effizient gebrochen werden könnte, bekannt ist [Bo99, Kap. 6].
3.1
Schlüsselerzeugung
Will eine Person es ihren Kommunikationspartnern ermöglichen, RSA-verschlüsselte
Nachrichten an sie zu senden, muss sie zunächst ihr RSA-Schlüsselpaar generieren.
Die Schlüsselerzeugung im Rahmen des RSA-Verfahrens läuft dabei wie folgt ab:
1. Mit einem geeigneten Verfahren [Kn98, S. 405] erzeugt man zufällig zwei Primzahlen p und q, p 6= q und berechnet ihr Produkt n = pq. n heißt RSA-Modul.
2. Mit den beiden Primzahlen kann man den im Folgenden benötigten Wert der
Eulerschen ϕ-Funktion für n berechnen: ϕ(n) = (p − 1)(q − 1).4
3. Man wählt eine Zahl e ∈ N mit 1 < e < ϕ(n), die zu ϕ(n) teilerfremd ist,
d.h. gcd(e, ϕ(n)) = 1. Dieser sogenannte Verschlüsselungsexponent e bildet
zusammen mit dem RSA-Modul den öffentlichen Schlüssel (n, e).
4. Das RSA-Schlüsselpaar wird komplettiert durch den privaten Schlüssel d. d
heißt auch Entschlüsselungsexponent und muss so gewählt werden, dass 1 <
d < ϕ(n) und insbesondere ed ≡ 1 mod ϕ(n) gilt.5
Den Public Key bestehend aus dem RSA-Modul und dem Verschlüsselungsexponenten veröffentlich man in einem Schlüsselverzeichnis, während der Private Key
sicher zu speichern ist, zum Beispiel auf einer Chipkarte. In der Grundform besteht
der private Schlüssel nur aus dem Entschlüsselungsexponenten, der es in Kombination mit den Elementen des öffentlichen Schlüssels ermöglicht, Nachrichten zu
entschlüsseln. Zur Beschleunigung der Entschlüsselung kann der private Schlüssel
weitere Elemente wie p, q und ϕ(n) enthalten, die aber aber keinesfalls veröffentlicht
werden dürfen, da ein Angreifer aus diesen den privaten Schlüssel ermitteln kann.
4
Die Eulersche ϕ-Funktion gibt für ein m ∈ N die Anzahl der zu m teilerfremden natürlichen
Zahlen an, die kleiner als m sind. In diesem Fall handelt es sich um alle Zahlen i, 1 ≤ i < n, die
kein Vielfaches von p oder q sind.
5
Wegen gcd(e, ϕ(n)) = 1 ist die Restklasse e + ϕ(n)Z invertierbar. Das Inverse wird mit dem
erweiterten euklidischen Algorithmus berechnet (siehe Anhang A.3).
6
Kapitel 3: RSA
Die Wahl der Schlüsselparameter beeinflusst die Sicherheit des RSA-Verfahrens,
aber auch die Effizienz, mit der Operationen im Kryptosystem durchgeführt werden. Ebenfalls entscheidend ist die Verwendung eines sicheren Zufallszahlengenerators, damit die benötigten Zufallszahlen tatsächlich hinreichend zufällig sind und
ein Angreifer die Primzahlen nicht erraten kann.
Im Beispiel will Bob einen 16-Bit-Schlüssel erzeugen und wählt im ersten Schritt
die beiden 8-Bit langen Primzahlen p = 199 und q = 233. Der RSA-Modul ist also
n = 199 · 233 = 46367, außerdem ergibt sich ϕ(46367) = 198 · 232 = 45936. Als Verschlüsselungsexponent wählt Bob e = 17, weshalb sich als Entschlüsselungsexponent
d = 21617 ergibt. Bob publiziert seinen öffentlichen Schlüssel (n, e) = (46367, 17)
und sichert den geheimen Schlüssel d an geeigneter Stelle.
3.2
Ver- und Entschlüsselung
Grundsätzlich lassen sich mit dem RSA-Verfahren natürliche Zahlen m verschlüsseln,
die kleiner als der RSA-Modul n sind. Gegeben ein m, 0 ≤ m < n ergibt sich der
Chiffretext c, 0 ≤ c < n als c = me mod n. Da n und e Teil des öffentlichen
Schlüssels sind, kann jeder diese Operation durchführen und eine verschlüsselte
Nachricht an den Schlüsselinhaber senden.
Wegen der Beschränkung auf natürliche Zahlen kleiner als der RSA-Modul muss
eine Nachricht nach einem festzulegenden Verfahren in eine Zahl m, 0 ≤ m < n
umgewandelt werden, was ihre Größe beschränkt. Ist der RSA-Modul 1024 Bit lang,
stehen nur 128 Byte zur Verfügung, gegebenenfalls wird für die Randomisierung weiterer Platz benötigt, der nicht für die Nachricht zur Verfügung steht.6 Da das RSAVerfahren wie Public-Key-Verfahren im Allgemeinen aber in hybriden Verfahren
zum Schlüsselaustausch verwendet wird, handelt es sich bei der zu verschlüsselnden
Nachricht nur um den symmetrischen Schlüssel. Die aktuellen symmetrischen Kryptosysteme verwenden Schlüssel mit Längen deutlich kleiner als 1024 Bit7 , sodass die
Beschränkung der Nachrichtenlänge die Verwendung von RSA in Hybridverfahren
nicht beeinträchtigt.
Ein Verfahren, beliebig lange Nachrichten mit RSA zu verschlüsseln, wäre die
Aufteilung der Nachricht in Blöcke entsprechender Größe, die einzeln in eine Zahl
umgewandelt und verschlüsselt werden. Da RSA aber etwa 100-mal langsamer als
symmetrische Verfahren wie DES ist [Schn96, S. 469], ist dieses Vorgehen nicht
effizient.
6
7
Der PCKS #1-Standard benötigt zum Beispiel 11 Byte [PKCS1, S. 23].
Die Länge beträgt zum Beispiel 192 Bit bei 3DES oder bis zu 256 Bit bei AES.
7
Kapitel 3: RSA
Erhält ein Empfänger einen Chiffretext c, 0 ≤ c < n, der mit seinem öffentlichen
Schlüssel erzeugt wurde, kann er daraus unter Verwendung seines privaten Schlüssels
d die ursprüngliche Nachricht entschlüsseln. Der Klartext m ergibt sich als m = cd
mod n, weil aus den bei der Schlüsselerzeugung zu gewährleistenden Eigenschaften
von n, e und d für alle Nachrichten m, 0 ≤ m < n folgt: (me )d ≡ m mod n (Beweis
in Anhang A.5). Der Klartext ist wiederum eine natürliche Zahl, die ursprüngliche
Nachricht erhält der Empfänger, indem er die Transformation, die der Sender verwendet hat, um seine Nachricht in eine Zahl umzuwandeln, rückgängig macht.
Das RSA-Verfahren ist tatsächlich ein Kryptosystem wie in Kapitel 2.1 definiert: Der Klartext- und Chiffretextraum ist P = C = {0, 1, . . . , n − 1}. Für
die Menge der Private Keys Kprv gilt Kprv ⊂ N × N,8 für die der Public Keys
Kpub ⊂ N × N. Die Verschlüsselungsfunktion ist E(m, (e, n)) = me mod n und die
Entschlüsselungsfunktions D(m, (d, n)) = md mod n. Außerdem ist gezeigt, dass
für jeden öffentlichen Schlüssel (n, e) ∈ Kpub ein privater Schlüssel (n, d) ∈ Kprv
existiert, sodass für alle Nachrichten m ∈ P gilt: D(E(m, (n, e)), (n, d)) = (me )d
mod n = m.
Nachdem Bob seinen öffentlichen Schlüssel bekannt gemacht hat, kann Alice nun
verschlüsselt mit ihm kommunizieren. Um ihre Nachricht mit dem Inhalt Ja“ (m =
”
19041) an Bob zu übermitteln, besorgt sich Alice den öffentlichen Schlüssel (n, e) =
(46367, 17) von Bob und berechnet den Chiffretext c = 1904117 mod 46367 =
20236. Sie wandelt den Chiffretext in eine sendefähige Form um und verschickt
ihn über den unsicheren Kommunikationskanal an Bob. Dieser berechnet aus dem
Chiffretext und seinem privaten Schlüssel d = 21617 dann die ursprüngliche Nachricht als m = 2023621617 mod 46367 = 19041. Er wandelt die Zahl wieder in eine
Zeichenkette um und kann die Nachricht von Alice lesen.
3.3
3.3.1
Sicherheit
Bezug zum Faktorisierungsproblem
Für das RSA-Verschlüsselungsverfahren kann man zeigen, dass das Schlüsselproblem
äquivalent zum Faktorisierungsproblem ist. Das Entzifferungsproblem konnte aber
nicht auf das Faktorisierungsproblem oder ein anderes als schwer angenommenes
mathematisches Problem von allgemeinem Interesse zurückgeführt werden.
Das Schlüsselproblem ist nicht schwerer als das Faktorisierungsproblem, da ein
Angreifer nach der Faktorisierung des RSA-Moduls n in seine Primfaktoren p und q
8
Um der Definition eines Public-Key-Kryptosystems aus Kapitel 2.1 zu genügen, ist hier der
RSA-Modul zusätzlich zum Entschlüsselungsexponenten Teil des Private Key.
8
Kapitel 3: RSA
den öffentlich bekannten Verschlüsselungsexponenten e analog zur Schlüsselerzeugung
invertieren kann. Für die Äquivalenz von Schlüssel- und Faktorisierungsproblem
bleibt nach Kapitel 2.2 zu zeigen, dass der RSA-Modul effizient faktorisiert werden
kann, wenn man den geheimen Schlüssel ermittelt hat. Es existiert ein probabilistischer polynomieller Algorithmus, der n in jeder Iteration mit Wahrscheinlichkeit
größer
1
2
faktorisiert, wenn außerdem e und d bekannt sind [Bu04, Kap. 9.3.4]. Da
der Algorithmus deshalb im Mittel höchstens zwei Iterationen benötigt und somit
insgesamt polynomielle Laufzeit hat, ist das Faktorisierungsproblem nicht schwerer
als das Schlüsselproblem. Insgesamt folgt also, dass die beiden Probleme äquivalent
sind und das Schlüsselproblem auf das Faktorisierungsproblem sicherheitsreduziert
werden kann.
Das zweite allgemeine kryptoanalytische Problem, die Bestimmung des Klartexts
zu einem Chiffretext, entspricht für RSA dem sogenannten RSA-Problem: finde zu
gegebenem c und bekanntem öffentlichen Schlüssel (n, e) ein m, sodass c = me
mod n gilt. Anders ausgedrückt geht es um das Ziehen der e-ten Wurzel aus c + nZ
im Restklassenring Z/nZ. Da nach Lösen des Schlüsselproblems das RSA-Problem
effizient zu lösen ist, indem einfach die Entschlüsselungsfunktion verwendet wird, ist
das RSA-Problem nicht schwerer als das Schlüsselproblem und aufgrund der oben
gezeigten Äquivalenz auch nicht schwerer als das Faktorisierungsproblem. Anders als
beim Schlüsselproblem konnte die Umkehrung bisher nicht bewiesen werden, sodass
das RSA-Problems nicht auf das Faktorisierungsproblem sicherheitsreduziert werden
kann. Nach vorherrschender Meinung ist das RSA-Problem aber am einfachsten zu
lösen, indem der RSA-Modul faktorisiert wird [MOV96, S. 99].
Beide Probleme hängen bei RSA also mit dem Faktorisierungsproblem zusammen, auch wenn das RSA-Problem nicht äquivalent zu diesem ist. Unter der Voraussetzung, dass n nicht effizient faktorisierbar ist, kann RSA derzeit als praktisch
sicher angesehen werden. Zu beachten ist aber, dass die theoretische Sicherheitsbetrachtung das nicht beweisen kann.
Für die Verwendung von RSA haben die obigen Ergebnisse einige Implikationen
hinsichtlich der Schlüsselgenerierung. RSA hat die hilfreiche Eigenschaft, dass die
Sicherheit des Verfahrens mit zunehmender Länge des RSA-Moduls steigt. Solange
kein Faktorisierungsalgorithmus bekannt ist, der Zahlen beliebiger Größe effizient
faktorisiert, kann man RSA sicherer machen, indem man die Bitlänge erhöht. Heute
wird eine Bitlänge von mindestens 1024 Bit, für zukunftssichere Verwendung 2048
Bit empfohlen [LV01, Kap. 5]. Die benötigte Schlüssellänge hängt auch davon ab,
welchen Wert die verschlüsselten Informationen haben und wie lange sie geheim
gehalten werden sollen.
9
Kapitel 3: RSA
Die Primfaktoren des RSA-Moduls n, p und q, sollten möglichst gleich groß sein,
um die Faktorisierung zu erschweren [Bu04, Kap. 9.3.6]. Will Bob einen Schlüssel
der Länge 1024 Bit erzeugen, so sollte er die beiden Primzahlen im ersten Schritt
mit einer Länge von jeweils 512 Bit wählen.
3.3.2
Angriffe auf RSA
Neben der Sicherheit bezüglich dieser allgemeinen Kryptographieprobleme, die bei
RSA wie gezeigt als derzeit gegeben angenommen wird, ist die Sicherheit auch im
Hinblick auf die Parameterwahl und Schwachstellen in bestimmten Situationen zu
überprüfen. Die Auswirkungen der Bitlänge des RSA-Moduls und der Wahl der
Primzahlen wurde schon im vorherigen Abschnitt betrachtet, im Folgenden soll
zunächst der Einfluss des Ver- und Entschlüsselungsexponenten auf die Sicherheit
betrachtet werden.
Wird der Verschlüsselungsexponent e zu klein gewählt, ist unter Umständen die
sogenannte Low-Exponent-Attacke möglich, mit der eine an verschiedene Empfänger
verschlüsselt versandte Nachricht entschlüsselt werden kann, ohne den geheimen
Schlüssel eines Empfängers zu kennen. Werde dieselbe Nachricht m an mehrere
Empfänger RSA-verschlüsselt versandt, z.B. weil ein Unternehmen allen Kunden
oder allen Mitarbeitern eine bedeutende Nachricht zukommen lässt. Von den daraus
resultierenden Chiffretexten ci habe ein Angreifer e Stück abgefangen, wobei der
öffentlichen Schlüssel jedes Empfängers den gleichen Verschlüsselungsexponenten e
bei unterschiedlichem RSA-Modul ni enthält.9 Der Angreifer kann dann aus diesen
Chiffretexten ci = me mod ni , 1 ≤ i ≤ e die ursprüngliche Nachricht effizient mit
dem chinesischen Restsatz ermitteln [Bu04, Kap. 9.3.7].
Da der Angreifer e Chiffretexte zur gleichen Nachricht abfangen muss, ist die
Attacke nur durchführbar für kleine e. Wählt man e genügend groß, kann man die
Attacke verhindern. Alternativ könnte man e im Rahmen der Schlüsselerzeugung
zufällig wählen und so die Wahrscheinlichkeit, dass mehrere Empfänger den gleichen Verschlüsselungsexponenten haben, verringern. Aus Vereinfachungs- und Effizienzgründen verwenden viele RSA-Implementierungen aber immer den gleichen
Exponenten [Schn96, S. 469]. Man kann des Weiteren vermeiden, dass bei gleichem
Nachrichtentext tatsächlich auch immer dieselbe Nachricht verschlüsselt wird, indem man RSA randomisiert. Sind einige Bits der Nachricht zufällig und demzufolge
bei jedem Empfänger anders, läuft die Low-Exponent-Attacke ins Leere.
9
Obwohl jeder Empfänger einen eigenen Schlüssel hat, kann das auftreten, weil ein Programm
zur Schlüsselerzeugung den frei wählbaren Exponenten e zumeist mit der gleichen Zahl belegt.
10
Kapitel 3: RSA
Eine in bestimmten Anwendungen zu beachtende Schwachstelle des RSA-Verfahrens ist die Multiplikativität der Verschlüsselungsfunktion.10 Werden zwei Nachrichten m1 und m2 RSA-verschlüsselt an den gleichen Empfänger, dessen öffentlicher
Schlüssel (n, e) sei, versandt und fängt ein Angreifer die resultierenden Chiffretexte
c1 = me1 mod n und c2 = me2 mod n ab, so kann er die Nachricht m = m1 m2
verschlüsselt an den Empfänger der ursprünglichen Nachrichten senden, ohne die
Klartexte m1 und m2 zu kennen. Dazu sendet er den Chiffretext c = c1 c2 mod n.
Aufgrund der Multiplikativität gilt c = c1 c2 mod n = (m1 m2 )e mod n = me
mod n. Insbesondere bei der Verwendung von RSA in Signaturverfahren ergeben
sich dadurch Betrugsmöglichkeiten. Allgemein kann man den Klartexten eine spezielle Struktur geben, die bei Multiplikation zweier Nachrichten nicht erhalten bleibt,
um die Ausnutzung dieser Schwachstelle zu verhindern. Einen Klartext, der nicht der
Struktur entspricht, kann der Empfänger direkt als Fälschung identifizieren. In einem Signaturverfahren wie in Kapitel 2.1 verhindert die Verwendung des Hashwerts,
dass die Multiplikativität ausgenutzt werden kann [Bu04, Kap. 13.3.6].
3.4
Effizienz
Die Effizienz des RSA-Verfahrens beim Ver- und Entschlüsseln hängt entscheidend
von der Wahl der Parameter n, e und d während der Schlüsselerzeugung ab. Da
kryptographische Operationen in Public-Key-Systemen aus Gründen der Schlüsselgeheimhaltung oftmals auf Chipkarten und deshalb langsam durchgeführt werden,
spielt die Effizienz eine besondere Rolle.
Wie gezeigt nimmt die Sicherheit von RSA mit der Bitlänge k = dlog2 ne des
RSA-Moduls zu, allerdings geht mit einem größeren Modul ein höherer Aufwand
für alle Rechenoperationen einher, da die Zahlen entsprechend größer werden11 , beispielsweise liegt der Aufwand einer Multiplikation modulo n nach Schulmethode12
in O(k 2 ) = O(log2 n) [Bu04, S. 32].
Ver- und Entschlüsselung erfordern jeweils eine Exponentiation modulo n, d.h.
eine Operation der Form xa mod n, wobei im schlechtesten Fall a ∈ Θ(n) gilt. Der
Aufwand einer naiven Implementierung, bei der die Basis (a − 1)-mal mit sich selbst
multipliziert und modulo n genommen wird, liegt in O(ak 2 ) ⊂ O(n log2 n). Das
Verfahren der binären Exponentiation [Bu04, Kap 3.12] benötigt bei einer Bitlänge
10
Die Verschlüsselungsfunktion E ist multiplikativ, da für alle m1 , m2 ∈ {0, . . . , n − 1} gilt:
E(m1 m2 ) ≡ E(m1 )E(m2 ) mod n
11
Wegen k ≥ 1024 können keine Standard-Datentypen fixer Länge verwendet werden, sodass der
Aufwand tatsächlich von der Bitlänge abhängt.
12
Der Multiplikand wird nacheinander mit jeder Ziffer des Multiplikators multipliziert. Diese
Teilergebnisse werden in entsprechender Stelligkeit aufaddiert.
11
Kapitel 3: RSA
l = dlog2 ae des Exponenten nur l Quadrierungen und maximal l weitere Multiplikationen [Kn98, Kap. 4.6.3] und liegt daher im schlechtesten Fall l ≈ k in
O(k 3 ) = O(log3 n). Auch wenn die binäre Exponentiation die Operationen deutlich
beschleunigt, sind sie weiterhin abhängig von der Größe der Exponenten und damit
auch abhängig von der Wahl des Ver- und Entschlüsselungsexponenten.
Je kleiner der Verschlüsselungsexponent e ist, desto geringer ist der Aufwand für
die Verschlüsselung, umso größer wird aber die Gefahr von Low-Exponent-Attacken.
Um auch im Fall von Anwendungen, die keine Gegenmaßnahmen gegen die Attacke
treffen, ein Mindestmaß an Sicherheit zu gewährleisten, wählt man meist nicht den
kleinstmöglichen Exponenten e = 3. Die Wahl von e wird aber auch nicht zufällig
getroffen, sondern auf solche Zahlen beschränkt, die mit dem Verfahren der binären
Exponentiation eine möglichst effiziente Verschlüsselung ermöglichen. Hinreichend
große Primzahlen der Form e = 2j +1 bieten sich an13 , da dann nur j Quadrierungen
und 1 weitere Multiplikation benötigt werden. Viele Implementierungen verwenden
j = 24 = 16, d.h. e = 216 + 1 = 65537 [X509, Anhang D.6].
Nach der Wahl von e ergibt sich d während der Schlüsselerzeugung automatisch
und es gilt wegen e ∈ O(1) d ∈ O(n). Da Chiffretexte oftmals auf Chipkarten entschlüsselt werden, wäre gerade ein kleiner Entschlüsselungsexponent für die Effizienz
wichtig, sodass man die Überlegung anstellen könnte, das Verfahren umzudrehen und
d fest zu wählen, um dann ein passendes e in der Größenordnung von n zu bestimmen. Diese aus Performanzsicht lohnende Vorgehensweise würde RSA aber unsicher
machen, da gezeigt wurde, dass das Verfahren für d < n0.292 gebrochen werden
kann [BD00]. d muss also in der Größenordnung von n liegen. Durch Ausnutzung
des chinesischen Restsatzes lässt sich die Entschlüsselung zwar um den Faktor 4
beschleunigen [Bu04, Kap. 9.3.9], die Entschlüsselung bleibt aber die aufwändigste
Operation im RSA-Verfahren.
3.5
RSA in Java
Da die Implementierung eines Verschlüsselungsverfahrens sehr sorgfältig geschehen
muss und entsprechend aufwändig ist, wird man zumeist existierende Implementierungen verwenden wollen, wenn man in einem Anwendungssystem auf kryptographische Verfahren zurückgreift. Die Verfügbarkeit leicht einsetzbarer Implementierungen stellt deshalb ein Kriterium für die Wahl eines Kryptosystems dar. Das
RSA-Verfahren wird von den meisten Kryptographie-Frameworks bereitgestellt. Im
13
Es wird eine Primzahl (und damit eine Fermat-Zahl, d.h. j = 2i ) gewählt, damit die Wahrscheinlichkeit für die notwendige Bedingung gcd(e, ϕ(n)) = 1 größer ist und kein neuer RSA-Modul
erzeugt werden muss.
12
Kapitel 3: RSA
folgenden wird der Einsatz von RSA in Java-Programmen auf Basis der Java Cryptography Architecture (JCA) dargestellt [JCA06a].
In der JCA stellen Provider-Klassen kryptographische Dienste wie Schlüsselerzeugung, Verschlüsselung oder Signierung bereit. Der Anwendungsprogrammierer
fragt Dienste beim Framework nach und kann dabei angeben, welcher Algorithmus verwendet werden soll. Das Framework sucht daraufhin einen Provider, der
den gewünschten Dienst mit dem gewünschten Algorithmus bereitstellt, und liefert
ein entsprechendes Objekt zurück, mit dem der Programmierer den Dienst verwenden kann. Die Standard-Javaimplementierung von Sun Microsystems liefert einige
Provider mit, die unter Anderem die Erzeugung von RSA-Schlüsselpaaren sowie
die Ver- und Entschlüsselung mit dem RSA-Verfahren unterstützen, sodass keine
zusätzlichen Pakete notwendig sind, um RSA zu verwenden. Da die JCA auch symmetrische Verschlüsselungsverfahren bereitstellt, ist die Implementierung von Hybridverfahren leicht möglich.
In die allgemeine Verwendung von RSA soll folgendes Beispiel kurz einführen,
ehe dann die Implementierung eines Hybridverfahrens vorgestellt wird. Der Service
KeyPairGenerator ermöglicht die Erzeugung von Schlüsselpaaren für asymmetrische Verfahren. Eine Instanz dieses Service für den RSA-Algorithmus erhält man
vom Framework, indem man die Klassenmethode getInstance der entsprechenden Engine-Klasse KeyPairGenerator aufruft und den gewünschten Algorithmus
angibt. Das Framework sucht daraufhin nach einem Provider, der diesen Service
für den gewünschten Algorithmus anbietet, und instanziiert den Service. Mit dem
zurückgelieferten Objekt kann man dann ein neues Schlüsselpaar bestehend aus Public und Private Key erzeugen.
Listing 1: Schlüsselerzeugung
KeyPairGenerator keyGen = KeyPairGenerator . g e t I n s t a n c e ( ”RSA” ) ;
keyGen . i n i t i a l i z e ( 1 0 2 4 ) ;
KeyPair kp = keyGen . g e n e r a t e K e y P a i r ( ) ;
Nachdem der Sender sich den öffentlichen Schlüssel des Empfängers beschafft
hat, verschlüsselt er seine Nachricht, indem er sich eine Instanz des Service Cipher für den RSA-Algorithmus beim Framework besorgt. Der Cipher-Service dient
sowohl der Ver- als auch der Entschlüsselung bei symmetrischen und asymmetrischen Verfahren. Der Sender initialisiert das Service-Objekt c in Listing 2 mit dem
öffentlichen Schlüssel des Empfängers und dem gewünschten Operationsmodus (hier
Verschlüsselung). Die zu verschlüsselnden Daten können einer Cipher blockweise
übergeben werden, indem die Methode byte[] update(byte[] input) mehrfach
aufgerufen wird. Ein Aufruf der Methode byte[] doFinal() beendet dann die Ver13
Kapitel 3: RSA
schlüsselung.14 Diese Vorgehensweise bietet sich vor allem bei der Verschlüsselung
größerer Daten mit symmetrischen Verfahren an, bei kleineren Datenmengen kann
alternativ direkt die Methode byte[] doFinal(byte[] input) aufgerufen werden.
Die Größe der Nachricht ist bei der RSA-Cipher in der Standard-Implementierung
aus den in Kapitel 3.2 genannten Gründen mit der Schlüssellänge k beschränkt,
maximal können k8 − 11 Bytes verschlüsselt werden. Die als String vorliegende Nachricht MSG in Listing 2 muss dieser Beschränkung genügen. Nach Ausführung der
Verschlüsselung enthält das Array cipherText die verschlüsselten Daten.
Listing 2: Verschlüsselung
PublicKey pubKey ;
. . . // Ö f f e n t l i c h e n S c h l ü s s e l d e s Kommunikationspartners i n pubKey
speichern
Cipher c = Cipher . g e t I n s t a n c e ( ”RSA” ) ;
c . i n i t ( Cipher .ENCRYPT MODE, pubKey ) ;
byte [ ] c i p h e r T e x t = c . d o F i n a l (MSG. g e t B y t e s ( ) ) ;
Listing 3 verdeutlicht, wie der Empfänger aus dem Array cipherText und seinem
geheimen Schlüssel privKey die Nachricht rekonstruiert. Er initialisiert den CipherService mit diesem für die Entschlüsselung. Mit der Methode doFinal entschlüsselt
er die Nachricht und erhält den Klartext.
Listing 3: Entschlüsselung
PrivateKey privKey ;
. . . // P r i v a t e n S c h l ü s s e l i n privKey l a d e n
Cipher c = Cipher . g e t I n s t a n c e ( ”RSA” ) ;
c . i n i t ( Cipher .DECRYPT MODE, kp . g e t P r i v a t e ( ) ) ;
byte [ ] p l a i n T e x t = c . d o F i n a l ( c i p h e r T e x t ) ;
Das Beispiel zeigt, dass die Verwendung von RSA in Java leicht möglich ist, aber
auf kurze Nachrichten beschränkt. Das eigentliche Anwendungsgebiet von RSA liegt
aber wie in Kapitel 2.1 erläutert in den Hybridverfahren, die beliebig große Nachrichten verschlüsseln können. Listing 4 im Anhang zeigt eine beispielhafte Implementierung einer Klasse für hybride Verschlüsselung, die wie im Folgenden beschrieben
arbeitet.
Zur Verschlüsselung einer längeren Nachricht erzeugt der Sender ein HybridCipher-Objekt, wobei er angibt, welche Algorithmen für die symmetrische und die
asymmetrische Verschlüsselung verwendet werden sollen. Die Methode void encrypt
(PublicKey ..., InputStream ..., OutputStream ...) verschlüsselt die Nachricht aus dem Eingabestrom dataIn mit dem hybriden Verfahren und speichert das
Ergebnis im Ausgabestrom cipherDataOut. Der Schlüssel für das symmetrische Ver14
Die Methoden geben jeweils den bisherigen Schlüsseltext zurück. Bei RSA gibt nur doFinal
den Schlüsseltext zurück, update gibt ein leeres Array zurück.
14
Kapitel 3: RSA
fahren wird mit dem öffentlichen Schlüssel assymKey verschlüsselt und der Ausgabe
des Schlüsseltexts vorangestellt.
Abbildung 1 zeigt schematisch den Ablauf der hybriden Verschlüsselung. Nach
der Generierung eines symmetrischen Sitzungsschlüssels wird dieser mit dem asymmetrischen Verfahren, das mit dem öffentlichen Schlüssel initialisiert wurde, verschlüsselt. In der Folge werden die Daten blockweise symmetrisch verschlüsselt. Der
verschlüsselte symmetrische Schlüssel und die verschlüsselten Daten sind die Ausgabe des Verfahrens. In der tatsächlichen Implementierung werden die verschlüsselten
Elemente immer sofort in den Ausgabestrom geschrieben, zusätzlich wird die Länge
des Chiffretextes für den symmetrischen Schlüssel in der Ausgabe festgehalten, um
die Rekonstruktion zu ermöglichen.
Abbildung 1: Sequenzdiagramm zur Verschlüsselung mit HybridCipher. keyGen ist
ein Objekt vom Typ KeyGenerator, die übrigen Objekte sind vom Typ Cipher.
Nachdem der Empfänger die verschlüsselte Nachricht erhalten hat, kann er sie
mit einem HybridCipher-Objekt, das die gleichen Algorithmen verwendet, entschlüsseln. Er übergibt dazu seinen geheimen Schlüssel mitsamt einem Eingabestrom
zum Chiffretext und einem Ausgabestrom der Methode void decrypt(PrivateKey
assymKey, InputStream cipherDataIn, OutputStream dataOut). Den grundsätzlichen Ablauf der Entschlüsselung in diesem Hybridsystem verdeutlicht Abbildung
2. Der am Anfang des Chiffretexts gespeicherte symmetrische Schlüssel wird entschlüsselt und zur blockweisen Entschlüsselung des restlichen Chiffretext verwendet.
15
Kapitel 3: RSA
Schlüssellänge in Bit
Erzeugung eines RSASchlüssels (kein Mittelwert)
Erzeugung des symmetrischen Schlüssels
Verschlüsselung des symmetrischen Schlüssels
Verschlüsselung der Daten
(inkl. I/O)
Entschlüsselung des symmetrischen Schlüssels
Entschlüsselung der Daten
(inkl. I/O)
512
23,7
1024
104,3
2048
844,1
4096
34177,5
0,0
0,0
0,0
0,0
0,2
0,4
1,3
4,9
2,0
2,0
2,0
2,0
1,1
6,5
43,6
333,7
2,0
2,0
2,1
2,1
Tabelle 1: Benötigte Zeit (in ms) für die Ausführung kryptographischer Operationen in HybridCipher in Abhängigkeit von der RSA-Schlüssellänge (Symmetrisches
Verfahren: AES, 64KB Daten, Mittelwerte aus je 100 Durchläufen)
Abbildung 2: Sequenzdiagramm zur Entschlüsselung mit HybridCipher
Die Implementierung des Hybridverfahrens verdeutlicht die vorteilhafte Eigenschaft der JCA, weitgehend algorithmenunabhängig zu sein. Das ermöglicht einen
schnellen Austausch der verwendeten Verfahren, sollten Schwachstellen in einem
Verfahren bekannt werden.
Tabelle 1 zeigt den Geschwindigkeitsnachteil des RSA-Verfahrens gegenüber symmetrischen Verfahren. Die asymmetrische Entschlüsselung des symmetrischen Schlüssels benötigt deutlich länger als die symmetrische Entschlüsselung der eigentlichen
Daten, obwohl die Datenmenge für den Sitzungsschlüssel, der asymmetrisch verund entschlüsselt wird, inklusive Padding in der Tabelle maximal 512 Byte beträgt,
16
Kapitel 4: Rabin
während das symmetrische Verfahren die 64 Kilobyte große Nachricht behandelt.
Zu beobachten ist ferner der schon angesprochene im Vergleich zur Verschlüsselung
höhere Aufwand für die Entschlüsselung im RSA-Verfahren, da diese nur bedingt
beschleunigt werden kann (vgl. Kapitel 3.4).
4
4.1
Rabin
Einleitung und Erläuterung
Wie in Kapitel 2.2 erläutert, wird die Sicherheitsreduktion eines Verschlüsselungsverfahrens als vorteilhaft für die Abschätzbarkeit der Sicherheit angesehen. Der
Nachteil des RSA-Verfahrens ist, dass nur das Schlüsselproblem, nicht aber das
Entzifferungsproblem auf das Faktorisierungsproblem zurückgeführt werden konnte.
Das Rabin-Verfahren ist eng mit RSA verwandt, hat aber den Vorteil, dass neben
dem Schlüsselproblem auch letztgenanntes Problem auf das Faktorisierungsproblem
reduziert werden kann. Es wurde 1979 von Michael Rabin veröffentlicht [Ra79].
Wie beim RSA-Verfahren sind bei der Erzeugung eines Schlüssels für das RabinVerfahren zunächst zufällig zwei Primzahlen p und q zu wählen. Zur Beschleunigung
der Entschlüsselung sollten die beiden Zahlen beim Teilen durch vier den Rest drei
ergeben: p ≡ q ≡ 3 mod 4. Der öffentliche Schlüssel ist n = pq, das Paar der
beiden Primzahlen (p, q) bildet den geheimen Schlüssel. Da das Brechen des RabinVerfahrens äquivalent zum Faktorisierungsproblem ist, beeinflusst die Bitlänge des
öffentlichen Schlüssels die Sicherheit des Verfahres und sollte mindestens 1024 Bit
betragen. Die Primzahlen sollten wiederum die gleiche Länge k2 haben, um die Faktorisierung zu erschweren.
Im Beispiel wählt Bob die 8-Bit Primzahlen p = 191 und q = 239, die die
Kongruenzbedingung erfüllen, und veröffentlicht die 16-Bit-Zahl n = 191 · 239 =
45649.
Verschlüsselt werden können mit dem Rabin-Verfahren Klartexte m mit m ∈
{0, . . . , n − 1}. Die für das RSA-Verfahren getroffenen Aussagen zur Umwandlung
von Textnachrichten in natürliche Zahlen und die Behandlung längerer Nachrichten
gelten analog auch für das Rabin-Verfahren. Der Sender verschlüsselt m, indem
er die Nachricht quadriert und modulo den öffentlichen Schlüssel nimmt, d.h. den
Chiffretext c = m2 mod n berechnet. Für Alices Nachricht Ja“, kodiert als m =
”
19041, ergibt sich also der Chiffretext c = 190412 mod 45649 = 15323.
Erhält man einen Chiffretext c, so muss man daraus die Wurzel ziehen, um m
zu erhalten. Das Wurzelziehen in Restklassen, also die Bestimmung eines m mit
m2 ≡ c mod n, ist aber nicht ohne weitere Informationen effizient durchführbar.
Mit Kenntnis der Primfaktoren p, q von n kann der Empfänger die Nachricht aber
17
Kapitel 4: Rabin
effizient unter Verwendung des chinesischen Restsatzes entschlüsseln.
Dazu berechnet er die Quadratwurzeln von c + pZ und von c + qZ. Aufgrund der
Kongruenzbedingung für die Primfaktoren sind die wie folgt berechneten ±mp und
±mq diese Quadratwurzeln:
mp = c(p+1)/4
mod p,
mq = c(q+1)/4
mod q
Für die Anwendung des chinesischen Restsatzes bestimmt der Empfänger die Koffizienten yp , yq ∈ Z mit dem erweiterten euklidischen Algorithmus, sodass yp p + yq q = 1
gilt. Mit
r = (yp pmq + yq qmp )
mod n
s = (yp pmq − yq qmp )
mod n
sind dann ±r, ±s ∈ Zn die Quadratwurzeln von c + nZ (siehe Anhang A.6).
Als Ergebnis erhält der Empfänger also alle vier Quadratwurzeln der Restklasse
c + nZ, wovon nur eine die ursprüngliche Nachricht ist. Der Empfänger muss sich
für die Nachricht entscheiden, die ihm am wahrscheinlichsten erscheint. Das kann
fehleranfällig oder unmöglich sein, wenn es sich bei der Nachricht nicht um Text,
sondern um zufällige Daten handelt, z.B. einen symmetrischen Schlüssel in einem
Hybridverfahren. Alternativ kann man nur Klartexte mit einer speziellen Struktur,
beispielsweise einem bestimmten Bitstring am Ende zulassen, sodass man die Wurzel
auswählen kann, die dieser Struktur entspricht. Mit dieser Einschränkung lässt sich
allerdings nicht mehr beweisen, dass das Brechen des Rabin-Verfahrens äquivalent
zur Faktorisierung ist.
Um den Chiffretext c = 15323 zu entschlüsseln, den er von Alice erhalten hat,
berechnet Bob mp = 1532348 mod 191 = 59 und mq = 1532360 mod 239 = 160.
Der erweiterte euklidische Algorithmus liefert yp p = −5 · 191 und yq q = 4 · 239.
Wegen r = (−5 · 191 · 160 + 4 · 239 · 59) mod 45649 = 40551 und s = (−5 ·
191 · 160 − 4 · 239 · 59) mod 45649 = 19041 sind die vier Quadratwurzeln von
c + nZ die Restklassen 40551, 5098, 19041 und 26608. Um zu erkennen, welche davon
die ursprüngliche Nachricht von Alice ist, wandelt Bob alle vier Zahlen nach dem
vereinbarten Verfahren in eine Zeichenkette um und erkennt, dass Alice ihm die
Nachricht Ja“ übermittelt hat, da die übrigen Wurzeln keine sinnvolle Nachricht
”
ergeben.
18
Kapitel 4: Rabin
4.2
4.2.1
Sicherheit
Bezug zum Faktorisierungsproblem
Der sicherheitstheoretische Vorteil des Rabin-Verfahrens gegenüber RSA besteht
darin, dass eine vollständige Sicherheitsreduktion auf das Faktorisierungsproblem
existiert. Sowohl Schlüssel- als auch Entzifferungsproblem sind nach der Definition
in Kapitel 2.2 äquivalent zum Faktorisierungsproblem.
Die Äquivalenz des Schlüsselproblems zum Faktorisierungsproblem ist beim RabinVerfahren offensichtlich, da der private Schlüssel ja gerade aus den Primfaktoren des
öffentlichen Schlüssels besteht. Das Entzifferungsproblem kann nicht schwerer als das
Faktorisierungsproblem sein, da mit dessen Lösung der geheime Schlüssel verfügbar
ist. Es gilt aber auch die Umkehrung, da ein probabilistischer Algorithmus existiert,
der wie folgt mit Hilfe eines Algorithmus zur Lösung des Entzifferungsproblems den
öffentlichen Schlüssel n faktorisieren kann [Bu04, Kap. 9.4.5].
Sei R ein dem Angreifer bekannter Algorithmus, mit dem er das Rabin-Verfahren
brechen kann. Der Algorithmus bestimmt also zu jedem Chiffretext c des RabinVerfahrens den Klartext m = R(c) derart, dass m + nZ eine Quadratwurzel von
c + nZ ist.
Mit diesem Algorithmus kann der Angreifer n faktorisieren, indem er eine Zahl
x ∈ {1, . . . , n − 1} zufällig wählt und diese modulo n quadriert. Das Ergebnis c = x2
mod n übergibt er dem Algorithmus und erhält nach Annahme eine Quadratwurzel
m = R(c) von c + nZ. Das muss nicht notwendigerweise x sein, sondern ist eventuell eine der weiteren drei Quadratwurzeln. Mit einer Wahrscheinlichkeit von 50 %
handelt es sich bei gcd(m − x, n) um einen echten Teiler von n und damit um einen
der Primfaktoren, womit n faktorisiert wäre [Bu04, S. 151]. Andernfalls wiederholt
man das Verfahren, bis ein Primfaktor von n gefunden wird.
Die Voraussetzung für das beschriebenen Faktorisierungsverfahren ist, dass der
Klartextraum unbeschränkt ist. Sind die Klartexte auf eine bestimmte Struktur eingeschränkt, um die Entschlüsselung zu vereinfachen, so muss der Angreifer im ersten
Schritt x entsprechend wählen. Der Algorithmus R liefert mit sehr hoher Wahrscheinlichkeit dann aber genau dieses x zurück, da den anderen Quadratwurzeln
die Struktur fehlt. Damit kann man n aber nicht faktorisieren und die angestrebte Äquivalenz besteht nicht. Die Einschränkung der Klartextstruktur birgt also ein
Sicherheitsrisiko und eliminiert den Vorteil des Rabin-Verfahrens gegenüber RSA.
4.2.2
Angriffe auf das Rabin-Verfahren
Der sicherheitstheoretische Vorteil, dass das Brechen des Rabin-Verfahrens äquivalent
zum Faktorisierungsproblem ist, ist zugleich die Basis für die größte Schwachstelle
19
Kapitel 5: Zusammenfassung
des Verfahrens. Ein aktiver Angreifer kann mit einer Chosen-Ciphertext-Attacke den
Modul n faktorisieren und erhält damit den geheimen Schlüssel, mit dem er u.A. die
gesamte Kommunikation des Opfers entschlüsseln kann.
Sein Vorgehen entspricht dabei dem im vorigen Kapitel vorgestellten Faktorisierungsalgorithmus: er wählt eine Zahl x und berechnet den Schlüsseltext c = x2
mod n. Dann lässt er c entschlüsseln, wozu er nach den Annahmen der ChosenCiphertext-Attacke (siehe Kapitel 2.2) die Möglichkeit hat. Mit dem Ergebnis kann
er wie oben n mit einer Wahrscheinlichkeit von 50% faktorisieren.
Lässt man nur Klartexte einer bestimmten Struktur zu, verhindert man die
Chosen-Ciphertext-Attacke, verliert aber auch die Äquivalenzeigenschaft. Das RabinVerfahren ist genau wie RSA anfällig für Low-Exponent-Attacken und sollte daher
randomisiert werden. Auch das Problem der Multiplikativität existiert beim RabinKryptosystem.
4.3
Effizienz und Beurteilung
Der Aufwand für die Verschlüsselung ist im Rabin-Verfahren etwas geringer als bei
RSA, da nur eine Quadrierung modulo n benötigt wird. Die Entschlüsselung erfordert in etwa den gleichen Aufwand wie im RSA-Kryptosystem [Bu04, Kap. 9.4.4].
Insgesamt unterscheidet sich der Aufwand der wesentlichen Operationen von RSA
und Rabin kaum und sollte bei der Entscheidung für ein Verfahren keine Rolle spielen, das wichtigere Kriterium ist die Sicherheit.
Das Rabin-Verfahren punktet zwar mit der Äquivalenz zum Faktorisierungsproblem, allerdings wird das Fehlen derselben bei RSA nicht als echtes Sicherheitsrisiko
verstanden. Ohnehin bietet die Äquivalenz keine vollständige Sicherheit, sondern
nur einen starken Hinweis und eine Absicherung. Generell wird die Möglichkeit der
Chosen-Ciphertext-Attacke als schwerwiegender Mangel des Rabin-Verfahrens angesehen, sodass es kaum praktisch eingesetzt wird. Das spiegelt sich zum Beispiel darin
wider, dass das Rabin-Verfahren anders als RSA nicht zu den Verfahren gehört, die
eine Implementierung von Java SE6 bereitstellen muss oder sollte [JCA06b, Implementation Requirements].
5
Zusammenfassung
Der Austausch geheimer Informationen über das per se unsichere Internet nimmt
beständig zu, gleichzeitig gibt es eine Vielzahl potentieller Kommunikationspartner. Die notwendige Verschlüsselung ist allein mit symmetrischen Verfahren nicht
möglich, da der Schlüsselaustausch logistisch nicht zu bewerkstelligen ist. Kapitel 2.1
zeigt, dass das Konzept der Public-Key-Kryptographie Abhilfe schaffen kann. In
20
Kapitel 5: Zusammenfassung
Hybridverfahren ermöglicht es die Geheimhaltung von Informationen und im Zusammenhang mit Signaturverfahren gewährleistet es Integrität, Authentizität und
unter Umständen auch Nicht-Abstreitbarkeit.
Das RSA-Kryptosystem als das erste praktisch einsetzbare asymmetrische Verfahren mit weiter Verbreitung hat auch heute noch eine große Bedeutung und wird
in der Praxis verwendet. Die Sicherheitsdiskussion hat trotz der unvollständigen
Äquivalenz zum Faktorisierungsproblem keine Schwäche identifiziert, aufgrund derer von einem Einsatz von RSA abzuraten ist. Beachtet man einige Fallen wie
die Low-Exponent-Attacke und wählt eine der benötigten Sicherheitsstufe angemessene Schlüssellänge, ist RSA bei randomisiertem Einsatz als sicher einzustufen.
Beständige Aufmerksamkeit für Fortschritte bei Angriffen auf RSA ist aber wie bei
jedem kryptographischen Verfahren notwendig, weshalb Anwendungssysteme soweit
wie möglich unabhängig von konkreten Algorithmen sein sollten. Da die meisten
Schwachstellen von RSA Fehlern in Implementierung und Design entspringen [Bo99,
Kap. 6], ist für den Einsatz ein genaues Verständnis des Verfahrens notwendig.
Der Ausblick auf die Verwendung von RSA in Java zeigt, dass die Java Cryptography Architecture einen einfachen Zugang zu Public-Key-Kryptographie bietet
und hybride Verschlüsselungsverfahren leicht zu implementieren sind. Deutlich wird
ferner, wie die JCA die Algorithmen-Unabhängigkeit als fundamentales ArchitekturPrinzip unterstützt und eine leichte Austauschbarkeit der in einem Anwendungssystem verwendeten Verfahren gewährleistet.
Die Ausführungen zum Rabin-Verfahren lassen erkennen, dass eine relativ bessere theoretische Sicherheit nicht gleichzusetzen ist mit einem besseren Kryptosystem.
Die Problematiken beim praktischen Einsatz von Rabin, insbesondere die mehrdeutige Entschlüsselung und die Anfälligkeit gegenüber Chosen-Ciphertext-Attacken,
führen dazu, dass das Verfahren trotz der kryptoanalytischen Äquivalenz zum Faktorisierungsproblem in der Praxis kaum Relevanz hat.
Auch wenn RSA aktuell sichere Kommunikation ermöglicht, ist es unbedingt
nötig, dass neue Verfahren entwickelt werden, um Ausweichmöglichkeiten für den
Fall, dass RSA gebrochen werden kann, zur Verfügung zu haben. Das ElGamalKryptosystem [Bu04, Kap. 9.6] oder das Elliptische-Kurven-Kryptosystem [Schn96,
Kap. 19.8] stellen solche alternativen asymmetrischen Verfahren dar.
21
Kapitel A: Mathematische Hintergründe und Beweise
A
A.1
Mathematische Hintergründe und Beweise
Restklassen
a + mZ bezeichnet die Restklasse von a mod m und umfasst alle Zahlen b, die sich
aus Addition ganzzahliger Vielfacher von m zu a ergeben, oder anders ausgedrückt,
die bei Division durch m den gleichen Rest wie a ergeben: a + mZ = {b : b =
a + km, k ∈ Z} = {b : a ≡ b mod m}
Der Restklassenring Z/mZ ist die Menge aller m Restklassen mod m.
A.2
Hilfssätze
A.2.1
Hilfssatz 1
Für s, t und r = lcm(s, t) gilt:
a ≡ b mod r
⇔ a ≡ b mod s
∧ a ≡ b mod t
A.2.2
Hilfssatz 2
Sei r eine Primzahl mit r ≡ 3 mod 4. Mit a = b(r+1)/4 mod r sind dann ±a + rZ
die Quadratwurzeln der Quadratzahl b ≡ c2 mod r in Z/rZ (0 ≤ c < r).
Beweis
Zu zeigen ist (±a)2 ≡ b ≡ c2 mod r.
(±a)2 ≡ ±b(r+1)/4
2
≡ b(r+1)/2 ≡ cr+1 ≡ c2
mod r
(1)
Die letzte Umformung ergibt sich nach dem Kleinen Satz von Fermat: da r eine
Primzahl ist und gcd(c, r) = 1 für 0 < c < r, gilt cr−1 ≡ 1 mod r ⇔ cr−1 · c2 ≡ c2
mod r (für c = 0 stimmt die Kongruenz ebenfalls).
A.3
Invertierung von Restklassen und der erweiterte euklidische Algorithmus
Damit die Restklasse a + mZ in Z/mZ invertierbar ist, d.h. eine Restklasse x + mZ
mit ax ≡ 1 mod m existiert, muss gcd(a, m) = 1 gelten. Das inverse Element x+mZ
lässt sich mit dem erweiterten euklidischen Algorithmus bestimmen.
22
Kapitel A: Mathematische Hintergründe und Beweise
Der erweiterte euklidische Algorithmus bestimmt für zwei natürliche Zahlen a, b
ihren größten gemeinsamen Teiler gcd(a, b) und zwei ganze Zahlen x, y mit ax+by =
gcd(a, b). Eine Darstellung des Algorithmus findet sich zum Beispiel in [Bu04, Kap.
2.9].
Die Kongruenz ax ≡ 1 mod m kann mit y ∈ Z umgeformt werden zu ax +
my = 1 = gcd(a, m), sodass der erweiterte euklidische Algorithmus direkt mit den
Eingaben a, m eingesetzt werden kann.
A.4
Chinesischer Restsatz
Der Chinesische Restsatz dient der Lösung der simultanen Kongruenz aus n Kongruenzgleichungen
x ≡ ai
mod mi 1 ≤ i ≤ n
mit ai ∈ Z, mi ∈ N, gcd(mi , mj ) = 1 ∀i, j, i 6= j
Folgende Hilfsgrößen werden benötigt:
m=
n
Y
mi
(2)
i=1
Mi =
m
mi
yi Mi = 1
(3)
mod mi
(4)
Die inversen Elemente yi können mit dem erweiterten euklidischen Algorithmus
berechnet werden, da gcd(mi , Mi ) = 1.
Die Lösung der simultanen Kongruenz ist dann [Bu04, Kap. 3.15]:
x=
n
X
!
ai yi Mi
mod m
(5)
i=1
Für den Fall n = 2 ergibt sich folgende Vereinfachung. Die Berechnung von y1 , y2
mit dem erweiterten euklidischen Algorithmus ergibt wegen M1 = m2 , M2 = m1
y1 M1 + x1 m1 = 1
∧ y2 M2 + x2 m2 = 1
⇒ y1 M1 + x1 M2 = 1
∧ y2 M2 + x2 M1 = 1
⇒ y1 M1 + y2 M2 = 1
23
Kapitel A: Mathematische Hintergründe und Beweise
Es genügt also ein Aufruf des euklidischen Algorithmus.
A.5
Korrektheit von Ver- und Entschlüsselung in RSA
Ist (n, e) ein öffentlicher und ist d der zugehörige private Schlüssel für das RSAVerfahren, so gilt ∀m ∈ {0, . . . , n − 1}: (me )d mod n = m.
Beweis
Zwischen Ver- und Entschlüsselungsexponenten gilt folgende Beziehung:
ed ≡ 1
mod (p − 1)(q − 1)
⇒ ∃l ∈ Z : ed = 1 + l(p − 1)(q − 1)
(6)
(7)
Die Exponentiation lässt sich demnach wie folgt umschreiben
(me )d = med = m1+l(p−1)(q−1) = m(m(p−1)(q−1) )l
(8)
Falls die Primzahl p kein Teiler von m und demzufolge gcd(p, m) = 1 ist, gilt
nach dem Kleinen Satz von Fermat [Bu04, S. 125] mp−1 ≡ 1 mod p. Daraus folgt:
(me )d ≡ m m(p−1)
(q−1)l
≡ m mod p
(9)
Die Kongruenz gilt auch für den Fall, dass p ein Teiler von m ist, da sich dann auf
beiden Seiten 0 ergibt. Analog zeigt man (me )d ≡ m mod q.
Da p und q teilerfremd und n = pq = lcm(p, q) ist, folgt daraus nach Hilfssatz
A.2.1 (me )d ≡ m mod n.
Zu beachten ist, dass die Reihenfolge der Exponentiation keine Rolle spielt und auch
(md )e ≡ m mod n gilt. RSA lässt sich deshalb auch zur Signierung nutzen.
A.6
Beweis der Korrektheit des Entschlüsselungsalgorithmus
für das Rabin-Verfahren
Im Folgenden soll der Algorithmus zur effizienten Bestimmung der Quadratwurzeln
im Rabin-Verfahren hergeleitet werden. Gesucht wird ein m mit m2 = c mod n. Da
n = pq = lcm(p, q) gilt nach Hilfssatz A.2.1:
m2 ≡ c mod n
⇔ m2 ≡ c mod p
∧ m2 ≡ c mod q
24
(10)
Kapitel B: Quelltext zum Kapitel RSA in Java“
”
Die beiden unteren Zeilen ergeben zusammen eine simultane Kongruenz, aus deren
Lösungen sich nach dem chinesischen Restsatz die Quadratwurzeln in Z/nZ ergeben.
Setze mp = c(p+1)/4 mod p und mq = c(q+1)/4 mod q. Dann sind nach Hilfssatz
A.2.2 ±mp +pZ die Quadratwurzeln von c+pZ in Z/pZ und ±mq +qZ die von c+qZ
in Z/qZ. Daraus lassen sich vier simultane Kongruenzen konstruieren, betrachtet
werden soll im Folgenden einer der vier Fälle, die anderen ergeben sich analog.
Gesucht ist die Lösung m der simultanen Kongruenz
m = +mp
mod p
(11)
∧ m = +mq
mod q
(12)
m + nZ ist dann nach Gleichungssystem 10 eine der Quadratwurzeln von c + nZ in
Z/nZ. Die Anwendung des chinesischen Restsatzes erfordert im Fall von 2 Kongruenzen die Berechnung von yp , yq mit yq q+yp p = 1 mit Hilfe des erweiterten euklidischen
Algorithmus. Mit diesen Koeffizienten ergibt sich dann m = yq qmq + yp pmp mod n.
B
Quelltext zum Kapitel RSA in Java“
”
B.1
Klasse HybridCipher
Listing 4: Implementierung eines hybriden Verschlüsselungsverfahrens in Java
package hh . s e c u r i t y ;
import hh . p r o f i l e r . P r o f i l e r ;
10
import
import
import
import
import
import
import
java .
java .
java .
java .
java .
java .
java .
i o . IOException ;
i o . InputStream ;
i o . OutputStream ;
s e c u r i t y . InvalidKeyException ;
s e c u r i t y . NoSuchAlgorithmException ;
s e c u r i t y . PrivateKey ;
s e c u r i t y . PublicKey ;
import
import
import
import
import
import
import
j a v a x . c r y p t o . BadPaddingException ;
j a v a x . c r y p t o . Cipher ;
javax . crypto . I l l e g a l B l o c k S i z e E x c e p t i o n ;
j a v a x . c r y p t o . KeyGenerator ;
j a v a x . c r y p t o . NoSuchPaddingException ;
j a v a x . c r y p t o . SecretKey ;
j a v a x . c r y p t o . s p e c . SecretKeySpec ;
20
/∗ ∗
∗ I m p l e m e n t i e r u n g e i n e s H y b r i d v e r f a h r e n s z u r Ver− und E n t s c h l ü s s e l u n g
von Daten .
25
Kapitel B: Quelltext zum Kapitel RSA in Java“
”
30
∗ Die Daten werden mit einem symmetrischen V e r f a h r e n v e r s c h l ü s s e l t .
Der dazu v e r w e n d e t e geheime S c h l ü s s e l wird mit einem
∗ a s s y m m e t r i s c h e n V e r f a h r e n v e r s c h l ü s s e l t . Bei d e r E n t s c h l ü s s e l u n g
l ä u f t d i e s e s V e r f a h r e n i n u m g e k e h r t e r R e i h e n f o l g e ab .
∗ @author Henning H e i t k ö t t e r
∗/
public c l a s s HybridCipher {
/∗ ∗ Zur Messung d e r b e n ö t i g t e n Z e i t f ü r Aufgaben während d e r Ver− und
E n t s c h l ü s s e l u n g . ∗/
public s t a t i c f i n a l P r o f i l e r p r o f = new P r o f i l e r (new S t r i n g [ ] {
” Erzeugung e i n e s RSA−S c h l ü s s e l s ” , ” Erzeugung d e s symmetrischen
S c h l ü s s e l s ” ,
” V e r s c h l ü s s e l u n g d e s symmetrischen S c h l ü s s e l s ” , ” V e r s c h l ü s s e l u n g
d e r Daten ( i n k l . I /O) ” ,
” E n t s c h l ü s s e l u n g d e s symmetrischen S c h l ü s s e l s ” , ” E n t s c h l ü s s e l u n g
d e r Daten ( i n k l . I /O) ” } , 1 0 0 ) ;
private f i n a l S t r i n g symAlgorithm ;
private f i n a l S t r i n g assymAlgorithm ;
private f i n a l KeyGenerator symKeyGen ;
private f i n a l Cipher symCipher ;
private f i n a l Cipher assymCipher ;
40
50
/∗ ∗
∗ E r z e u g t e i n neues h y b r i d e s V e r s c h l ü s s e l u n g s v e r f a h r e n .
∗ @param symAlgorithm Der A l g o r i t h m u s f ü r d i e s y m m e t r i s c h e
V e r s c h l ü s s e l u n g , z .B. AES
∗ @param assymAlgorithm Der A l g o r i t h m u s f ü r d i e a s s y m m e t r i s c h e
V e r s c h l ü s s e l u n g , z .B. RSA
∗ @throws NoSuchAlgorithmException
∗ @throws NoSuchPaddingException
∗/
public HybridCipher ( S t r i n g symAlgorithm , S t r i n g assymAlgorithm )
throws NoSuchAlgorithmException , NoSuchPaddingException {
super ( ) ;
t h i s . symAlgorithm = symAlgorithm ;
t h i s . assymAlgorithm = assymAlgorithm ;
symKeyGen = KeyGenerator . g e t I n s t a n c e ( getSymAlgorithm ( ) ) ;
symCipher = Cipher . g e t I n s t a n c e ( getSymAlgorithm ( ) ) ;
assymCipher = Cipher . g e t I n s t a n c e ( getAssymAlgorithm ( ) ) ;
}
60
/∗ ∗
∗ V e r s c h l ü s s e l t d i e Daten aus dem Eingabestrom d a t a I n mit einem
symmetrischen V e r s c h l ü s s e l u n g s v e r f a h r e n .
∗ Der S c h l ü s s e l d e s symmetrischen V e r f a h r e n s w ird mit dem RSA−
V e r f a h r e n v e r s c h l ü s s e l t und zusammen mit den
∗ v e r s c h l ü s s e l t e n Daten i n den Ausgabestrom cipherDataOut
geschrieben .
∗
∗ Ein A u f r u f d e r Methode d e c r y p t , dem d e r e r z e u g t e S c h l ü s s e l t e x t a l s
Eingabe ü b e r g e b e n wird , e r g i b t
∗ den u r s p r ü n g l i c h e n Text , s o f e r n d e r zu assymKey k o r r e s p o n d i e r e n d e
geheime S c h l ü s s e l z u r E n t s c h l ü s s e l u n g
∗ v e r w e n d e t wir d .
26
Kapitel B: Quelltext zum Kapitel RSA in Java“
”
70
∗ @param assymKey Der ö f f e n t l i c h e S c h l ü s s e l e i n e s a s s y m m e t r i s c h e n
V erf ah re ns , mit dem d e r s y m m e t r i s c h e S c h l ü s s e l
∗ v e r s c h l ü s s e l t wird .
∗ @param d a t a I n Ein Eingabestrom mit den zu v e r s c h l ü s s e l n d e n Daten .
∗ @param cipherDataOut Ein Ausgabestrom , i n d e r d e r S c h l ü s s e l t e x t
g e s c h r i e b e n wir d .
∗/
public void e n c r y p t ( PublicKey assymKey , InputStream dataIn ,
OutputStream cipherDataOut ) throws I n v a l i d K e y E x c e p t i o n ,
I l l e g a l B l o c k S i z e E x c e p t i o n , BadPaddingException , IOException {
SecretKey symKey = generateSymKey ( ) ;
encryptSymKey ( symKey , assymKey , cipherDataOut ) ;
p r o f . s t a r t ( ” V e r s c h l ü s s e l u n g d e r Daten ( i n k l . I /O) ” ) ;
symCipher . i n i t ( Cipher .ENCRYPT MODE, symKey ) ;
// Daten v e r s c h l ü s s e l n und w e g s c h r e i b e n
byte [ ] i nB uf = new byte [ 0 x2000 ] ;
int l e n ;
while ( ( l e n = d a t a I n . r e a d ( i nB uf ) ) != −1){
cipherDataOut . w r i t e ( symCipher . update ( inBuf , 0 , l e n ) ) ;
}
cipherDataOut . w r i t e ( symCipher . d o F i n a l ( ) ) ;
p r o f . end ( ” V e r s c h l ü s s e l u n g d e r Daten ( i n k l . I /O) ” ) ;
80
cipherDataOut . c l o s e ( ) ;
dataIn . c l o s e ( ) ;
}
90
100
/∗ ∗
∗ E n t s c h l ü s s e l t d i e Daten aus dem Eingabestrom c i p h e r D a t a I n , s o f e r n
e s s i c h um d i e Ausgabe von H y b r i d C i p h e r . e n c r y p t mit den g l e i c h e n
∗ A l g o r i t h m e n h a n d e l t und assymKey d e r p r i v a t e S c h l ü s s e l p a s s e n d zum
b e i d e r V e r s c h l ü s s e l u n g v e r w e n d e t e n ö f f e n t l i c h e n S c h l ü s s e l i s t .
∗
∗ @param assymKey Der p r i v a t e S c h l ü s s e l , mit dem d e r
S i t z u n g s s c h l ü s s e l e n t s c h l ü s s e l t werden kann .
∗ @param c i p h e r D a t a I n Eingabestrom mit den v e r s c h l ü s s e l t e n
I n f o r m a t i o n e n . Der C h i f f r e t e x t muss beim A u f r u f n i c h t
v o l l s t ä n d i g v o r l i e g e n ,
∗ sondern kann − z .B. mit einem P i p e d I n p u t S t r e a m − s c h r i t t w e i s e
g e l i e f e r t werden ( h i l f r e i c h im Kontext M u l t i t h r e a d i n g / Networking
).
∗ @param dataOut Ausgabestrom , i n den d i e e n t s c h l ü s s e l t e n Daten
g e s c h r i e b e n werden .
∗/
public void d e c r y p t ( PrivateKey assymKey , InputStream c i p h e r D a t a I n ,
OutputStream dataOut ) throws I n v a l i d K e y E x c e p t i o n ,
I l l e g a l B l o c k S i z e E x c e p t i o n , BadPaddingException , IOException {
SecretKey symKey = decryptSymKey ( assymKey , c i p h e r D a t a I n ) ;
p r o f . s t a r t ( ” E n t s c h l ü s s e l u n g d e r Daten ( i n k l . I /O) ” ) ;
symCipher . i n i t ( Cipher .DECRYPT MODE, symKey ) ;
// Daten e n t s c h l ü s s e l n und w e g s c h r e i b e n
byte [ ] i nB uf = new byte [ 0 x2000 ] ;
int l e n ;
27
Kapitel B: Quelltext zum Kapitel RSA in Java“
”
while ( ( l e n = c i p h e r D a t a I n . r e a d ( i nB uf ) ) != −1){
dataOut . w r i t e ( symCipher . update ( inBuf , 0 , l e n ) ) ;
}
dataOut . w r i t e ( symCipher . d o F i n a l ( ) ) ;
p r o f . end ( ” E n t s c h l ü s s e l u n g d e r Daten ( i n k l . I /O) ” ) ;
110
dataOut . c l o s e ( ) ;
cipherDataIn . c l o s e () ;
}
120
130
140
/∗ ∗
∗ E r z e u g t den S c h l ü s s e l f ü r das s y m m e t r i s c h e
V e r s c h l ü s s e l u n g s v e r f a h r e n , mit dem d i e Daten v e r s c h l ü s s e l t
werden .
∗ @return Der geheime S c h l ü s s e l .
∗/
private SecretKey generateSymKey ( ) {
p r o f . s t a r t ( ” Erzeugung d e s symmetrischen S c h l ü s s e l s ” ) ;
SecretKey symKey = symKeyGen . generateKey ( ) ;
p r o f . end ( ” Erzeugung d e s symmetrischen S c h l ü s s e l s ” ) ;
return symKey ;
}
/∗ ∗
∗ V e r s c h l ü s s e l t den symmetrischen S c h l ü s s e l symKey mit dem
ö f f e n t l i c h e n S c h l ü s s e l assymKey d e s a s s y m m e t r i s c h e n
V e r s c h l ü s s e l u n g s v e r f a h r e n s
∗ und s p e i c h e r t i h n i n den Ausgabestrom cipherDataOut ( zusammen mit
den z u r R e k o n s t r u k t i o n b e n ö t i g e n I n f o r m a t i o n e n )
∗ @param symKey Der zu v e r s c h l ü s s e l n d e S i t z u n g s s c h l ü s s e l .
∗ @param assymKey Der ö f f e n t l i c h e S c h l ü s s e l , mit dem d e r
S i t z u n g s s c h l ü s s e l v e r s c h l ü s s e l t werden s o l l .
∗ @param cipherDataOut In d i e s e n Ausgabestrom werden d i e
v e r s c h l ü s s e l t e n I n f o r m a t i o n e n g e s c h r i e b e n .
∗/
private void encryptSymKey ( SecretKey symKey , PublicKey assymKey ,
OutputStream cipherDataOut ) throws I n v a l i d K e y E x c e p t i o n ,
I l l e g a l B l o c k S i z e E x c e p t i o n , BadPaddingException , IOException {
p r o f . s t a r t ( ” V e r s c h l ü s s e l u n g d e s symmetrischen S c h l ü s s e l s ” ) ;
assymCipher . i n i t ( Cipher .ENCRYPT MODE, assymKey ) ;
byte [ ] cipherKey = assymCipher . d o F i n a l ( symKey . getEncoded ( ) ) ;
p r o f . end ( ” V e r s c h l ü s s e l u n g d e s symmetrischen S c h l ü s s e l s ” ) ;
// S p e i c h e r e Länge d e s S c h l ü s s e l s und den S c h l ü s s e l
cipherDataOut . w r i t e ( intToByteArray ( cipherKey . l e n g t h ) ) ;
cipherDataOut . w r i t e ( cipherKey ) ;
}
150
/∗ ∗
∗ E n t s c h l ü s s e l t den symmetrischen S c h l ü s s e l .
∗ @param assymKey Das Gegenst ück zum P u b l i c Key , mit dem d e r
S i t z u n g s s c h l ü s s e l v e r s c h l ü s s e l t i s t .
∗ @param c i p h e r D a t a I n Eingabestrom , an d e s s e n Anfang d e r
v e r s c h l ü s s e l t e S i t z u n g s s c h l ü s s e l s t e h t .
∗ @return Der S i t z u n g s s c h l ü s s e l d e s h y b r i d e n V e r f a h r e n s .
∗/
private SecretKey decryptSymKey ( PrivateKey assymKey , InputStream
28
Kapitel B: Quelltext zum Kapitel RSA in Java“
”
c i p h e r D a t a I n ) throws I n v a l i d K e y E x c e p t i o n , IOException ,
I l l e g a l B l o c k S i z e E x c e p t i o n , BadPaddingException {
// V e r s c h l ü s s e l t e n , k o d i e r t e n S c h l ü s s e l a u s l e s e n
byte [ ] cipherKeyLengthBA = new byte [ 4 ] ;
readGuaranteed ( c i p h e r D a t a I n , cipherKeyLengthBA ) ; //An d i e s e r S t e l l e
werden d i e e r s t e n 4 B y t e s b e n ö t i g t .
int cipherKeyLength = byteArrayToInt ( cipherKeyLengthBA ) ;
byte [ ] cipherKey = new byte [ cipherKeyLength ] ;
readGuaranteed ( c i p h e r D a t a I n , cipherKey ) ; // B e n ö t i g t wi rd d e r
komplette
160
p r o f . s t a r t ( ” E n t s c h l ü s s e l u n g d e s symmetrischen S c h l ü s s e l s ” ) ;
// S c h l ü s s e l d a t e n e n t s c h l ü s s e l n und S c h l ü s s e l e r z e u g e n
assymCipher . i n i t ( Cipher .DECRYPT MODE, assymKey ) ;
byte [ ] keyEncoded = assymCipher . d o F i n a l ( cipherKey ) ;
SecretKeyFactory k f = SecretKeyFactory . g e t I n s t a n c e (
getSymAlgorithm ( ) ) ;
//
SecretKey r e s u l t = k f . g e n e r a t e S e c r e t ( new S e cr e tK e y Sp e c ( keyEncoded
, getSymAlgorithm ( ) ) ) ;
SecretKey r e s u l t = new SecretKeySpec ( keyEncoded , getSymAlgorithm ( ) )
;
p r o f . end ( ” E n t s c h l ü s s e l u n g d e s symmetrischen S c h l ü s s e l s ” ) ;
return r e s u l t ;
}
//
170
private s t a t i c byte [ ] intToByteArray ( int i ) {
byte [ ] r e s u l t = new byte [ 4 ] ;
result
result
result
result
180
[0]
[1]
[2]
[3]
=
=
=
=
( byte )
( byte )
( byte )
( byte )
(i
(i
(i
(i
>>> 2 4 ) ;
>> 16 & 0xFF) ;
>> 8 & 0xFF) ;
& 0xFF) ;
return r e s u l t ;
}
private s t a t i c int byteArrayToInt ( byte [ ] ba ) {
i f ( ba . l e n g t h != 4 )
throw new I l l e g a l A r g u m e n t E x c e p t i o n ( ”Das Byte−Array muss d i e Länge
4 haben . ” ) ;
int r e s u l t = ( ( ba [ 0 ] & 0xFF) << 2 4 ) | ( ( ba [ 1 ] & 0xFF) << 1 6 ) | ( ( ba
[ 2 ] & 0xFF) << 8 ) | ( ba [ 3 ] & 0xFF) ;
190
return r e s u l t ;
}
/∗ ∗
∗ L i e s t d a t a . l e n g h t Byte an Daten aus dem Eingabestrom . F a l l s n i c h t
s o v i e l e Daten z u r Verfügung s t e h e n , das
∗ Ende d e s Stroms a b e r n i c h t e r r e i c h t i s t , b l o c k i e r t d i e Methode .
T r i t t EOF auf , b e v o r d i e g e f o r d e r t e n Daten
∗ g e l e s e n werden konnte , w ird e i n e IOException g e w o r f e n .
∗ @param i n Der Eingabestrom , aus dem d i e Daten g e l e s e n werden .
∗ @param d a t a Array , das d i e Daten aufnimmt . Im E r f o l g s f a l l wir d das
Array v o l l s t ä n d i g g e f ü l l t , s e i n e Länge g i b t a l s o an ,
29
Kapitel B: Quelltext zum Kapitel RSA in Java“
”
∗ wie v i e l e B y t e s g e l e s e n werden s o l l e n .
∗ @throws IOException F a l l s das Ende d e s Stroms e r r e i c h t wird , b e v o r
d a t a . l e n g t h B y t e s g e l e s e n werden konnten .
∗/
private s t a t i c void readGuaranteed ( InputStream in , byte [ ] data )
throws IOException {
int curLen , t o t a l L e n = 0 ;
int r e m a i n i n g = data . l e n g t h ;
while ( r e m a i n i n g > 0 ) {
curLen = i n . r e a d ( data , t o t a l L e n , r e m a i n i n g ) ;
i f ( curLen == −1)
throw new IOException ( ”EOF, bevor ” + data . l e n g t h + ” Bytes
g e l e s e n werden konnten ” ) ;
else {
r e m a i n i n g −= curLen ;
t o t a l L e n += curLen ;
}
}
}
200
210
public S t r i n g getSymAlgorithm ( ) {
return symAlgorithm ;
}
220
public S t r i n g getAssymAlgorithm ( ) {
return assymAlgorithm ;
}
}
B.2
Sonstige Klassen
Listing 5: Test des hybriden Verschlüsselungsverfahrens
package hh . s e c u r i t y ;
import hh . p r o f i l e r . P r o f i l e r ;
10
20
import
import
import
import
import
import
import
import
import
import
java .
java .
java .
java .
java .
java .
java .
java .
java .
java .
i o . BufferedInputStream ;
i o . BufferedOutputStream ;
io . FileInputStream ;
i o . FileOutputStream ;
i o . IOException ;
i o . OutputStream ;
i o . PipedInputStream ;
i o . PipedOutputStream ;
s e c u r i t y . KeyPair ;
s e c u r i t y . KeyPairGenerator ;
public c l a s s HybridTest {
public s t a t i c c l a s s NullStream extends OutputStream {
@Override
public void w r i t e ( int b ) throws IOException {
// doNothing
}
}
30
Kapitel B: Quelltext zum Kapitel RSA in Java“
”
public s t a t i c void main ( S t r i n g [ ] a r g s ) throws E x c e p t i o n {
test () ;
testMultithreaded () ;
//
p r o f i l e () ;
}
30
/∗ ∗
∗ Z e i t m e s s u n g e i n i g e r O pe ra ti o ne n i n H y b r i d C i p h e r b e i
u n t e r s c h i e d l i c h e n S c h l ü s s e l g r ö ß e n .
∗/
public s t a t i c void p r o f i l e ( ) throws E x c e p t i o n {
P r o f i l e r p r o f = HybridCipher . p r o f ;
BufferedInputStream plainIn , cipherIn ;
BufferedOutputStream cipherOut , p l a i n O u t ;
KeyPairGenerator keyGen = KeyPairGenerator . g e t I n s t a n c e ( ”RSA” ) ;
int [ ] k e y S i z e s = new int [ ] { 5 1 2 , 1 0 2 4 , 2 0 4 8 , 4 0 9 6 } ;
40
f o r ( int i = 0 ; i < k e y S i z e s . l e n g t h ; i ++){
prof . restart () ;
keyGen . i n i t i a l i z e ( k e y S i z e s [ i ] ) ;
p r o f . s t a r t ( ” Erzeugung e i n e s RSA−S c h l ü s s e l s ” ) ;
KeyPair kp = keyGen . g e n e r a t e K e y P a i r ( ) ;
p r o f . end ( ” Erzeugung e i n e s RSA−S c h l ü s s e l s ” ) ;
f o r ( int j = 0 ; j < 1 0 0 ; j ++){
p l a i n I n = new B u f f e r e d I n p u t S t r e a m (new F i l e I n p u t S t r e a m ( ” p l a i n .
txt ”) ) ;
c i p h e r O u t = new BufferedOutputStream (new FileOutputStream ( ”
cipher . txt ”) ) ;
50
HybridCipher hc = new HybridCipher ( ”AES” , ”RSA” ) ;
hc . e n c r y p t ( kp . g e t P u b l i c ( ) , p l a i n I n , c i p h e r O u t ) ;
c i p h e r I n = new B u f f e r e d I n p u t S t r e a m (new F i l e I n p u t S t r e a m ( ” c i p h e r .
txt ”) ) ;
p l a i n O u t = new BufferedOutputStream (new FileOutputStream ( ”
plain2 . txt ”) ) ;
hc . d e c r y p t ( kp . g e t P r i v a t e ( ) , c i p h e r I n , p l a i n O u t ) ;
}
System . out . p r i n t l n ( ” S c h l ü s s e l l ä n g e : ” + k e y S i z e s [ i ] ) ;
System . out . p r i n t l n ( ”−−−−−−−−−−−−−−−−−−−−” ) ;
p r o f . p r i n t P r o f i l i n g I n f o ( System . out ) ;
System . out . p r i n t l n ( ) ;
60
}
}
70
/∗ ∗
∗ T e s t f a l l z u r e i n f a c h e n Ver− und E n t s c h l ü s s e l u n g mit H y b r i d C i p h e r .
∗/
public s t a t i c void t e s t ( ) throws E x c e p t i o n {
BufferedInputStream plainIn , cipherIn ;
BufferedOutputStream cipherOut , p l a i n O u t ;
KeyPairGenerator keyGen = KeyPairGenerator . g e t I n s t a n c e ( ”RSA” ) ;
keyGen . i n i t i a l i z e ( 2 0 4 8 ) ;
KeyPair kp = keyGen . g e n e r a t e K e y P a i r ( ) ;
31
Kapitel B: Quelltext zum Kapitel RSA in Java“
”
p l a i n I n = new B u f f e r e d I n p u t S t r e a m (new F i l e I n p u t S t r e a m ( ” p l a i n . t x t ” ) )
;
c i p h e r O u t = new BufferedOutputStream (new FileOutputStream ( ” c i p h e r .
txt ”) ) ;
HybridCipher hc = new HybridCipher ( ”AES” , ”RSA” ) ;
hc . e n c r y p t ( kp . g e t P u b l i c ( ) , p l a i n I n , c i p h e r O u t ) ;
80
c i p h e r I n = new B u f f e r e d I n p u t S t r e a m (new F i l e I n p u t S t r e a m ( ” c i p h e r . t x t ”
));
p l a i n O u t = new BufferedOutputStream (new NullStream ( ) ) ;
hc . d e c r y p t ( kp . g e t P r i v a t e ( ) , c i p h e r I n , p l a i n O u t ) ;
}
90
/∗ ∗
∗ T e s t f a l l , d e r d i e M ö g l i c h k e i t z u r nahezu p a r a l l e l e n Ver− und
E n t s c h l ü s s e l u n g d e m o n s t r i e r t .
∗/
public s t a t i c void t e s t M u l t i t h r e a d e d ( ) throws E x c e p t i o n {
final BufferedInputStream pl a in In ;
f i n a l BufferedOutputStream p l a i n O u t ;
f i n a l PipedOutputStream c i p h e r O u t ;
f i n a l PipedInputStream c i p h e r I n ;
KeyPairGenerator keyGen = KeyPairGenerator . g e t I n s t a n c e ( ”RSA” ) ;
keyGen . i n i t i a l i z e ( 2 0 4 8 ) ;
f i n a l KeyPair kp = keyGen . g e n e r a t e K e y P a i r ( ) ;
100
// K o n s t r u i e r e e i n e Pipe z u r Ü b e r t r a g u n g d e r Daten z w i s c h e n den
Threads
p l a i n I n = new B u f f e r e d I n p u t S t r e a m (new F i l e I n p u t S t r e a m ( ” p l a i n . t x t ” ) )
;
c i p h e r O u t = new PipedOutputStream ( ) ;
c i p h e r I n = new PipedInputStream ( c i p h e r O u t ) ;
p l a i n O u t = new BufferedOutputStream (new FileOutputStream ( ” p l a i n 2 .
txt ”) ) ;
f i n a l HybridCipher e n c r y p t H y b r i d = new HybridCipher ( ”AES” , ”RSA” ) ;
f i n a l HybridCipher d e c r y p t H y b r i d = new HybridCipher ( ”AES” , ”RSA” ) ;
110
120
// Der E n t s c h l ü s s e l u n g s −Thread kann auch v o r d e r V e r s c h l ü s s e l u n g
g e s t a r t e t werden
// Die e n t s p r e c h e n d e n Methoden b l o c k i e r e n dann s o l a n g e , b i s Daten
vorhanden s i n d .
new Thread (new Runnable ( ) {
@Override
public void run ( ) {
try {
d e c r y p t H y b r i d . d e c r y p t ( kp . g e t P r i v a t e ( ) , c i p h e r I n , p l a i n O u t ) ;
} catch ( E x c e p t i o n e ) {
e . printStackTrace () ;
}
}
}) . s t a r t ( ) ;
new Thread (new Runnable ( ) {
@Override
public void run ( ) {
32
Kapitel B: Quelltext zum Kapitel RSA in Java“
”
try {
e n c r y p t H y b r i d . e n c r y p t ( kp . g e t P u b l i c ( ) , p l a i n I n , c i p h e r O u t ) ;
} catch ( E x c e p t i o n e ) {
e . printStackTrace () ;
}
}
}) . s t a r t ( ) ;
130
}
}
Listing 6: Beispiel zur Verwendung von RSA in Java
package hh . s e c u r i t y ;
import
import
import
import
java .
java .
java .
java .
security
security
security
security
. KeyPair ;
. KeyPairGenerator ;
. PrivateKey ;
. PublicKey ;
import j a v a x . c r y p t o . Cipher ;
10
public c l a s s RSAExample {
private s t a t i c f i n a l S t r i n g MSG = ” N a c h r i c h t ” ; // < ( k /8 − 11) Byte
private s t a t i c f i n a l S t r i n g RSA ALGO = ”RSA” ;
/∗ ∗
∗ @param a r g s
∗/
public s t a t i c void main ( S t r i n g [ ] a r g s ) throws E x c e p t i o n {
// S c h l ü s s e l e r z e u g u n g
KeyPairGenerator keyGen = KeyPairGenerator . g e t I n s t a n c e (RSA ALGO) ;
keyGen . i n i t i a l i z e ( 1 0 2 4 ) ;
KeyPair kp = keyGen . g e n e r a t e K e y P a i r ( ) ;
20
PublicKey pubKey = kp . g e t P u b l i c ( ) ;
PrivateKey privKey = kp . g e t P r i v a t e ( ) ;
// V e r s c h l ü s s e l u n g
Cipher c = Cipher . g e t I n s t a n c e (RSA ALGO) ;
c . i n i t ( Cipher .ENCRYPT MODE, pubKey ) ;
byte [ ] c i p h e r T e x t = c . update (MSG. g e t B y t e s ( ) ) ;
30
// E n t s c h l ü s s e l u n g
c . i n i t ( Cipher .DECRYPT MODE, privKey ) ;
@SuppressWarnings ( ” unused ” )
byte [ ] p l a i n T e x t = c . d o F i n a l ( c i p h e r T e x t ) ;
}
}
Listing 7: Klasse zur Zeitmessung
package hh . p r o f i l e r ;
import
import
import
import
j a v a . i o . P rint Str eam ;
j a v a . math . B i g I n t e g e r ;
j a v a . u t i l . HashMap ;
j a v a . u t i l . Map ;
33
Kapitel B: Quelltext zum Kapitel RSA in Java“
”
10
/∗ ∗
∗ H i l f s k l a s s e z u r Messung von A u s f ü h r u n g s z e i t e n .
∗ @author Henning H e i t k ö t t e r
∗/
public c l a s s P r o f i l e r {
private c l a s s P r o f i l i n g I n f o {
private long [ ] d u r a t i o n s ;
private int c n t ;
String function ;
private long l a s t S t a r t ;
P r o f i l i n g I n f o ( S t r i n g f u n c t i o n , int maxExecutions ) {
super ( ) ;
this . function = function ;
d u r a t i o n s = new long [ maxExecutions ] ;
cnt = 0 ;
}
20
void n e x t E x e c u t i o n S t a r t ( ) {
l a s t S t a r t = System . nanoTime ( ) ;
}
30
void l a s t E x e c u t i o n E n d ( ) {
d u r a t i o n s [ c n t ] = System . nanoTime ( ) − l a s t S t a r t ;
c n t++;
}
long g e t A v e r a g e ( ) {
B i g I n t e g e r sum = new B i g I n t e g e r ( ” 0 ” ) ;
f o r ( long l : d u r a t i o n s )
sum = sum . add (new B i g I n t e g e r ( Long . t o S t r i n g ( l ) ) ) ;
40
return sum . d i v i d e (new B i g I n t e g e r ( I n t e g e r . t o S t r i n g ( c n t ) ) ) .
longValue ( ) ;
}
}
private Map<S t r i n g , P r o f i l i n g I n f o > profMap ;
private S t r i n g [ ] p r o f i l e d F u n c t i o n s ;
private int maxExecutions ;
50
public P r o f i l e r ( S t r i n g [ ] p r o f i l e d F u n c t i o n s , int maxExecutions ) {
this . p r o f i l e d F u n c t i o n s = p r o f i l e d F u n c t i o n s ;
t h i s . maxExecutions = maxExecutions ;
initMap ( ) ;
}
private void initMap ( ) {
profMap = new HashMap<S t r i n g , P r o f i l i n g I n f o >() ;
for ( S t r i n g f : p r o f i l e d F u n c t i o n s )
profMap . put ( f , new P r o f i l i n g I n f o ( f , maxExecutions ) ) ;
}
60
public void s t a r t ( S t r i n g f u n c t i o n ) {
profMap . g e t ( f u n c t i o n ) . n e x t E x e c u t i o n S t a r t ( ) ;
}
34
Kapitel B: Quelltext zum Kapitel RSA in Java“
”
public void end ( S t r i n g f u n c t i o n ) {
profMap . g e t ( f u n c t i o n ) . l a s t E x e c u t i o n E n d ( ) ;
}
public void r e s t a r t ( ) {
initMap ( ) ;
}
70
public void p r i n t P r o f i l i n g I n f o ( P rin tStr eam out ) {
f o r ( P r o f i l i n g I n f o p i : profMap . v a l u e s ( ) ) {
out . p r i n t f ( ”%s \ t%d\n” , p i . f u n c t i o n , p i . g e t A v e r a g e ( ) ) ;
}
}
}
35
Literaturverzeichnis
Literatur
[BD00] Dan Boneh, Glenn Durfee: Cryptanalysis of RSA with Private Key d Less
Than N 0.292 , in: IEEE Transactions on Information Theory, Vol 46, No. 4, S.
1339-1349, 2000.
[Bo99] Dan Boneh: Twenty Years of Attacks on the RSA Cryptosystem, in: Notices
of the American Mathematical Society, Vol. 46, No. 2, S. 203-213, 1999.
[Bu04] Johannes Buchmann: Einführung in die Kryptographie, 3. Aufl., Springer,
2004.
[IKT07] Statistisches Bundesamt: IKT in Unternehmen. Nutzung von Informationstechnologie in Unternehmen. Ergebnisse für das Jahr 2006, 2007.
[JCA06a] Sun Microsystems: Java Cryptography Architecture (JCA) Reference Guide., URL: http://java.sun.com/javase/6/docs/technotes/guides/security/crypto/CryptoSpec.html, Abrufdatum:
17. November 2007.
[JCA06b] Sun Microsystems: Java Cryptography Architecture. Standard Algorithm
Name Documentation, URL: http://java.sun.com/javase/6/docs/technotes/guides/security/StandardNames.html,
Abrufdatum: 17. November 2007.
[Kn98] Donald E. Knuth: The Art of Computer Programming. Seminumerical Algorithms, 3. Aufl., Addison-Wesley, 1998.
[LV01] Arjen K. Lenstra, Eric R. Verheul: Selecting Cryptographic Key Sizes in:
Journal of Cryptology: the journal of the International Association for Cryptologic Research, Vol. 14, No. 4, S. 255-293, 2001.
[MOV96] Alfred J. Menezes, Paul C. van Oorschot, Scott A. Vanstone: Handbook
of Applied Cryptography, CRC Press, 1996.
[PKCS1] RSA Laboratories: PKCS #1 v2.1: RSA Cryptography Standard, RSA Security Inc. Public-Key Cryptography Standards (PKCS), 2002.
[Ra79] Michael O. Rabin: Digitalized Signatures and Public-Key Functions as intractable as Factorization, MIT Laboratory for Computer Science Series, 1979.
[RSA77] Ron L. Rivest, Adi Shamir, Leonard M. Adleman: A Method for Obtaining Digital Signatures and Public-Key-Cryptosystems., MIT Laboratory for
Computer Science Series, 1977.
[Schn96] Bruce Schneier: Applied Cryptography, 2. Aufl., John Wiley & Sons, 1996.
36
Literaturverzeichnis
[Sh94] Peter W. Shor: Algorithms for Quantum Computation: Discrete Log and Factoring, in: Proceedings of the 35th Symposium on Foundations of Computer
Science, S. 124-134, 1994.
[X509] ITU-T: Recommendation X.509. Authentication Framework, 1997.
37
Herunterladen