Bibliothek zur Implementierung kryptographischer Protokolle in

Werbung
Johannes Theuermann
Bibliothek zur Implementierung
kryptographischer Protokolle
in endlichen Körpern
Masterarbeit
zur Erlangung des akademischen Grades
Diplom-Ingenieur
Studium
Informatik
Alpen-Adria-Universität Klagenfurt
Fakultät für Technische Wissenschaften
Betreuer
Univ.-Prof. Dr. Patrick Horster
Univ. Ass. DDipl.-Ing. Dr. Stefan Rass
Institut für Angewandte Informatik
Forschungsgruppe Systemsicherheit
Klagenfurt, 24. November 2010
Bei Fragen, Problemen oder Anregungen kontaktieren Sie bitte die Forschungsgruppe
Systemsicherheit ([email protected]) oder den Autor ([email protected]).
iii
“Besser auf neuen Wegen etwas stolpern
als in alten Pfaden auf der Stelle zu treten.”
Sprichwort aus China.
Ehrenwörtliche Erklärung
Ich erkläre ehrenwörtlich, dass ich die vorliegende wissenschaftliche Arbeit selbstständig angefertigt und die mit ihr unmittelbar verbundenen Tätigkeiten selbst erbracht habe. Ich erkläre
weiters, dass ich keine anderen als die angegebenen Hilfsmittel benutzt habe. Alle aus gedruckten, ungedruckten oder dem Internet im Wortlaut oder im wesentlichen Inhalt übernommenen
Formulierungen und Konzepte sind gemäß den Regeln für wissenschaftliche Arbeiten zitiert und
durch Fußnoten bzw. durch andere genaue Quellenangaben gekennzeichnet.
Die während des Arbeitsvorganges gewährte Unterstützung einschließlich signifikanter Betreuungshinweise ist vollständig angegeben.
Die wissenschaftliche Arbeit ist noch keiner anderen Prüfungsbehörde vorgelegt worden. Diese
Arbeit wurde in gedruckter und elektronischer Form abgegeben. Ich bestätige, dass der Inhalt
der digitalen Version vollständig mit dem der gedruckten Version übereinstimmt.
Ich bin mir bewusst, dass eine falsche Erklärung rechtliche Folgen haben wird.
Johannes Theuermann
Klagenfurt, 24. November 2010
iv
“Wie lange noch Speckles?”
“Nur noch ein paar Anschläge und dann hab ich
Sabers Verschlüsselung dechiffriert. Um auf Darwins
PDA zugreifen zu können, muss ich ein univariates
Polynom über einen Finiten Körper faktorisieren.”
Aus dem Disney-Film “G-Force – Agenten mit Biss”
Danksagung
Hiermit möchte ich mich bei all den Menschen bedanken, die – auf unterschiedlichste Art und
Weise – mich durch mein Studium und im Speziellen durch diese Diplomarbeit begleitet und
unterstützt haben.
Ich möchte mich bei Herrn Prof. Dr. Patrick Horster für die Möglichkeit, meine Diplomarbeit
am Institut für Angewandte Informatik in der Forschungsgruppe Systemsicherheit schreiben zu
können, bedanken. Im Besonderen möchte ich mich bei meinem Betreuer DDipl.-Ing. Dr. Stefan
Rass bedanken, der mir sehr viele Ideen geliefert hat und mir nicht nur bei einigen, sondern bei
vielen Problemen mit Rat und Tat zur Seite stand.
Ein großes Dankeschön an meine Freundin Angelika. Ohne dich wäre diese Arbeit vermutlich
nicht so lesbar geworden. Aber vor allem auch danke, dass du mit mir durch diese harte Zeit
gegangen bist.
Danke auch an all meine Freunde für all die aufbauenden Worte in dunklen Tagen.
Zusammenfassung
In der vorliegenden Arbeit wird ein Werkzeug zur Implementierung kryptographischer Verfahren
und Protokolle vorgestellt. Ausgehend von den benötigten mathematischen Grundlagen und
deren Berechenbarkeit wird das Problemfeld der Kryptographie angeschnitten. Verschiedene
Verfahren werden detailliert besprochen und die für ihre Realisierung benötigten Algorithmen
analysiert. Die Mehrzahl dieser wird in einer Bibliothek gesammelt für weitere Entwicklungen
zur Verfügung gestellt. Zu Demonstrationszwecken wurde ein Stackrechner realisiert, der auf
die Methoden der Bibliothek zugreift. Einzelne Berechnungsbeispiele sollen abschließend die
Benutzbarkeit der implementierten Methoden belegen.
Für die geplante Weiterentwicklung im Zuge eines Folgeprojektes wird ein Konzept vorgestellt,
welches die Anforderungen an die zu implementierende Applikation darlegt. Des Weiteren wird
eine Sprache vorgestellt, die für diesen Zweck genutzt werden könnte.
Inhaltsverzeichnis
1 Einführung und Motivation
1
1.1
Grundbegriffe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2
1.2
RSA-Verfahren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4
1.3
Diffie-Hellman-Protokoll . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6
1.4
ElGamal-Verfahren
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7
1.5
Shamir’s Secret-Sharing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8
2 Grundlagen
11
2.1
Langzahlarithmetik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
11
2.2
Zufallszahlenerzeugung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
12
2.3
Euler’sche ϕ-Funktion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13
2.4
Modulo Arithmetik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
14
2.5
Primzahltest . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
16
2.6
Erweiterter Euklidischer Algorithmus . . . . . . . . . . . . . . . . . . . . . . . . .
19
2.7
Verfahren zum schnellen Potenzieren . . . . . . . . . . . . . . . . . . . . . . . . .
20
2.8
Chinesischer Restsatz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
22
2.9
Faktorisieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
23
3 Endliche Körper
27
3.1
Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
27
3.2
Modulo-Arithmetik mit Polynomen . . . . . . . . . . . . . . . . . . . . . . . . . .
28
3.3
Irreduzibilitätstest . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
29
3.4
Primitivwurzeln, Einheitswurzeln und primitive Polynome . . . . . . . . . . . . .
30
3.5
Finden von q-ten primitiven Einheitswurzeln . . . . . . . . . . . . . . . . . . . .
31
3.6
Faktorisieren von Polynomen . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
31
3.7
Ausgewählte Anwendungen endlicher Körper . . . . . . . . . . . . . . . . . . . .
32
viii
Inhaltsverzeichnis
4 Sprach-Spezifikationen
35
4.1
Deklarationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
35
4.2
Zuweisungen
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
36
4.3
Datentypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
36
4.4
Datenstrukturen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
38
4.5
Kontrollstrukturen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
39
4.6
Zufallszahlenerzeugung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
40
4.7
Vordefinierte Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
43
5 Implementierung
45
5.1
Datenstrukturen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
45
5.2
Bibliothek . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
47
5.3
Demonstrator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
57
5.4
Konzept . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
61
5.5
Der Jigloo GUI Editor in Eclipse . . . . . . . . . . . . . . . . . . . . . . . . . . .
70
6 Anwendungsbeispiele
71
6.1
RSA-Verfahren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
71
6.2
Diffie-Hellman-Protokoll . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
73
6.3
ElGamal-Verfahren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
75
6.4
Shamir’s Secret Sharing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
78
7 Zusammenfassung und Ausblick
83
A Syntax Überblick
85
B GUI-Editor und Piktogramme
87
C Symbole und Notation
89
Literaturverzeichnis
91
1 Einführung und Motivation
Kryptographische Verfahren und Protokolle finden gegenwärtig in verschiedenen Bereichen des
täglichen Lebens Anwendung. Wann immer zwei Instanzen auf (abhör-)sicherem Wege miteinander kommunizieren wollen, muss die auszutauschende Information verschlüsselt werden.
Kommunikation im Internet auf gesichertem Wege erfolgt beispielsweise mithilfe des HTTPSProtokolls (für Online-Banking, Buchungsvorgänge, etc.). Sollen Emails für potenzielle MithörerInnen unlesbar sein, müssen auch diese verschlüsselt werden. Emails müssen aber nicht nur vor
’MitleserInnen’ geschützt werden, AngreifInnen können auch ein Email mit falscher AbsenderInnenadresse abschicken (beispielsweise durch die Verwendung von Telnet; das dafür notwendige
technische Wissen ist für jedermann in Form der Request for Comments (RFC) zugänglich).
Einen Schutz gegen diese Art von Angriff stellen digitale Signaturen dar: Um AbsenderInnen zu
authentifizieren kann man Emails – und andere Dokumente – digital unterschreiben.
Bei militärischen Operationen wird im Allgemeinen jedwede Kommunikation (also sowohl Textals auch Sprachnachrichten) verschlüsselt. Gerade in Krisensituationen ist die abhörsichere Kommunikation zwischen den Befehlshabern sehr wichtig. Nehmen wir als Krisensituation den Anschlag auf die USA am 9. September 2001 als Beispiel: Die Regierung hatte ein gesichertes
Kommunikationssystem, welches zeitweise zusammenbrach. Man musste wichtige Informationen
über handelsübliche Mobiltelefone austauschen. Selbst Condoleeza Rice (damalige Nationale Sicherheitsberaterin) gesteht heute: “Das war wirklich gefährlich. Wenn Terroristen uns abgehört
hätten, hätten sie vieles mitbekommen über die Handys.” [DerS10]
Der zunehmenden Relevanz der Systemsicherheit muss natürlich auch in der Ausbildung von
Fachkräften Rechnung getragen werden. Hierbei soll die vorliegende Arbeit einen Beitrag leisten. Sie stellt einen Teil eines Projektes dar, an dessen Ende die Implementierung eines Rechners
für endliche Körper steht. Zweck dieser Applikation soll es sein, Studierenden im Rahmen des
Informatikstudiums die Möglichkeit zu bieten, anhand verschiedener Berechnungsbeispiele ein
besseres Verständnis für die Abläufe in kryptographischen Verfahren und Protokollen zu entwickeln. Zu diesem Zweck wurden Ressourcen entwickelt, die für diese geplante Applikation
genutzt werden können. Für die Applikation selbst wird ein Konzept vorgeschlagen, welches in
Folgeprojekten als Basis für die Implementierung genutzt werden kann.
Zur Einführung beginnen wir mit einer skizzenhaften Beschreibung verschiedener Verfahren
und Protokolle, welche aktuell weite Verbreitung gefunden haben. Im Zuge dessen wird insbesondere verdeutlicht, welche mathematischen Operationen für die Durchführung dieser Verfahren und Protokolle notwendig sind. Die hier angeführten Verfahren und Protokolle bauen
meist auf der Komplexität mathematischer Probleme auf. Die Sicherheit einiger Verfahren beruht beispielsweise auf der Schwierigkeit, große Zahlen zu faktorisieren, welche in der Literatur
2
1
Einführung und Motivation
[RiSA78, Hors99] als Faktorisierungsproblem bekannt ist. Auch das diskreten Logarithmusproblem ([ElGa85]) dient häufig als Grundlage sicherer Krypto-Systeme.
Das diskrete Logarithmusproblem soll anhand eines Beispiels gezeigt werden. Es sei p eine große
Primzahl, a und x seien zwei natürliche Zahlen. Die Berechnung y = ax MOD p kann mit Hilfe
eines entsprechenden Algorithmus (Repeated Square and Multiplying; siehe Abschnitt 2.7) mit
O(log p)3 Langzahloperationen durchgeführt werden. Dies entspricht bei einem p mit ungefähr
200 Bit Länge etwa 108 Operationen, welche von einem Standardcomputer (mit ≈ 6 GFLOPS)
im Millisekundenbereich berechnet werden können. Soll die Gleichung (y = ax MOD p) bei
bekanntem a, p und y nach x gelöst werden, so benötigt man laut [MeVO96] bei Verwendung des
p
Index-Calculus-Verfahrens O(exp ln(p) · ln(ln(p))) Operationen. Bei einem p ≈ 10200 entspricht
das ungefähr 1.2 · 1023 Operationen.
Der zeitliche Aufwand für diese Berechnung kann an folgendem Rechenbeispiel verdeutlicht werden: Dafür wird – vereinfacht – eine Langzahloperation einer Gleitkommaoperation gleichgesetzt.
Die “TOP500 SUPERCOMPUTER SITES”[TOP10] erstellt zweimal im Jahr eine Rangliste der
weltweit besten Supercomputer. Der zur Zeit (November 2010) schnellste Supercomputer heißt
“Tianhe-1A” und befindet sich am National Supercomputing Center in Tianjin (China). Dieser
leistet 2.566 · 106 GFLOPS (Giga Floatingpoint Operations Per Second). Bei diesem Rechner
beträgt der Zeitaufwand für die Berechnung:
1.2 · 1023 Operationen
≈ 4.677 · 107 s ≈ 1.52 Jahre.
2.566 · 1015 Operationen/s
Dies bedeutet, dass der zurzeit schnellste Supercomputer nach dem heutigen Stand der Technik
und den Vorgaben von oben über zwei Jahre benötigen würde, um die Zahl x zu bestimmen und
somit das System zu brechen.
In diesem Kapitel wird auf die theoretische Durchführbarkeit der einzelnen Verfahren und Protokolle eingegangen. In Kapitel 6 werden sowohl im implementierten Rechner als auch in der in
Kapitel 4 vorgestellten Sprache Rechenbeispiele durchgeführt.
Sollten hier verwendete Symbole oder Notationen nicht bekannt sein, können diese im Anhang C
nachgelesen werden.
1.1 Grundbegriffe
In diesem Abschnitt werden einige wichtige Grundbegriffe eingeführt, die bereits im ersten Kapitel (aber auch noch später) benötigt werden.
Gruppen
Eine Gruppe (G, ·) besteht aus einer Menge G mit einer 2-stelligen Operation (Multiplikation)
· : G × G → G, welche folgenden Gesetzen genügt:
Assoziativgesetz: Für alle x, y, z ∈ G gilt, dass (x · y) · z = x · (y · z).
Existenz eines neutralen Elementes: Es existiert ein neutrales Element (Einselement)
e ∈ G, dass e · x = x · e = x für alle x ∈ G gilt. Abhängig von der Schreibweise, no-
1.1 Grundbegriffe
3
tieren wir das bezüglich der Gruppenoperation neutrale Element entweder mit 1 bei
multiplikativer Schreibweise (G, ·) oder mit 0 bei additiver Schreibweise (G, +).
Existenz von inversen Elementen: Für alle x ∈ G existiert ein inverses Element x−1 ∈ G,
sodass x · x−1 = e = x−1 · x gilt. Invertierbare Elemente nennen wir auch Einheiten.
Bei der Gruppe G handelt es sich um eine endliche Gruppe, wenn G endlich viele Elemente
besitzt. Die Anzahl der Elemente der Gruppe G wird als Ordnung von G bezeichnet. [KuSt98]
Eine Gruppe heißt kommutativ, falls zusätzlich zu den oben angeführten Gesetzen noch für alle
x, y, ∈ G gilt, dass x · y = y · x. [Kurz77]
Untergruppen
Eine nichtleere Teilmenge U einer Gruppe (G, ·) heißt Untergruppe von G, wenn (U, ·) selbst
eine Gruppe ist. Dies gilt auf jeden Fall, wenn neben x, y ∈ U auch x · y ∈ U und x−1 ∈ U
gilt. [KuSt98]
Zyklische Gruppen
Es handelt sich bei G um eine zyklische Gruppe, wenn alle Elemente von G als Potenzen eines
(festen) Elementes g erzeugt werden können [KuSt98]. In diesem Fall wird g ein primitives
Element, erzeugendes Element oder auch Generator genannt. Ein Verfahren zum Auffinden
solcher Elemente wird in Abschnitt 3.4 angegeben.
Ringe
Ein Ring (R, +, ·) ist eine Menge mit zwei zweistelligen Operationen + : R × R → R und
· : R × R → R, wobei
• (R, +) eine Gruppe ist,
• für · das Assoziativgesetz gilt, und
• · distributiv gegenüber + ist, d.h. (x · (y + z) = (x · y) + (x · z) und (x + y) · z = (x · z) + (y · z)
für alle x, y, z ∈ R. [Buch04]
Existiert ein Element 1 ∈ R welches neutral bezüglich · ist, d.h. es gilt 1 · x = x = x · 1 für alle
x ∈ R, so nennen wir R einen Ring mit Eins. Falls die Multiplikation · kommutativ ist, liegt ein
kommutativer Ring (ggf. mit Eins) vor.
Das bezüglich + neutrale Element (Existenz ist gesichert aufgrund der Tatsache, dass (R, +) eine
Gruppe sein muss) wird mit 0 notiert (aufgrund der additiven Schreibweise der Gruppe (R, +)).
Körper
Ein Körper ist ein kommutativer Ring mit Eins, in dem jedes von 0 verschiedene Element
invertierbar ist [Buch04]. Eine genauere Betrachtung findet in Kapitel 3 statt.
Einheitengruppen
Die bezüglich · invertierbaren Elemente eines kommutativen Rings (R, +, ·) mit Eins bilden eine
Gruppe. Sie heißt Einheitengruppe des Rings und wird mit (R∗ , ·) bezeichnet. [Buch04]
Für einen Körper F ist (F ∗ , ·) = (F \ {0}, ·) die multiplikative Einheitengruppe [Kurz07].
4
1
Einführung und Motivation
Charakteristik
In Ringen und Körpern ist die Charakteristik die kleinste natürliche Zahl n, sodass eine n-malige
Addition von 1 die 0 ergibt (1| + 1 +
{z· · · + 1} = 0).
n mal
In der Tabelle 1.1 werden die verschiedene algebraischen Strukturen gegenübergestellt. Diese Tabelle sollte darstellen, welche algebraischen Strukturen welche Eigenschaften besitzen. Außerdem
werden bekannte Beispiele angegeben.
Tab. 1.1: Eigenschaften verschiedener algebraischer Strukturen
Gruppe
×
×
×
Mult (·)
Add (+)
Assoziativ
Kommutativ
Neutrales Element
Inverses Element
Assoziativ
Kommutativ
Neutrales Element
Inverses Element
Distributivgesetz
Bekannte Beispiele
Komm.
Gruppe
×
×
×
×
Ring
Ring mit 1
×
×
×
×
×
×
×
×
×
×
×
(Z, +)
Komm.
Ring mit 1
×
×
×
×
×
×
×
×
(Z, +, ·)
Körper
×
×
×
×
×
×
×
×
×
R, Q, C
1.2 RSA-Verfahren
Eines der ersten Public-Key Kryptosysteme wurde 1978 von Ronald Rivest, Adi Shamir und
Leonard Adleman [RiSA78] veröffentlicht. Neben dem Ver- und Entschlüsselungsverfahren eignet
sich das in [RiSA78] vorgeschlagene Verfahren auch zur Erstellung von digitalen Signaturen.
Im Folgenden werden die beim RSA-Verfahren auszuführenden Schritte für die Ver- und Entschlüsselung, sowie die Signaturerstellung und -verifikation im Detail beschrieben:
Initialisierung
Zur Initialisierung des Systems werden folgende Schritte durchgeführt:
1. Alice wählt zwei große Primzahlen p, q ∈R P \ {2} , p 6= q, und berechnet das Produkt
n = p·q (Die Parameter p und q sind für jede Instanz im System unterschiedlich zu wählen,
um eine Faktorisierung durch Bildung des größten gemeinsamen Teilers zu vermeiden),
sowie den Wert ϕ(n) = (p − 1) · (q − 1) (Die Euler’sche ϕ-Funktion wird noch später in
Kapitel 2 näher erläutert werden).
2. Weiters wählt sie eine zu ϕ(n) teilerfremde Zahl e und berechnet eine Zahl d, sodass
e · d MOD ϕ(n) = 1 gilt.
3. Es sei m ∈ [0 : n − 1] die zu verarbeitende Nachricht, wobei lange Nachrichten gegebenenfalls in Blöcke aufgeteilt werden. Nicht voll ausgefüllte Blöcke müssen mittels Padding
aufgefüllt werden.
Die Werte p, q und ϕ(n) werden für das Verfahren nicht mehr benötigt und sollten nicht gespeichert werden, damit das System nicht durch Auffinden der Werte – durch fremde Instanzen –
angreifbar wird. Zum Zweck der schnellen Entschlüsselung mit Hilfe des chinesischen Restsatzes
1.2 RSA-Verfahren
5
können diese Werte jedoch im laufenden Betrieb Verwendung finden (dies wird noch genauer im
Abschnitt 2.8 gezeigt).
Ver- und Entschlüsselung
Zum Verschlüsseln benötigt man e und n, die den öffentlichen Schlüssel bilden. Die geheime
Entschlüsselungsinformation ist der Wert d, welcher aus dem Paar (e, n) nicht effizient bestimmt
werden kann, wenn die Faktorisierung von n unbekannt bzw. nicht effizient durchführbar ist.
Ein Beweis der Korrektheit ist in [RiSA78] zu finden.
• Verschlüsselungsfunktion: E : [0 : n − 1] → [0 : n − 1] , E(m) := me MOD n = c.
• Entschlüsselungsfunktion: D : [0 : n − 1] → [0 : n − 1] , D(c) := cd MOD n = m.
Signaturerstellung und -verifikation
Da die Anwendung des öffentlichen Schlüssels beim Potenzieren die Wirkung des geheimen
Schlüssels aufhebt (und umgekehrt), kann eine Signatur durch “Entschlüsseln” mit dem geheimen Schlüssel direkt konstruiert werden. Die Verifikation ergibt sich dann auf natürliche Weise
durch Anwendung des öffentlichen Schlüssels wie beim Verschlüsseln. Daraus folgt, dass die
Schlüsselpaare für die Verschlüsselung und Signatur unterschiedlich sein müssen, da sonst Angriffsmöglichkeiten entstehen. Wie beim Ver- und Entschlüsseln gilt, dass n und e öffentlich sind
und d geheim ist.
• Signaturfunktion: S : [0 : n − 1] → [0 : n − 1] , S(m) := md MOD n = s.
• Verifikationsfunktion:
V : [0 : n − 1] → {true,false},

 true, falls se MOD n = m.
V (s) :=
 false, falls se MOD n 6= m.
Sollte die Signatur so durchgeführt werden könnte man als Angriff zu einer vorhandenen Signatur s eine Nachricht m generieren, welche bei der Verifikation V (s) := true ergibt. Die
Nachricht m ist in der Regel sinnlos und somit unbrauchbar (unkorrektes Dateiformat oder
kein ASCII-Text). Um diese Attacke auszuschließen, ist es obligatorisch, statt der Nachricht m,
einen Hashwert h(m) zu signieren, wobei die Hashfunktion kollisionsresistent sein muss. Eine
Hashfunktion bildet ein Dokument beliebiger Bitlänge auf einen Wert fixer Bitlänge ab. Da die
Anzahl der möglichen Dokumente unendlich, die Anzahl der möglichen Hashwerte aber endlich
ist, können zwei unterschiedliche Dokumente den selben Hashwert generieren. Eine Hashfunktion gilt als kollisionisresistent, wenn es praktisch undurchführbar ist, solche Kollisionen zu finden.
Somit ist die aus Sicherheitsgründen
eingesetzte Funktion S(m) := (h(m))d MOD n = s, und

 true, falls se MOD n = h(m).
die Verifikation V (s) :=
 false, falls se MOD n 6= h(m).
Benötigte Hilfsmittel
• Langzahlarithmetik: Wie aus der Darstellung ersichtlich benötigt man große Zahlen, um
die berechenmäßige Schwierigkeit des Faktorisierungsproblems zu sichern.
• Zufallszahlengenerator: Für die Erzeugung von Primzahlen im Rahmen der Festlegung der
Systemparameter bei der Initialisierung.
6
1
Einführung und Motivation
• Primzahltests.
• Erweiterter Euklidischer Algorithmus: Dieser ermöglicht die Bestimmung von multiplikativ inversen Elementen innerhalb der Modulo-Arithmetik. Beim RSA-Verfahren dient er
insbesondere zur Lösung der Gleichung e · d ≡ 1 (mod n) bei bekanntem e und n.
• Verfahren zum schnellen Potenzieren: Das Verfahren sollte Langzahlen als Exponenten
unterstützen und wird sowohl beim Ver- als auch beim Entschlüsseln benötigt.
Wir beenden die Auflistung der für RSA benötigten Algorithmen hier und kommen in Kapitel 2
darauf zurück, wo wir tiefer ins Detail gehen.
1.3 Diffie-Hellman-Protokoll
Das zuvor beschriebene Verschlüsselungsverfahren ist asymmetrisch, d.h. ein Geheimnis wird mit
unterschiedlichen Schlüsseln ver- bzw. entschlüsselt. Im Gegensatz zu diesen verwenden symmetrische Verfahren für die Ver- und Entschlüsselung denselben Schlüssel. Diese Verfahren sind
in der Regel effizienter durchführbar als asymmetrische Verfahren, erfordern aber vor ihrer Anwendung den Austausch des Schlüssels auf einem sicheren Kanal. Das Diffie-Hellman-Protokoll
ermöglicht einen solchen sicheren Schlüsselaustausch.
Das Protokoll wurde 1976 entwickelt und wurde ursprünglich für die multiplikative Gruppe
Z∗p beschrieben. Durch eine sogenannte Small-Subgroup Attacke (siehe [KoMe04]) kann die ursprüngliche Variante in kurzer Zeit gebrochen werden. Aus diesem Grund wird eine Untergruppe
G von Z∗p der Ordnung q, wobei q ein Primteiler von p − 1 ist, verwendet.
Initialisierung
1. Alice und Bob wählen zwei öffentlich bekannte Primzahlen p, q ∈R P wobei q|(p − 1) gilt.
2. Alice und Bob einigen sich auf eine öffentlich bekanntes Element α mit folgender Eigenschaft: Jedes von 0 verschiedene Element x ∈ Zp läßt sich schreiben als x = αn für eine
natürliche Zahl n. Wir nennen das Element α eine Primitivwurzel oder auch ein erzeugendes Element von Zp . Für das Protokoll setzen wir g := α(p−1)/q . Wir bezeichnen g als q-te
primitive Einheitswurzel, da offenbar g q = αp−1 = 1 gilt (eine exakte Definition findet sich
in Kapitel 3).
Ablauf
Alice
Bob
x ∈R G
y ∈R G
A = g x MOD p
KAB = (B)x MOD p
A
B
Abb. 1.1: Diffie-Hellman-Protokoll
B = g y MOD p
KAB = (A)y MOD p
1.4 ElGamal-Verfahren
7
Benötigte Hilfsmittel
Zusätzlich zu den bereits aufgelisteten Algorithmen (Zufallszahlengenerator, Primzahlenerzeugung, Langzahlarithmetik und Verfahren zum schnellen Potenzieren), benötigt man beim DiffieHellman einen effizienten Algorithmus zum Auffinden von q-ten primitiven Einheitswurzeln.
1.4 ElGamal-Verfahren
Wie beim RSA-Verfahren handelt es sich beim ElGamal-Verfahren um ein Verschlüsselungs- und
Signaturverfahren. Es wurde 1984 veröffentlicht und basiert auf dem diskreten Logarithmusproblem [ElGa85]. Sämtliche Berechnungen werden in dem endlichen Körper Zp ausgeführt, wobei
p eine hinreichend große Primzahl ist.
Initialisierung
1. Alice wählt ein p ∈R P, sodass p − 1 einen großen Primfaktor besitzt, und eine Primitivwurzel α modulo p. Siehe Abschnitt 3.4 für Details hierzu.
2. Weiters wählt Alice den geheimen Schlüssel xA ∈R [0 : p−2] und berechnet den öffentlichen
Schlüssel yA := αxA MOD p.
Ver- und Entschlüsselung
k MOD p.
• Verschlüsseln: Bob wählt ein zufälliges k ∈R [0 : p − 2] und berechnet K = yA
Wenn m ∈ Zp den Klartext bezeichnet, so gilt für die Verschlüsselungsfunktion E,
E : [0 : p − 1] → [1 : p − 1] × [0 : p − 1], E(m) := (c1 , c2 ), mit den zwei Komponenten
c1 := αk MOD p und c2 := K · m MOD p.
Bemerkung: Der Schlüssel K kann prinzipiell auch für eine beliebige andere symmetrische
Verschlüsselungsfunktion – beispielsweise beim Advanced Encryption Standard (AES) bei
geeigneter Vorverarbeitung von K – eingesetzt werden. Die Verschlüsselungsfunktion kann
daher in ihrer konkreten Ausprägung leicht variieren.
• Entschlüsseln: Wird das Chiffrat (c1 , c2 ) wie oben berechnet empfangen, so gehen wir zum
Entschlüsseln wie folgt vor:
Entschlüsselungsfunktion: D : [1 : p − 1] × [0 : p − 1] → [0 : p − 1]
D(c1 , c2 ) := ((D1 (c1 ))−1 · c2 ) MOD p, wobei D1 : [1 : p − 1] → [0 : p − 1], für D1 (c1 ) gilt
D1 (c1 ) := cx1 A MOD p.
Bemerkung: Analog zum Verschlüsseln kann auch hier der Schlüssel K für eine andere
symmetrische Entschlüsselungsfunktion verwendet werden.
Signaturerstellung und -verifikation
Für das Signieren wird dieselbe Initialisierung wie beim Ver- und Entschlüsseln durchgeführt. Es
sei die Nachricht m ∈ Zp gegeben. Im folgenden wird das Kongruenzzeichen ≡ verwendet. Wenn
angegeben wird x ≡ y (mod n), dann gilt x kongruent y modulo n, also x MOD n = y MOD n.
• Signieren: Alice wählt k ∈R Z∗p−1 und bestimmt r ≡ αk (mod p) (Es kann gezeigt
werden, dass somit auch r eine Primitivwurzel modulo p ist). Alice löst die Kongruenz
m ≡ xA · r + k · s (mod p − 1) nach s und erhält s ≡ k −1 · (m − xA · r) (mod p − 1).
Die Signatur von m ist das Paar (r, s).
8
1
Einführung und Motivation
r ·r s (mod p) gilt. Falls dies zutrifft wird die Signatur
• Verifizieren: Überprüfung ob αm ≡ yA
akzeptiert, andernfalls als ungültig abgelehnt.
Benötigte Hilfsmittel
Analog zum RSA-Verfahren sind auch hier Langzahlarithmetik, Zufallszahlengeneratoren, Primzahlengenerator bzw. -test, der erweiterte Euklidische Algorithmus und ein Verfahren zum
schnellen Potenzieren notwendig. Zusätzlich erfordert das Verfahren auch einen Algorithmus
zum Auffinden von Primitivwurzeln. Wie in Kapitel 2 dargelegt, wird dies durch eine spezielle
Wahl der Primzahl p ermöglicht.
Bemerkung: Um auch hier die Small-Subgroup Attacke zu vermeiden arbeitet man nicht mit
Zp sondern in einer Untergruppe von Zp von primer Ordnung (vgl. Abschnitt 1.3). Es werden
mit dieser Einschränkung aber keine weiteren Algorithmen benötigt, als beim herkömmlichen
ElGamal-Verfahren.
1.5 Shamir’s Secret-Sharing
Wenn bei den bislang vorgestellten (wie auch bei vielen anderen) Verfahren der geheime Schlüssel
verloren geht, kann der Klartext nicht effizient rekonstruiert werden. Damit im Falle des Verlustes
der Klartext wiedergewonnen werden kann, empfiehlt es sich eine Kopie des geheimen Schlüssels
nach seiner Erzeugung sicher zu hinterlegen. Idealerweise in einer Form, die dem Verwahrer des
Geheimnisses (des geheimen Schlüssels) den Zugriff nicht ohne zusätzliche Maßnahmen erlaubt.
Verschlüsselung kann hierfür nicht genutzt werden, da dies lediglich eine “Verschiebung” des
Problems bewirken würde, die Hinterlegung muss daher anders geschehen. Secret-Sharing ist
ein Verfahren, welches die Aufteilung eines Geheimnisses auf mehrere Instanzen ermöglicht,
sodass keine der Instanzen alleine Zugriff auf die Informationen erhalten kann. Erst wenn eine
Mindestanzahl von Instanzen ihre Informationen kombinieren, kann das hinterlegte Geheimnis
offengelegt werden. Die Mindestanzahl kann im Zuge der Initialisierung festgelegt werden. Sie
stellt den Schutz gegen das Bekanntwerden der geheimen Informationen dar, da in diesem Fall
das Vertrauen von einem Geheimnisträger auf mehrere Instanzen aufgeteilt wird.
In Abbildung 1.2 ist der allgemeine Ablauf grob skizziert. Ein Dealer kennt das Geheimnis; aus
diesem berechnet er die sogenannten Shares und verteilt sie. Um das Geheimnis zu rekonstruieren
benötigt man nun eine Teilmenge (mit vordefinierter Mindestgröße) der erzeugten Shares.
Die Grundidee beim Verfahren von Shamir besteht darin, das Geheimnis durch PolynomInterpolation rekonstruierbar zu machen. Der Grad des zu bestimmenden Interpolationspolynoms bestimmt den Schwellwert für die Rekonstruktion. Die einzelnen Shares sind beliebige
Stützstellen dieses Polynoms. Da ein Polynom vom Grad t durch wenigstens t + 1 beliebige
– aber paarweise verschiedene – Punkte eindeutig bestimmt ist, ist die Rekonstruktion des
Polynoms und in weiterer Folge des Geheimnisses durch Kooperation von beliebigen t + 1 SpielerInnen möglich, die ihre Stützstellen zur Verfügung stellen. Jedoch kann keine Teilmenge von
t oder weniger SpielerInnen das Polynom erfolgreich rekonstruieren, da die Eindeutigkeit des
Ergebnisses nicht mehr gewährleistet ist. Die Rekonstruktion geschieht durch Interpolation und
Auswertung des Interpolationspolynoms an der Stelle 0.
1.5 Shamir’s Secret-Sharing
9
Abb. 1.2: Prinzip eines Secret-Sharings Schematas
Initialisierung
1. Dealer wählt das Geheimnis s ∈ Zq , mit q ∈ P und t zufällige Koeffizienten r1 , . . . , rt ∈ Z∗q .
2. Anschließend legt er das Sharing Polynom g(x) = (s + r1 · x + r2 · x2 + . . . + rt · xt ) MOD q
fest, welches für die Erzeugung der Shares verwendet wird.
Sharing
Der Dealer erzeugt n ≥ t + 1 Shares si = g(i) für i = 1, 2, . . . , n und verteilt diese vertraulich
an die einzelnen Instanzen. Die Verteilung muss vertraulich erfolgen, da sonst ein Angreifer
mithören könnte und damit genug Shares für eine Rekonstruktion hätte.
Rekonstruktion
• Es seien m ≥ t + 1 Shares gegeben
• Das Polynom g wird mittels der Interpolationsformel von Lagrange rekonstruiert
g(x) ≡
m
X
i=1
si
m
Y
(x − j) · (i − j)−1 (mod q)
j=1,j6=i
• Das Geheimnis s wird nun berechnet durch s = g(0).
Die Produkte können für x = 0 vorab berechnet und abgespeichert werden, da das Polynom
selber uninteressant ist, und nur der Wert g(0) benötigt wird. Damit reduziert sich die Rekonstruktionsformel zu einer gewichteten Summe und ist somit effizienter berechenbar. Dies wird
im berechneten Beispiel in Kapitel 6 noch genauer erklärt.
Verteilte Addition und Multiplikation
Secret-Sharing wird nicht nur zur Geheimnisverteilung verwendet, es kann auch für verteilte Berechnungen angewandt werden. Dabei nehmen einzelne Instanzen an Berechnungen teil, kennen
10
1
Einführung und Motivation
aber weder das Endergebnis noch Teilergebnisse davon. Einfache Beispiele hierfür sind die verteilte Addition und die verteilte Multiplikation. Bei diesen Anwendungen können die Instanzen
nur im Kollektiv von wenigstens t + 1 SpielerInnen das Ergebnis berechnen, zu kleine Gruppen
können keine (Teil-)Ergebnisse rekonstruieren.
Bei einer verteilten Addition oder Multiplikation mit einer öffentlichen Konstante c muss jede
Instanz die Konstante mit dem eigenen Share multiplizieren bzw. dazu addieren. Wenn s das
Geheimnis ist und si ein Share von s, dann ist si · c ein Share von s · c und si + c ein Share von
s + c. [Lory08]
Bei einer verteilten Addition von zwei, mittels Secret-Sharing verteilten Werten, muss jede Instanz die Shares (der einzelnen Geheimnisse) miteinander addieren. Seien s, t die geheimen
Werte, si sei Share von s und gi Share von t, dann ist si + gi ein Share von s + t. [Lory08]
Die verteilte Multiplikation von zwei geheimen Werten ist etwas aufwändiger. In [Lory07] wird
dafür nicht nur das Protokoll von Gennaro beschrieben, sondern auch ein effizienteres Protokoll
(auf Basis von Newton’s Schema der “dividierten Differenzen”) vorgestellt.
Benötigte Hilfsmittel
Um Shamir’s Secret-Sharing zu realisieren, benötigen wir keine anderen als die bisher genannten
Algorithmen. Sharing funktioniert über beliebigen endlichen Körpern, hier sind also abhängig
von der Anwendung möglicherweise auch allgemeinere Strukturen als Restklassen von Interesse.
2 Grundlagen
In Kapitel 1 wurde eine Auswahl an Algorithmen für die Umsetzung kryptographischer Verfahren
und Protokolle vorgestellt. Im vorliegenden Kapitel werden nun die für die Implementierung
dieser Algorithmen benötigten mathematischen Grundlagen besprochen. Des Weiteren wird ein
Überblick über jene Algorithmen geboten, die im Zuge dieser Diplomarbeit implementiert und
im Demonstrator (Rechner) verwendet wurden.
Behandelt werden Montgomery- und Karatsuba-Multiplikation, Primzahltest nach Miller-Rabin,
Erzeugung starker Primzahlen nach Gordon, erweiterter Euklidischer Algorithmus, Potenzieren
nach Repeated Square and Multiplying und Montgomery, Chinesischer Restsatz nach Garner
und Gauss sowie Faktorisieren nach Pollard-Rho und Berlekamp.
Neben der Definition der Euler’schen ϕ-Funktion, Idee und Zweck der Zufallszahlenerzeugung
werden auch die Rechenregeln für Modulo-Arithmetik vorgestellt.
2.1 Langzahlarithmetik
Häufig beruht die Sicherheit eines Kryptosystems auf der Schwierigkeit, ein – zumeist arithmetisches – Problem in sinnvoller Zeit zu lösen. Nehmen wir die Faktorisierung als Beispiel, so ist das
Auffinden der Primfaktoren der Zahl 221 sehr einfach, während dasselbe Problem für Zahlen
mit mehreren hundert bis tausend Stellen nach heutigem Stand von Technik und Erkenntnis
nicht mehr in sinnvoller Zeit lösbar ist.
Wenn man zwei n-Bit Zahlen nach der Schulmethode (nach [MeVO96, Algorithmus 14.12])
multipliziert, liegt der Algorithmus in der Komplexitätsklasse O(n2 ). Möchte man zwei Zahlen
addieren oder subtrahieren, so kann dies einfach bewerkstelligt werden mit dem Aufwand O(n).
Sollte es zu einem Überlauf kommen (oder bei der Subtraktion zu negativen Zahlen), muss man
lediglich ein einziges Bit (Carry) zusätzlich speichern. Hingegen kann bei einer Multiplikation
von zwei n-Bit Zahlen das Ergebnis bis zu 2 · n Bit lang sein. Um derartige Überläufe zu
vermeiden, rechnet man häufig mit Restklassen und endlichen Körpern (siehe Abschnitt 2.4). Um
Modulo-Multiplikationen auszuführen, wird häufig der Montgomery Algorithmus 2.1 verwendet,
welcher praktischen Angriffen durch Ausspähen der Stromaufnahme ([AcKS06, SaST04]) besser
entgegenwirkt.
Bach und Shallit [BaSh96] geben an, dass man bei gegebenen a und b deren größten gemeinsamen Teiler und die Variablen x und y, sodass ggT(a, b) = a · x + b · y gilt, in O((log a) · (log b))
Bitoperationen berechnen kann. Tabelle 2.1 zeigt man eine Gegenüberstellung der Laufzeitkomplexitäten verschiedener (arithmetischer) Operationen. Es werden Berechnungen von zwei n-bit
langen Zahlen in Z, Berechnungen in Zn und Berechnungen im Körper Fnm gegenübergestellt.
12
2 Grundlagen
Letztere geben die Anzahl der Operationen innerhalb von Zn für die jeweilige Berechnung im
endlichen Körper Fnm an. [BaSh96, MeVO96]
Tab. 2.1: Komplexitäten der Langzahlarithmetik
Berechnungen
Addition
Subtraktion
Multiplikation
Berechnung des größten gemeinsamen Teilers und Bestimmung von multiplikativ inversen Elementen
modulare Exponentation
Laufzeitkomplexität
in Z bzw. Zn
in Fnm
O(log n)
O(m)
O(log n)
O(m)
O((log n)2 )
O(m2 )
2
O((log n) )
O(m2 )
O((log n)3 )
O((log n) · m3 )
2.2 Zufallszahlenerzeugung
Einige Kryptoverfahren verlieren ihre Sicherheit, wenn einfließende Zufallszahlen wiederverwendet werden. Dasselbe geschieht, wenn Zufallszahlen zwischen verschiedenen Instanzen gleich
gewählt werden. In [Scha07] wird gezeigt, wie eindeutige Zufallszahlen einfach generiert werden
können, dazu unten aber mehr.
RSA Labs liefern eine plattformunabhängige Familie an Security-Werkzeugen namens BSAFE
(Verfügbar in C und Java-Bibliotheken) [RSA10]. Diese Bibliotheken liefern (unter anderem)
geeignete Zufallszahlengeneratoren und können auf der Homepage (nach Registrierung) gratis
heruntergeladen werden.
Für die Generierung von Zufallszahlen kann auch das AES Verschlüsselungsverfahren benutzt
werden. Verschlüsselt man beispielsweise einen Zähler (inkrementiert modulo 2128 ) durch AES, so
erhält man eine 128-Bit lange Zufallszahl, wobei die erzeugte Folge die maximale Periodenlänge
von 2128 hat. Doch auch wenn kürzere Zahlen benötigt werden, kann dies mit AES realisiert
werden. Dafür müssen die erhaltenen Bitmuster geeignet gefiltert werden: Sollen beispielsweise
Zufallszahlen der Länge 124 Bit erzeugt werden, so nimmt man nur jene Zahlen, deren ersten
vier Bits gleich Null sind (wie in Abbildung 2.1 dargestellt). Für alle anderen werden neue Zahlen generiert. Dieses Verfahren ist jedoch nur praktikabel für Bitlängen nahe 128. Für eindeutige
Zufallszahlen von kürzerer Länge muss anders vorgegangen werden. Bei den verwendeten Zahlen
werden die führenden Nullen entfernt und nur die übrigen 124 Bits als Zufallszahl genutzt. In
Java werden mit dem ’javax.crypto’ Paket geeignete Klassen mitgeliefert, um AES zu verwenden. In Kapitel 5 wird die Anwendung noch näher erläutert. In [Srin03] wird ferner gezeigt, wie
diese Klassen benutzt und AES angewandt werden kann.
Man kann zwar bewerkstelligen, dass in einem geschlossenen System eine Zufallszahl nur einmal
verwendet wird – sollte eine bereits verwendete Zufallszahl generiert werden, wird sie einfach
nicht verwendet und stattdessen eine neue generiert –, wenn Zufallszahlen aber systemweit
eindeutig sein sollten, so kann man das lokal nicht ohne Weiteres garantieren, da man nicht
alle verwendeten Zufallszahlen kennt. [Scha07] liefert eine einfache Überlegung, wie systemweit
eindeutige Zufallszahlen generieren werden können. Ein Weg, zu garantieren, dass Zufallszahlen
systemweit eindeutig sind, könnte sein, die Zufallszahl mit einer für das System eindeutigen
Zahl zu konkatenieren. Die eindeutige Zahl könnte beispielsweise die Identifikationsnummer einer
2.3 Euler’sche ϕ-Funktion
13
Abb. 2.1: 124 Bit Zufallszahlen
Chip-Karte sein, da diese weltweit einzigartig ist. Sofern sich alle Verfahren daran halten, hätte
man damit eine systemweit eindeutige Zufallszahl generiert. [Scha07]
Um qualitativ hochwertige Zufallszahlen zu generieren, sollten im System äußere physikalische
Einflüsse berücksichtigt werden, wodurch ein Pool an Zufallsbits entsteht. Gute Quellen für solche Pools könnten beispielsweise das Rauschen elektrischer Bauteile (Soundkarte, Videokamera,
etc.), Luftturbulenzen im Festplattengehäuse oder Schwankungen frei laufender Oszillatoren
sein. Schlechte Quellen hingegen sind Systemzeit, Zeitintervalle von Tastaturanschlägen oder
die von Menschen bewirkten Mausbewegungen. Wenn man den Pool mit zufälligen Bits gefüllt
hat, muss bestimmt werden, wieviele Bits in dem Pool tatsächlich voneinander unabhängig und
für den Angreifer nicht zu erraten sind. Danach wird der Pool mit einem geeigneten Algorithmus
durchmischt.
2.3 Euler’sche ϕ-Funktion
Für eine gegebene natürliche Zahl n gibt ϕ(n) an, wieviele zu n teilerfremde natürliche Zahlen
k < n existieren. So ist beispielsweise ϕ(7) = 6, da die Zahlen 1 bis 6 keinen gemeinsamen Teiler
mit 7 haben. Ist n eine Primzahl, so gilt immer ϕ(n) = n − 1.
Laut [Kurz07, Dank06] gelten für ϕ(n) folgende Gesetzmäßigkeiten:
• Für zwei zueinander teilerfremde Zahlen a und b gilt immer, dass ϕ(a · b) = ϕ(a) · ϕ(b).
• Für zwei zueinander teilerfremde Zahlen a und b gilt immer, dass aϕ(b) MOD b = 1.
• Wenn p ∈ P und n ∈ N gilt, dann gilt weiters ϕ(pn ) = pn−1 · (p − 1).
Daraus kann direkt die Formel zur Berechnung von ϕ abgeleitet werden, sofern die PrimfakQk
ei
torzerlegung von n bekannt ist. Sei n =
i=1 pi , wobei pi ∈ P und ei ∈ N gilt, so gilt
Qk
.
ϕ(n) = n · i=1 1 − p−1
i
Im Falle des RSA-Verfahrens, d.h. für den Modulus n = p · q, ist die Faktorisierung bekannt. So
kann ϕ(n) durch (p − 1) · (q − 1) bestimmt werden.
14
2 Grundlagen
Sofern die Faktorisierung von n nicht bekannt ist, kann ϕ(n) nicht effizient ermittelt werden.
Laut [Kurz07][8.2.2] könnte ein Algorithmus, der aus n effizient den Wert ϕ(n) bestimmt, auch
zur Faktorisierung eines RSA-Modulus n = p · q eingesetzt werden.
2.4 Modulo Arithmetik
Wir wissen, dass für zwei beliebige natürliche Zahlen a und b, wobei b < a, stets eindeutige
Zahlen q und r (wobei r < b) existieren, sodass gilt a = q · b + r. Wir nennen r den Divisionsrest
und schreiben r = a MOD b. Besitzen zwei Zahlen a und b den selben Divisionsrest modulo n,
so schreiben wir kurz a ≡ b (mod n). Man beachte hierbei, dass diese Relation keine Gleichheit
zwischen a und b impliziert, sondern lediglich bedeutet, dass die Differenz zwischen a und b von
n geteilt wird, d.h. a ≡ b (mod n) genau dann wenn n|(b − a). Wenn a, n, r, k ∈ N; r < n und
a MOD n = r ⇔ a = k · n + r gilt, so gelten folgende Rechenregeln:
(a · b) MOD n = (ka · n + ra ) · (kb · n + rb ) MOD n
= (ka · kb · n2 + n · ka · rb + n · kb · ra +ra · rb ) MOD n
{z
}
|
≡0
= (ra · rb ) MOD n
= ((a MOD n) · (b MOD n)) MOD n
(a + b) MOD n = (ka · n + ra + kb · n + rb ) MOD n
= (n · (ka + kb ) +ra + rb ) MOD n
|
{z
}
≡0
= (ra + rb ) MOD n
= ((a MOD n) + (b MOD n)) MOD n
Wenn n prim ist, existiert für jedes Element a 6= 0 ein Element x, sodass a · x ≡ x · a ≡ 1 (mod n)
gilt, d.h. es kann auch ein multiplikativ inverses Element bestimmt werden. Der erweiterte Euklidische Algorithmus liefert es explizit: Wir wissen, dass der größte gemeinsame Teiler zweier
Zahlen sich immer als Linearkombination der beiden Zahlen schreiben läßt, d.h.
ggT(x, n) = 1 ≡ a · x + |{z}
b · n MOD n,
≡0
also 1 ≡ a · x (mod n), womit schließlich a ≡ x−1 (mod n) gilt. Sollte es sich bei n um keine Primzahl handeln, so existiert nicht zwingend ein solches Element. So besitzt z.B. 6 kein
multiplikativ inverses Element modulo 9, da ggT(6, 9) = 3 = (−1 · 6 + 1 · 9) MOD 9. Sollte n
keine Primzahl sein, so können dennoch multiplikativ inverse Elemente vorhanden sein. So kann
beispielsweise der größte gemeinsame Teiler von 5 und 9 als ggT(5, 9) = 1 = 5 · 2 + 9 · (−1)
berechnet werden und somit wäre 2 das multiplikative Inverse Element zu 5.
Wie in [Knut01] dargelegt, liegt ein großer Vorteil der Modulo Arithmetik in ihrer hohen Geschwindigkeit. Dies zeigt sich besonders bei der Multiplikation, da hier nie der Wertebereich des
Modulo überschritten werden kann. Im Gegensatz zur ’normalen’ Multiplikation ist das Ergebnis
der Modulo Multiplikation zweier n-stelligen Zahlen immer auch n-stellig. Für Rechenoperationen kann dies dann optimal ausgenützt werden, wenn der Modulo Wert möglichst nahe an der
2.4 Modulo Arithmetik
15
Wortgröße des Prozessors liegt. Bei einem 64-Bit-Rechner würde somit ein Modulo Wert mit
einer 64 Bit-Länge eine optimale Performanz ermöglichen [Knut01]. Neben dem Vorteil, dass
Überläufe kein Problem darstellen, ergeben sich auch keinerlei Rundungsfehler, wie es bei der
Floating-Point Arithmetik vorkommen kann.
Montgomery Multiplikation
Man kann sich leicht davon überzeugen, dass eine Multiplikation zweier n-Bit Binärzahlen
nach der Schulmethode O(n2 ) Bit-Operationen erfordert. Da nach der Standard-Methode keine
Modulo-Reduktion erfolgt besitzt das Ergebnis 2 · n Binärstellen. Die Modulo-Reduktion muss
nachträglich durchgeführt werden und erfordert daher zusätzlichen Aufwand. Der Algorithmus
von Montgomery bewerkstelligt eine Multiplikation mit anschliessender Modulo-Reduktion mit
dem (größenordnungsmäßig gleichen) Aufwand O(n2 ).
Der Algorithmus 2.1 benötigt als Eingabeparameter die Zahlen x, y und m, wobei diese in Binärdarstellung vorhanden sein müssen. Die Binärdarstellung von x wird als x = (xn−1 · · · x1 x0 )2
angegeben, wobei n ∈ N die Anzahl der Binärstellen entspricht. Mittels xi (i ∈ N, i < n) wird
auf den i-ten Wert der Binärdarstellung von x zugegriffen. Als Ergebnis liefert der Algorithmus
das Produkt x · y · R−1 MOD m zurück, wobei R = bn , b = 2 und n die Bitlänge der Zahlen m, x
und y ist.
Bei einer Multiplikation x · y MOD m (siehe Algorithmus 2.1) gilt laut [MeVO96]: Wenn
A + xi · y + ui · m (xi ist die i-te Stelle der binären Darstellung von x, weiters werden ui
und A in Zeile 4 bzw. Zeile 5 berechnet) ein Vielfaches von b ist, dann benötigt man nur eine
Rechtsverschiebung um eine Division durch b durchzuführen. Da die Schleife n-mal durchlaufen
wird, werden bei diesem Algorithmus 2 · n · (n + 1) Operationen ausgeführt. Daraus ergibt sich
eine Komplexität von O(n2 ).
Algorithmus 2.1 Montgomery Multiplikation [MeVO96]
Multipliziert zwei Zahlen und liefert das Produkt.
m = (mn−1 · · · m1 m0 )b , x = (xn−1 · · · x1 x0 )b , y = (yn−1 · · · y1 y0 )b
0 ≤ x, y < m, R = bn : ggT(m, b) = 1, m′ = −m−1 MOD b (Zahlenbasis b = 2).
Output: x · y · R−1 MOD m.
1: function Montgomery(m, x, y)
2:
A := 0
⊲ A = (an an−1 · · · a1 a0 )b
3:
for i := 0, . . . , n − 1 do
4:
ui := (a0 + xi · y0 ) · m′ MOD b
5:
A := (A + xi · y + ui · m) · b−1
6:
end for
7:
if A ≥ m then
8:
A := A − m
9:
end if
10:
return A
11: end function
Zweck:
Input:
16
2 Grundlagen
Karatsuba-Ofman Multiplikation
Wie soeben dargelegt wurde, berechnet Algorithmus 2.1 das Produkt zweier n-Bit Binärzahlen
mit anschließender Modulo-Reduktion in O(n2 ) Operationen. Karatsuba und Ofman reduzierten
diese Komplexität 1962 auf O(nlog2 (3) ) = O(n1.58 ) [KaOf63]. Dies erreichten Sie, indem sie
eine Multiplikation zweier 2 · n-Bit Binärzahlen durch 3 Multiplikationen von n-bit Binärzahlen
realisierten. Wenn die Karatsuba-Ofman Multiplikation rekursiv angewendet wird, braucht der
Algorithmus nur 3 · n einstellige Multiplikationen, um zwei 2 · n-stellige Zahlen zu multiplizieren.
Dies stellt eine signifikante Verbesserung im Vergleich zu den 4 · n einstelligen Multiplikationen
dar, welche mit der Schulmethode notwendig sind [Knut01, LiHL03, Bart03].
Die Idee und Verbesserung sollte im folgenden Beispiel erläutert werden: Es sei x = a1 · 2n + a0
und y = b1 · 2n + b0 , dann ist nach der Schulmethode
x · y = (a1 · 2n + a0 ) · (b1 · 2n + b0 )
= a1 · b1 · 22·n + 2n · (a1 · b0 + a0 · b1 ) + a0 · b0 ,
man benötigt also 4 Multiplikationen. Der Trick von Karatsuba war es nun, das Produkt
(a1 + a0 ) · (b1 + b0 ) = a1 · b1 + a1 · b0 + a0 · b1 + a0 · b0 zu berechnen, und dann die sowieso
benötigten Produkte a1 · b1 und a0 · b0 einfach abzuziehen.Die Produktbildung läuft demnach
wie folgt ab:
1. Berechne a1 · b1 (1 Multiplikation)
2. Berechne a0 · b0 (1 Multiplikation)
3. Berechne (a1 + a0 ) · (b1 + b0 ) − a0 · b0 − a1 · b1 = a0 · b1 + a1 · b0 (1 Multiplikation).
So können in nur 3 Multiplikationen alle benötigten Terme berechnet werden.
Laut [Heun03] braucht man – um zwei n-stellige Binärzahlen zu multiplizieren – mit der Schulmethode höchstens 2·n2 −3·n Bit Operationen, beim Karatsuba-Ofman Algorithmus aber lediglich
41·nlog2 (3) . Heun [Heun03] gibt außerdem eine modifizierte Variante des Karatsuba-Ofman Algorithmus an, welcher 11·nlog2 (3) Bit Operationen benötigt. Damit ist der Karatsuba-Ofman Algorithmus bereits ab etwa 65 Bit langen Zahlen effizienter als die Schulmethode [LiHL03, Bern97].
In [LiHL03] werden nicht nur mehrere Versionen des Karatsuba-Ofman Algorithmus angegeben,
es wurde zudem eine Auswertung erstellt, wie schnell die einzelnen Verfahren – im Vergleich zur
Schulmethode – sind.
Neben den hier vorgestellten Algorithmen sollte noch der Toom-Cook-Algorithmus (Laufzeit
O(n1+ε ) für ε > 0, dieser ist für große Zahlen laut [BGTZ08] um 17% schneller als der
Karatsuba-Ofman Algorithmus) und der Schönhage-Strassen-Algorithmus [ScSt71, GaKZ07]
(Laufzeit O(n log(n) log log(n))) angeführt werden. Diese führen zur Multiplikation noch eine
Modulo-Reduktion durch.
2.5 Primzahltest
Ein aus Effizienzgründen häufig eingesetzter Primzahltest ist der Miller-Rabin Algorithmus. Dieser gehört zu den probabilistischen Primzahltests, d.h. eine Zahl, welche von dem Algorithmus
als prim klassifiziert wird, kann mit geringer Wahrscheinlichkeit auch zusammengesetzt sein.
2.5 Primzahltest
17
Entscheidet der Algorithmus hingegen, dass eine Zahl zusammengesetzt ist, so trifft dies beweisbar sicher zu. Beim Miller-Rabin Algorithmus wird daher nicht nur die zu testende Zahl n als
Eingangsparameter benötigt, sondern auch ein Sicherheitsparameter t, welcher die Irrtumswahrscheinlichkeit bestimmt. Ein Nachweis dieser Behauptung ist jedoch nicht trivial und erfordert
einige Vorarbeit. Nehmen wir an, X wäre das Ereignis, dass n zusammengesetzt ist und Y ist das
Ereignis, dass der Algorithmus n als Primzahl identifiziert. Relevant ist nun die Irrtumswahrscheinlichkeit P (X|Y ). Manche Quellen, wie beispielsweise [KaKi10, Will08], geben P (X|Y ) des
Algorithmus mit 4−t an. Es wird angenommen, dass die Zahl n aus einer Menge S gewählt wird
und dass 0 < p < 1 gilt, wobei p die Wahrscheinlichkeit ausdrückt, dass n eine Primzahl ist (dies
(Y |X)
1 1 t
ist abhängig von der Menge S) [MeVO96]. Somit gilt P (X|Y ) = P (X)P
≤ PP(Y(Y|X)
P (Y )
) ≤ p 4
da P (Y ) ≥ p ist (Der Algorithmus kann manche zusammengesetzte Zahlen irrtümlich als prim
klassifizieren.). Den allgemeinen Algorithmus liefert [MeVO96], dieser wird in Algorithmus 2.2
dargestellt. Eine Version für Java liefert [Pepp07].
Algorithmus 2.2 Miller-Rabin Primzahltest
Zweck:
Dieser Algorithmus testet ob die übermittelte Zahl eine Primzahl ist.
Input:
n ≥ 3 die zu prüfende Zahl, t ≥ 1 Sicherheitsparameter.
Output: “zusammengesetzt” oder “prim”.
1: function MillerRabin(n, t)
2:
Berechne s und r, dass n − 1 = 2s · r und r ungerade ist.
3:
for i := 1, . . . , t do
4:
a ∈R {2, 3, . . . , n − 1}
5:
y := ar MOD n
⊲ Algorithmus 2.6
6:
if y 6= 1 and y 6= n − 1 then
7:
j := 1
8:
while j ≤ s − 1 and y 6= n − 1 do
9:
y := y 2 MOD n
10:
if y = 1 then
11:
return “zusammengesetzt”
12:
end if
13:
j := j + 1
14:
end while
15:
if y 6= n − 1 then
16:
return “zusammengesetzt”
17:
end if
18:
end if
19:
end for
20:
return “prim”
21: end function
Einige Verfahren erfordern die (zufällige) Wahl mehrerer Primzahlen, die aus Sicherheitsgründen
paarweise verschieden zu wählen sind. Wir illustrieren diesen Umstand am Beispiel des RSAVerfahrens: Wenn hier bei zwei unterschiedlichen Instanzen (Alice und Bob) zur Berechnung
von n zwei gleiche Primzahlen benutzt werden (nA = p · q und nB = q · r), kann man mittels
Euklidischem Algorithmus einfach den größten gemeinsamen Teiler bilden (q = ggT(nA , nB ))
18
2 Grundlagen
und damit die Primzahlen ermitteln (p =
unsicher.
nA
q
und r =
nB
q ).
Somit wäre die Verschlüsselung
Häufig wählt man Primzahlen, bei denen die Faktorisierung von p − 1 gewissen Anforderungen genügt. Besitzt p − 1 beispielsweise selbst einen großen Primfaktor liegt eine sogenannte
sichere Primzahl (safe prime) vor. Weitere Forderungen führen zu sogenannten starken Primzahlen (strong primes), welche für die Implementierung von Kryptoverfahren häufig Anwendung
finden [Gord84]. Eine Primzahl p heißt starke Primzahl, wenn folgendes gilt:
• p − 1 hat einen großen Primfaktor r.
• p + 1 hat einen großen Primfaktor s.
• r − 1 hat einen großen Primfaktor t.
Algorithmus 2.5 wird in [MeVO96] angegeben und generiert starke Primzahlen. Der Algorithmus braucht keine Eingabeparameter und liefert eine zufällige Starke Primzahl zurück. In Zeile 2
werden zwei zufällige Primzahlen benötigt. Hier kann der Miller-Rabin Primzahltest (siehe Algorithmus refalg:millerRabin) benutzt werden. Dafür werden zufällige ungerade Zahlen getestet
ob sie durch bekannte Primzahlen teilbar sind, anschließend wird mittels Miller-Rabin Primzahltest getestet ob die Zahl prim ist oder nicht. Sollte der Miller-Rabin Primzahltest ausgeben,
dass es sich um eine Primzahl handelt, wird diese verwendet, ansonsten wird erneut eine neue
Zufallszahl getestet (vgl. [MeVO96][Algorithm 4.44]).
Algorithmus 2.3 Algorithmus nach Gordon
Zweck:
Algorithmus um Starke Primzahlen zu generieren.
Input:
keine.
Output: Starke Primzahl.
1: function Strong-primes
2:
s, t ∈R P
3:
i ∈R N
4:
while 2 · i · t + 1 ∈
/ P do
5:
i := i + 1
6:
end while
7:
r := 2 · i · t + 1
8:
p0 := 2 · (sr−2 MOD r) · s − 1
9:
j ∈R N
10:
while p0 + 2 · j · r · s ∈
/ P do
11:
j := j + 1
12:
end while
13:
p := p0 + 2 · j · r · s
14:
return p
15: end function
⊲ Algorithmus 2.2
⊲→r∈P
⊲ Algorithmus 2.2
In Java kann man sich der BigInteger Klasse bedienen, um (probabilistisch) zu testen ob eine
Zahl prim ist. Die Methode dafür heißt ’isProbablePrime(int t)’ und benötigt einen Sicherheitsparameter t mit folgender Interpretation: Sollte die Methode zurückgeben, dass es sich um
eine Primzahl handelt, ist die Zahl mit der Wahrscheinlichkeit 1 − 21t prim. Die Ausführungszeit steigt proportional zu t. Weiters wird die Methode ’probablePrime(int, Random)’ angebo-
2.6 Erweiterter Euklidischer Algorithmus
19
ten, mit welcher Primzahlen erzeugt werden können. Hier müssen die gewünschte Bitlänge und
ein Random-Objekt mitgeliefert werden, zurückgegeben wird ein BigInteger-Objekt welches eine
Primzahl repräsentiert. Außerdem gibt es die Methode ’nextProbablePrime()’ welche – ausgehend von der aktuellen Zahl – die nächst größere Primzahl zurück gibt. Laut [SUN10a] gilt eine
Wahrscheinlichkeit, dass die zurückgelieferte Zahl zusammengesetzt ist, von 2−100 . Außerdem
wird keine Primzahl übersprungen. So gilt es, wenn diese Methode die Primzahl p liefert, dass
keine Primzahl q existiert, sodass this < q < p gilt.
Der Miller-Rabin Algorithmus kann nicht mit Sicherheit entscheiden, ob es sich bei einer Zahl
um eine Primzahl handelt. Genauso gibt es bei den von Java angebotenen Methoden keine
absolute Sicherheit. Lange war man sich nicht sicher, ob es einen Algorithmus geben kann,
der in Polynomialzeit entscheidt, ob es sich bei einer Zahl mit Sicherheit um eine Primzahl
handelt. [AgKS04] liefert einen Algorithmus und beweist, dass man die Entscheidung sehr wohl
in Polynomialzeit treffen kann. Laut [AgKS04] besitzt der Algorithmus eine Laufzeitkomplexität
von O((log n)6 ).
2.6 Erweiterter Euklidischer Algorithmus
Wie in Kapitel 1 erwähnt erfordern einige Verfahren die Bestimmung von multiplikativ inversen
Elementen. Dieses wird mittels des erweiterten Euklidischem Algorithmus berechnet.
In [Ries87] ist der Euklidische Algorithmus zur Bestimmung des größten gemeinsamen Teilers
zweier ganzer Zahlen zu finden. Bei der ursprünglichen Variante des Algorithmus subtrahierte
man die kleinere Zahl von der größeren bis eine davon 0 war, und erhielt so den größten gemeinsamen Teiler. Wenn die Zahlen bei diesem Algorithmus unterschiedlich groß sind, werden sehr
viele Subtraktionen notwendig. Dieses Problem kann umgangen werden indem die Subtraktion
durch eine Modulo-Reduktion ersetzt wird. Dies wird in Algorithmus 2.4 dargestellt.
Algorithmus 2.4 Euklidischer Algorithmus (Rekursiv)
Zweck:
Auffinden des größten gemeinsamen Teilers.
Input:
Zahlen a und b: a ≥ b.
Output: größter gemeinsamer Teiler von a und b.
1: function Euklid(a, b)
2:
if b = 0 then
3:
return a
4:
else
5:
return Euklid(b, a MOD b)
6:
end if
7: end function
Einen erweiterten Euklidischen Algorithmus für Zahlen liefert [MeVO96], dieser ist in Algorithmus 2.5 angeführt. Bei diesem Algorithmus wird nicht nur der größte gemeinsame Teiler sondern
auch die multiplikativ inversen Elemente der zwei Zahlen zurückgeliefert. Beim Aufruf mit den
Werten (a, b) liefert der Algorithmus somit ein Triple (d, x, y) zurück. Der Wert d entspricht
dem größten gemeinsamen Teiler von a und b. Die multiplikativen inversen Elemente von a und
b sind x und y.
20
2 Grundlagen
Algorithmus 2.5 Erweiteter Euklidischer Algorithmus
Berechnen des größten gemeinsamen Teilers und der multiplikativ inversen Elemente.
Input:
Zahlen a ∈ N und b ∈ N für die a ≥ b gilt.
Output: (d, x, y) wobei ggT(a, b) = d = a · x + b · y.
1: function ErwEuklid(a, b)
2:
if b = 0 then
3:
d := a; x := 1; y := 0;
4:
return (d, x, y)
5:
end if
6:
x2 := 1; x1 := 0; y2 := 0; y1 := 1;
7:
while b > 0 do
8:
q := ⌊a/b⌋; r := a − q · b; x := x2 − q · x1 ; y := y2 − q · y1 ;
9:
a := b; b := r; x2 := x1 ; x1 := x; y2 := y1 ; y1 := y;
10:
end while
11:
d := a; x := x2 ; y := y2 ;
12:
return (d, x, y)
13: end function
Zweck:
Die BigInteger Klasse in Java bietet auch einen geeignete Methode gcd(BigInteger val), welche
den größten gemeinsamen Teiler als BigInteger zurückliefert [SUN10a]. Für die Berechnung des
Multiplikativen Inversen stellt die Klasse eine Methode modInverse(BigInteger m) bereit. Diese
Methode berechnet das multiplikativ inverse modulo m der durch das Objekt repräsentierten
Restklasse.
2.7 Verfahren zum schnellen Potenzieren
Wie bereits in Kapitel 1 gezeigt wurde, ist es bei verschiedenen Verfahren notwendig, große
Zahlen zu potenzieren. In weiterer Folge werden Vor- und Nachteile zweier Algorithmen dargelegt, die dabei helfen, diese Potenzierung effizient zu bewerkstelligen. Im Folgenden betrachten
wir Repeated Square and Multiplying und Montgomery-Potenzierung. Der Repeated Square and
Multiplying Algorithmus ist sehr bekannt und weit verbreitet. Es wird aber noch gezeigt werden,
dass dieser Algorithmus angegriffen, und somit die Potenz eruiert werden kann. Dieser Angriff
wird bei der Montgomery-Potenzierung erschwert.
Repeated Square and Multiplying
Repeated Square and Multiplying ist ein sehr effizienter Algorithmus für die Potenzierung großer
Zahlen. Es gibt jedoch mittlerweile ein paar Angriffe auf dieses Verfahren: [AcKS06] zeigt, dass
man durch Messen der Stromaufnahme – bei einem Microprozessor – erkennen kann, an welchen
Stellen in der Schleife mehr Strom benötigt wurde. In Algorithmus 2.6 ist ersichtlich, dass Zeile 6
nur ausgeführt wird, wenn das aktuell betrachtete Bit in d eine 1 ist. Da die Stromaufnahme
bei so einem Durchlauf größer ist, kann durch einen AngreiferIn erkannt werden, an welchen
Stellen im Bitmuster d eine 1 hat. Somit kann d ermittelt werden, wodurch die Berechnung
unsicher wird. Zur Erinnerung: Bei der Entschlüsselungsfunktion des RSA Verfahrens wird das
2.7 Verfahren zum schnellen Potenzieren
21
Geheimnis cd MOD n berechnet. Wenn durch Beobachtung der geheime Schlüssel d ausspioniert
werden kann, können alle Nachrichten entschlüsselt werden.
Algorithmus 2.6 Repeated Square and Multiply
Zweck:
Effiziente Berechnung der Potenz.
Input:
c ∈ N, d = (dn−1 · · · c1 d0 )2 , N ∈ N.
Output: cd MOD N .
1: function squareNmultiply(c, d, N )
2:
y := c
3:
m := 1
4:
for k = 0, . . . , n − 1 do
5:
if dk = 1 then
6:
m := (m · y) MOD N
7:
end if
8:
y := (y · y) MOD N
9:
end for
10:
return m
11: end function
⊲ Multiply
⊲ Square
Balanced Montgomery Powering Ladder
Einem Angriff wie in Repeated Square and Multiplying – durch Messen der Stromaufnahme –
wird entgegengewirkt, wenn in der Schleife immer Berechnungen mit gleicher Stromaufnahme
durchgeführt werden. Ob die Potenz im Algorithmus 2.7 an der Stelle eine 1 oder eine 0 besitzt
ist irrelevant, da immer gleich viele Operationen ausgeführt werden. Die Verzweigung des Algorithmus in Zeile 5 führt in beiden Richtungen gleich viele (und gleich komplexe) Operationen
aus [AcKS06]. Laut [AcKS06] funktioniert der Algorithmus wie in 2.7 beschrieben.
Algorithmus 2.7 Balanced Montgomery Powering Ladder
Zweck:
Berechnung der Potenz.
Input:
c ∈ N, d = (dn−1 · · · c1 d0 )2 , N ∈ N.
Output: cd MOD N .
1: function Montgomery(c, d, N )
2:
R0 := 1
3:
R1 := c
4:
for i := 0, . . . , n − 1 do
5:
if di = 0 then
6:
R1 := (R0 · R1 ) MOD N
7:
R0 := (R0 · R0 ) MOD N
8:
else
9:
R0 := (R0 · R1 ) MOD N
10:
R1 := (R1 · R1 ) MOD N
11:
end if
12:
end for
13:
return R0
14: end function
22
2 Grundlagen
2.8 Chinesischer Restsatz
Wir haben gesehen, dass beim RSA Verfahren zum Entschlüsseln einer Nachricht die Berechnung
m = cd MOD n durchgeführt werden muss. Um diese Berechnung zu beschleunigen, kann der
Chinesische Restsatz angewandt werden. Damit ändert sich zwar nicht die Komplexitätsklasse,
aber die Berechnungszeit reduziert sich auf ein Viertel [MeVO96].
Im Folgenden wird vorausgesetzt, dass die Moduli m1 , . . . , mn paarweise teilerfremd sind. Der
Chinesische Restsatz (CRS) trifft folgende Aussage:
Es seien m1 , m2 , . . . , mk ∈ N + 1 mit ggT(mi , mj ) = 1 für i 6= j und a1 , a2 , . . . , ak ∈ Z,
k ∈ N + 1. Dann existiert genau ein x ∈ [0 : m − 1] mit x ≡ ai (mod mi ), i ∈ [1 : k] und
m = m1 · m2 · · · mk .
Seien a, b ∈ Z und m ∈ N so nennen wir Gleichungen der Form a · x ≡ b (mod m) lineare
Kongruenzen mit der Unbestimmten x. Eine solche Kongruenz ist eindeutig nach x lösbar,
wenn ggT(a, m) = 1. Mit x = x1 + j · m und j ∈ Z sind alle Lösungen (in Z) bestimmt.
Andernfalls, d.h. wenn ggT(a, m) > 1 gilt, existieren genau ggT(a, m) verschiedene Lösungen.
Angewendet auf das RSA Verfahren bedeutet dies, dass die Paare (c1 , d1 ) mit c1 = c MOD p
und d1 = d MOD (p − 1) bzw. (c2 , d2 ) mit c2 = c MOD q und d2 = d MOD (q − 1) berechnet
werden und die lineare Kongruenz m1 = cd11 MOD p und m2 = cd22 MOD q gelöst wird. Die zwei
Parameter p und q müssen geheim verwaltet werden und dienen ausschließlich zur Lösung der
linearen Kongruenzen.
In [MeVO96] wird der Algorithmus 2.8 angegeben. Der sogenannte Garners Algorithmus dient
der (effizienten) Bestimmung von x, wobei 0 ≤ x < M , x ≡ vi (mod mi ) und 1 ≤ i ≤ t
verlangt wird. Alternativ hierzu existiert auch der sogenannte Gauss’sche Algorithmus, welcher
in [MeVO96] angegeben wird. Dieser leitet sich direkt aus dem Beweis des chinesischen Restsatzes ab. Der hier angegebene Algorithmus ist effizienter als jener von Gauss. Der Algorithmus von
Gauss benötigt zusätzlich zum Garners Algorithmus eine Modulo Reduktion. Bei der Annahme,
dass der Modulus M eine k · t-Bit und mi eine k-Bit lange Zahl ist, werden O((k · t)2 ) Bitoperationen bei einer Reduktion von M benötigt, dagegen nur O(k 2 ) Bitoperationen bei einer
Reduktion von mi . Sofern der Garners Algorithmus nur Modulus Reduktionen nach mi (mit
2 ≤ i ≤ t) durchführt, werden O(t · k 2 ) Bitoperationen benötigt. [MeVO96]
Zur Illustration des Lösungsvorganges einer simultanen Kongruenz eignet sich der GaussAlgorithmus besser als Garners Verfahren. Wir geben den Algorithmus von Gauss hier nicht
explizit an, sondern illustrieren ihn der Vollständigkeit halber an einem kleinen Beispiel:
Die simultane Kongruenz
x ≡ 1 (mod 5)
x ≡ 3 (mod 4)
x ≡ 2 (mod 3)
soll gelöst werden. Also wird m1 = 5, m2 = 4, m3 = 3, a1 = 1, a2 = 3, a3 = 2 und das Produkt
m = m1 · m2 · m3 = 60 bestimmt. Weiters wird M1 = m/m1 = 12, M2 = m/m2 = 15 und
M3 = m/m3 = 20 berechnet. Es wird y1 · M1 ≡ 1 (mod m1 ) gelöst, indem y1 · 12 ≡ 1 (mod 5)
also 2 · y1 ≡ 1 (mod 5) bestimmt wird. Die kleinste positive Lösung ist y1 = 3. Des Weiteren
2.9 Faktorisieren
23
Algorithmus 2.8 Garners Algorithmus
Dieser Algorithmus zur Lösung linearer Kongruenzen nach dem Chinesischen
Restsatz, dient der (effizienten) Bestimmung von x, wobei 0 ≤ x < M ,
x ≡ vi (mod mi ) und 1 ≤ i ≤ t verlangt wird.
Q
Input:
M = tt=1 mi > 1 mit (mi , mj ) = 1 für alle i 6= j, v(x) = (v1 , v2 , . . . , vt ).
Output: x.
1: function GarnersCRS(M, v(x))
2:
for i := 2, . . . , t do
3:
Ci := 1
4:
for j := 1, . . . , (i − 1) do
5:
u := m−1
⊲ Algorithmus 2.5
j MOD mi
6:
Ci := u · Ci MOD mi
⊲ Algorithmus 2.1
7:
end for
8:
end for
9:
u := v1
10:
x := u
11:
for i := 2, . . . , t do
12:
u := (vi − x)Q· Ci MOD mi
13:
x := x + u · i−1
j=1 mj
14:
end for
15:
return x
16: end function
Zweck:
liefert y2 · M2 ≡ 1 (mod m2 ) das Ergebnis y2 · 15 ≡ 1 (mod 4) und somit ein y2 = 3. Außerdem
gilt für y3 · M3 ≡ 1 (mod m3 ), dass y3 · 20 ≡ 1 (mod 3) und somit die kleinste nicht negative
Lösung y3 = 2 ist. Daraus ergibt sich, dass
x ≡ 1 · 12 · 3 + 3 · 15 · 3 + 2 · 20 · 2 ≡ 11 (mod 60)
und somit x = 11 als Lösung der simultanen Kongruenz gilt. [Buch04]
2.9 Faktorisieren
Trotz erheblicher Anstrengungen konnte bis heute kein Algorithmus entwickelt werden, welcher
in der Lage ist, eine beliebig große Zahl n mit dem Aufwand O((log n)α ) für ein von n unabhängiges α, zu faktorisieren. Es gilt, dass jede natürliche Zahl n ∈ N in (bis auf die Reihenfolge der
Q
Faktoren) eindeutiger Art und Weise als Produkt von Primzahlen n = ki=1 pei i , wobei pi ∈ P
und ei ∈ N für alle i = 1, 2, . . . , k dargestellt werden kann (Fundamentalsatz der Arithmetik).
Um n zu faktorisieren können verschiedene Algorithmen verwendet werden. Im implementierten Stackrechner (siehe Abschnitt 5.3) wurde keine Funktion zum Faktorisieren vorgesehen, es
wurde aber der Pollard’s ρ-Algorithmus (nach [Ries87] und [GaGe03]) in der zu Grunde liegenden Java-Bibliothek implementiert. Auf diesbezügliche Details wird aber in Abschnitt 5.2 noch
näher eingegangen.
Ein einfacher Faktorisierungsalgorithmus wird in Abbildung 2.2 nach [Knut01] skizziert. Hier
wird die zu faktorisierende Zahl solange durch Primzahlen (p = 2, 3, 5, . . .) geteilt, bis der Rest
0 ist. Wenn der Rest 0 ist, hat man einen Primfaktor gefunden. Man reduziert nun die aktuelle
24
2 Grundlagen
Zahl um den gefundenen Primfaktor und rechnet mit dem Quotienten weiter, bis man wieder
einen Primfaktor gefunden hat. Der Algorithmus terminiert, wenn die Zahl n = 1 oder eine
Primzahl ist. Wir nennen diesen einfachen Algorithmus “trial-division”.
Abb. 2.2: Einfacher Faktorisierungsalgorithmus
Analog zu Primzahlen, welche nicht als Produkt kleinerer Zahlen dargestellt werden können,
gestatten manche Polynome ebenso keine Darstellung als Produkt von Polynomen kleineren
Grades. Wir nennen solche Polynome irreduzibel. Sie spielen eine ähnliche Rolle wie Primzahlen
und erlauben auch die Formulierung eines Faktorisierungsproblems für Polynome in analoger
Weise zum Faktorisierungsproblem für ganze Zahlen. Anders als bei der Faktorisierung von
Zahlen ist die Faktorisierung von Polynomen jedoch mit polynomialem Aufwand möglich. Es
kann jedes Polynom a(x) ∈ K[x] als Produkt irreduzibler Polynome pk (x) (k = 1, . . . , n) in
folgender Form dargestellt werden [Koep06, Ries87]:
a(x) =
n
Y
k=1
pk (x) =
m
Y
ql (x).
l=1
Ein irreduzibles Polynom zeichnet sich dadurch aus, dass es sich nicht als Produkt von Polynomen kleineren Grades anschreiben lässt [Dank06].
Laut [Wern08] handelt es sich bei einem primitiven Polynom um ein irreduzibles Polynom p(x)
mit Grad m, wenn die kleinste ganze Zahl n für die p(x) das Polynom xn + 1 ohne Rest
teilt n = 2m − 1 ist. In [Koep06] wird gezeigt, dass das Produkt zweier primitiver Polynome
a(x), b(x) ∈ Z[x] ebenfalls primitiv ist.
Beim Polynom f ∈ Zp [x] handelt es sich um ein quadratfreies Polynom, wenn es kein Polynom
p ∈ Zp [x] mit einem Grad ≥ 1 gibt, sodass p(x)2 |f (x) gilt.
Wenn ein Polynom g mit der Eigenschaft (g(x))p ≡ g(x) (mod xn + x + a) vorliegt, dann wird
weiters gezeigt, dass es eine Faktorisierung der Form
xn + x + a =
Y
ggT(xn + x + a, g(x) − s)
0≤s<p
gibt, welche genau dann, wenn der Grad von g(x) < 0 ist, nicht trivial ist.
2.9 Faktorisieren
25
Pollard’s ρ-Algorithmus
In [Ries87] wird der Algorithmus 2.9 angegeben, um Zahlen mit dem Pollard ρ-Algorithmus
zu faktorisieren. Der Algorithmus benötigt drei Eingangsparameter: die zu faktorisierende Zahl
n, ein Zufallszahl a und einen Anfangswert zum Suchen x1 . Es ist durchaus möglich, dass der
Algorithmus keine Faktorisierung findet. In diesem Fall muss der Algorithmus mit einem anderen
Wert für a wiederholt werden.
Algorithmus 2.9 Pollard’s ρ-Algorithmus
Faktorisiert eine Zahl.
n ∈ N Zahl die zu Faktorisieren ist, a ∈ N Zufallszahl, x1 ∈ N Anfangswert zum
Suchen.
Output: gefundene Faktoren von n oder Text “keinen Faktor gefunden”.
1: function Pollard(a, x1 , n)
2:
if a 6= 0 then
3:
x := x1 ; y := x1 ; q := 1
4:
for i := 1, . . . , 10000 do
5:
x := (x · x − a) MOD n; y := (y · y − a) MOD n
6:
y := (y · y − a) MOD n; q := q · (y − x) MOD n
7:
if i MOD 20 = 0 then
8:
p := ggT(q, n)
9:
if p > 1 then
10:
while n MOD p = 0 do
11:
p als Faktor gefunden, speichern und weitersuchen.
12:
n := n/p
13:
end while
14:
end if
15:
end if
16:
end for
17:
end if
18:
if p > 0 then
19:
return p
20:
else
21:
return “keinen Faktor gefunden”
22:
end if
23: end function
Zweck:
Input:
Berlekamp-Algorithmus für das Faktorisieren von quadratfreien Polynomen
Mit Hilfe des Algorithmus aus [MeVO96, Algorithmus 3.110] ist es effizient möglich, ein Polynom
in ein Produkt von quadratfreien Polynomen zu zerlegen. Laut [MeVO96] müssen nachdem die
Q
quadratfreie Faktorisierung von f (x) = ki=1 fi (x)i gefunden wurde, die quadratfreien Polynome
f1 (x), f2 (x), . . . , fk (x) noch weiter faktorisiert werden um die gesamte Faktorisierung von f (x)
zu erhalten. Diese letzten Faktorisierungsschritte schafft der Berlekamp-Algorithmus.
Laut [MeVO96] ist die Laufzeit des Berlekamp-Algorithmus um ein quadratfreies Polynoms
vom Grad m über Zp [x] zu Faktorisieren mit O(m3 + t · p · m2 ) Operationen in Zp [x] bestimmt,
wobei t die Anzahl der irreduziblen Faktoren ist. Somit ist mit Hilfe des Berlekamp-Algorithmus
26
2 Grundlagen
(siehe [MeVO96, Algorithmus 3.111]) ein effizientes Faktorisieren in Zp [x] möglich, solange p
klein gehalten wird.
Seien p ∈ P und a(x) ∈ Zp [x] vom Grad n. Dann liefert der Berlekamp-Algorithmus eine
nichttriviale Faktorisierung von a(x), sofern eine solche existiert. Dieser Algorithmus kann gegebenenfalls rekursiv weitergeführt werden [Koep06].
Shor-Algorithmus
Shor [Shor94] hat einen Algorithmus angegeben, welcher die Faktorisierung einer n-Bit Zahl
mit in log(n) polynomialem Aufwand auf einem Quantencomputer bewerkstelligen kann. Der
Shor-Algorithmus wurde für einen Quantencomputer entwickelt. Da im Gebiet der Quantenrechner noch sehr viel Forschungsarbeit geleistet werden muss und bis jetzt nur Quantencomputer
gebaut werden konnten die sich im experimentellen Stadium befinden (und es auch nicht absehbar ist, wann ein anwendbarer Quantencomputer gebaut werden kann), wird in dieser Arbeit
ausschließlich auf Algorithmen eingegangen, welche auf klassischen Rechnern ausführbar sind.
Sollte es einmal Quantencomputer geben,dann müssen neue Kryptoverfahren und -protokolle
entwickelt werden, da beispielsweise das Faktorisierungsproblem gelöst und das RSA Verfahren somit nicht mehr sicher wäre. [Hall07] zeigt, dass der Shor Algorithmus nicht der einzige
Algorithmus ist, der bis jetzt nicht effizient lösbare Probleme mittels Quantencomputer zu in
Polynomialzeit lösbaren Problemen macht. In [Shor03] kann auch nachgelesen werden, warum
es bis jetzt noch wenige Algorithmen für Quantencomputern gibt.
3 Endliche Körper
Um zur Definition eines endlichen Körpers zu gelangen, muss vorab geklärt werden was ein
Körper ist. Der Begriff Körper (englisch Field ) wurde im 19. Jahrhundert erstmals von Richard
Dedekind verwendet; implizit tauchen das Konzept und einzelne Besonderheiten von Körpern
jedoch bereits bei Gauss, Galois oder Abel auf [LiPi98]. In [MuMu07] wird folgende intuitive
Definition geboten: “A Field is an algebraic structure consisting of a set of elements for which the
operations of addition, subtraction, multiplication, and division satisfy certain properties.” Die
bekanntesten Beispiele sind die rationalen, reellen und komplexen Zahlen. Bei diesen Beispielen
handelt es sich um unendliche Körper, da sie alle eine unendliche Zahl von Elementen beinhalten.
Es gibt jedoch auch Zahlbereiche, die nur aus endlich vielen Elementen bestehen und zugleich die
Kriterien eines Körpers erfüllen, in diesem Fall spricht man von endlichen Körpern [MuMu07].
Kurz gesprochen kann festgehalten werden: als endlichen Körper bezeichnet man eine Menge mit
einer endlichen Anzahl an Elementen, auf denen die Grundrechnungsarten ausgeführt werden
können [Kurz07]. Es kann gezeigt werden, dass endliche Körper immer pn , wobei p eine Primzahl
und n ∈ N ist, Elemente enthalten. Umgekehrt gibt es für jede Zahl der Form pn einen endlichen
Körper mit genau so vielen Elementen.
Endliche Körper werden – nach ihrem Entdecker Evariste Galois (1811-1832) – auch als Galoisfelder bezeichnet, man schreibt Fpn bzw. GF (pn ), wenn der Körper pn Elemente besitzt.
Den Körper GF (p) identifiziert man häufig mit Zp , also der Menge der Restklassen modulo
p ∈ P. [Kurz07]
Wir werden in Abschnitt 3.7 auf ausgewählte Anwendungen für endliche Körper eingehen.
3.1 Einführung
Aus Kapitel 1 ist bereits bekannt, dass ein Körper ein kommutativer Ring mit Einselement ist,
in dem jedes von 0 verschiedene Element invertierbar ist.
Jeder endliche Körper ist kommutativ, die Ordnung ist immer eine Primzahlpotenz (pn für
p ∈ P, n ∈ N), die multiplikative Einheitengruppe ist immer zyklisch. [Jung93]
Für einen Körper (F, +, ·) muss gelten
• (F, +) ist eine kommutative Gruppe.
• (F \ {0}, ·) ist eine kommutative Gruppe.
• Die Multiplikation · ist distributiv gegenüber der Addition +.
28
3
Endliche Körper
3.2 Modulo-Arithmetik mit Polynomen
Endliche Körper sind bezüglich der Addition, Multiplikation und den jeweiligen InversenBildungen abgeschlossen: Alle Ergebnisse für diese Operationen liegen immer auch im selben
Körper. [LiNi00] Dies soll nun detaillierter erläutert werden.
Addition, Multiplikation und Potenzen
“Letztendlich wird die Addition und Multiplikation in endlichen Körpern auf die Addition
und Multiplikation von ganzen Zahlen zurückgeführt. Deswegen müssen wir die an sich selbstverständlichen Rechenoperationen in Z genauer analysieren.” [Kurz07]. Sei a, b, x ∈ N dann ist
die Gleichung a + x = b nur dann lösbar, wenn a < b. Dies gilt für Zahlen aus der Menge der
natürlichen Zahlen. Wären die Zahlen aber aus der Menge Z = N ∪ {0} ∪ (−N), dann wäre
diese Gleichung immer lösbar. Laut [Kurz07] werden Addition und Multiplikation in Z durch
fünf Gesetze geregelt:
1. Addition und Multiplikation sind assoziativ:
(a + b) + c = a + (b + c) und (a · b) · c = a · (b · c)
2. Addition und Multiplikation sind kommutativ:
a + b = b + a und a · b = b · a
3. Das Nullelement (0) ist neutrales Element bezüglich der Addition und das Einselement
(1) ist neutrales Element bezüglich der Multiplikation:
0 + a = a und 1 · a = a
4. a + x = b besitzt eine eindeutige Lösung x in Z.
5. Es gilt das Distributivgesetz:
a · (b + c) = a · b + a · c
Als einfache Folgerung daraus ergeben sich die Potenzgesetze:
ai+j = ai · aj und (ai )j = ai·j , für i, j ≥ 0
In [MeVO96] wird der Algorithmus 3.1 als eine effiziente Lösung angegeben, um ein Polynom mit
einer Zahl zu potenzieren. Als Eingabeparameter werden das Polynom g ∈ Fpm , 0 ≤ k < pm − 1
und die Potenz k benötigt (der Körper Fpm kann auch als Zp [x]/f (x) – wobei f (x) ∈ Zp [x]
ein irreduzibles Polynom vom Grad m über Zp darstellt – angeschrieben werden). Zurück wird
g(x)k MOD f (x) geliefert.
Inverse Elemente
Um inverse Elemente eines endlichen Körpers zu bestimmen, wird analog wie bei Zahlen der
erweiterte Euklidische Algorithmus angewendet. [Kurz07] gibt zusätzlich einen Algorithmus zur
3.3 Irreduzibilitätstest
29
Algorithmus 3.1 Square-and-Multiply Algorithmus für Exponent in Fpm
Effiziente Berechnung der Potenz eines Polynoms.
g ∈ Fpm , 0 ≤ k < pm − 1 und k = (kn−1 · · · k1 k0 )2 . Des weiteren muss das
irreduzible Polynom f (x) ∈ Zp [x] vom Grad m über Zp bekannt sein.
Output: g(x)k MOD f (x).
1: function SquareAndMultiply(g, k)
2:
s(x) := [1]
3:
if k = 0 then
4:
return s
5:
end if
6:
G(x) := g(x)
7:
if k0 = 1 then
8:
s(x) := g(x)
9:
end if
10:
for i := 1, . . . , t do
⊲ t entspricht Binärstellen von k
2
11:
G(x) := G(x) MOD f (x)
⊲ f (x) ∈ Zp [x] ist irreduzibles Polynom von Grad m
12:
if ki = 1 then
13:
s(x) := G(x) · s(x) MOD f (x)
14:
end if
15:
end for
16:
return s(x)
⊲ s(x) = g(x)k MOD f (x)
17: end function
Zweck:
Input:
Division von Polynomen an, welcher dann beim erweiterten Euklidischen Algorithmus verwendet
wird. Die Algorithmen im Detail können in [Kurz07] nachgeschlagen werden.
3.3 Irreduzibilitätstest
Allgemein kann behauptet werden, dass es schwierig ist, eine Zahl als Primzahl oder als zusammengesetzt zu klassifizieren. Andererseits ist es einfach, für ein Polynom die Eigenschaft
irreduzibel (unzerlegbar) zu entscheiden.
Es seien zwei Polynome a und b 6= 0 aus F[x]. Wenn für ein weiteres Polynom f ∈ F[x] gilt, dass
b = f · a, dann ist a Teiler von b und b ist Vielfaches von a. Die Schreibweise dafür ist gleich wie
bei den natürlichen Zahlen: a|b und f = ab . [Kurz07]
Ist der Grad von f gleich 0 oder a gleich 0 (f (x) ist konstant oder a(x) ist konstant für alle
x ∈ N), so heißt a trivialer Teiler von b und es handelt sich um eine triviale Zerlegung von b.
Die Polynome a und b nennt man teilerfremde Polynome, wenn sie keinen gemeinsamen Teiler
vom Grad ≥ 1 besitzen.
Wenn a vom Grad wenigstens 1 ist und nur triviale Teiler besitzt, dann nennt man a irreduzibel. Ein normiertes (normiert bedeutet, dass der Leitkoeffizient des Polynoms 1 ist) irreduzibles
Polynom entspricht einer Primzahl in N [Kurz07]. Polynome vom Grad 1 sind immer irreduzibel [LiPi98].
Für alle irreduziblen Polynome p ∈ F[x] gilt somit, dass ein beliebiges Polynom a ∈ F[x] entweder
ein Vielfaches von p oder zu p teilerfremd ist.
30
3
Endliche Körper
Ein Algorithmus zum Testen der Irreduzibilität von Polynomen ist in [MeVO96] zu finden, und
als Algorithmus 3.2 hier wiedergegeben. Das Verfahren stützt sich auf folgenden Satz: Sei f
ein Polynom vom Grad m über Zp . Das Polynom f ist genau dann irreduzibel über Zp , wenn
i
(f (x), xp − x) = 1 für alle i mit 1 ≤ i ≤ ⌊m/2⌋ gilt.
Algorithmus 3.2 Irreduzibilitätstest für Polynome
Zweck:
Bestimmt, ob das Polynom f irreduzibel über Zp ist oder nicht.
Input:
p ∈R P, f ∈ Zp [x].
Output: Text “reduzierbar” oder “irreduzibel”.
1: function Irreduzibilitaetstest(p, f )
2:
u(x) := [x]
⊲ m ist der Grad von f
3:
for i := 1, . . . , m
2 do
4:
u(x) := u(x)p MOD f (x)
⊲ Algorithmus 3.1
5:
d(x) := ggT(f (x), u(x) − [x])
⊲ Algorithmus laut [Kurz07]
6:
if d 6= 1 then
7:
return “reduzierbar”
8:
end if
9:
end for
10:
return “irreduzibel”
11: end function
Weiters gibt [MeVO96] den Algorithmus 3.3 an, um irreduzible Polynome über Zp zu erzeugen. Dieser Algorithmus besitzt eine Laufzeitkomplexität von O(m3 (log m)(log p)). Analog zur
Konstruktion des endlichen Körpers Zp mit Hilfe der Modulo-Arithmetik und der Primzahl p,
lassen sich allgemeine endliche Körper der Ordnung pn konstruieren als Menge von Polynomen
vom Grad < n und Modulo-Arithmetik mit einem irreduziblen Polynom über Zp vom Grad n
als Modulus.
Algorithmus 3.3 Generator für zufällige normierte irreduzible Polynome
Zweck:
Erzeugt zufällige normierte irreduzible Polynome.
Input:
p ∈ P, m ∈ N.
Output: Ein normiertes irreduzibles Polynom f vom Grad m aus Zp [x].
1: function ErzeugeIrreduziblesPolynom(p, m)
2:
repeat
3:
Wähle a0 , a1 , . . . , am−1 wobei ai ∈R N, 0 ≤ ai ≤ p − 1, und a0 6= 0
4:
Setze f (x) := [xm + am−1 · xm−1 + · · · + a2 · x2 + a1 · x + a0 ]
5:
until f (x) ist reduzibel
⊲ Algorithmus 3.2
6:
return f (x)
⊲ f (x) ist ein normiertes irreduzibles Polynom über Zp [x] vom Grad m
7: end function
3.4 Primitivwurzeln, Einheitswurzeln und primitive Polynome
Ein primitives Element eines Körper F ist dadurch charakterisiert, dass alle Elemente im Körper
durch Potenzen dieses Elementes erzeugt werden können. Es existiert also ein z ∈ F∗ , sodass
für alle a ∈ F \ {0} gilt, dass es zumindest ein n ∈ N gibt und die Gleichung a = z n erfüllt ist.
Wir nennen ein solches Element auch eine Primitivwurzel. [Kurz07]
3.5 Finden von q-ten primitiven Einheitswurzeln
31
Zur Verdeutlichung sei ein Beispiel gegeben:
Sei Z5 ein endlicher Körper mit den Elementen {0, 1, 2, 3, 4}, dann ist 2 ein erzeugendes Element,
weil alle Elemente aus Z∗p = Zp \ {0} durch eine Potenz von 2 dargestellt werden können:
21 = 2, 22 = 4, 23 = 3, 24 = 1. Auch 3 ist ein erzeugendes Element (31 = 3, 32 = 4, 33 = 2,
34 = 1). Dagegen ist 4 im Körper Z5 kein erzeugendes Element, weil die Potenzen zu 4 nicht
alle Elemente in Z5 \ {0} ergeben (41 = 4, 42 = 1, 43 = 4, 44 = 1, . . .).
Laut [Kurz07] besagt ein Satz, dass in einem endlichen Körper immer zumindest ein solches
primitives Element existiert. Dies wird in [Kurz07, Kapitel 7] bewiesen, in dieser Arbeit sei
lediglich darauf verwiesen.
Das Auffinden eines erzeugenden Elementes kann schwierig sein. Zu beachten wäre:
1. Sollte die Faktorisierung von p nicht bekannt sein, ist es nicht möglich, ein erzeugendes
Element zu finden.
2. Sollte die Faktorisierung von p − 1 bekannt sein, dann kann eine Primitivwurzel leicht
bestimmt werden. Ein Kriterium zum Finden von erzeugenden Elementen ist: Sei p ∈ P.
Dann ist α Primitivwurzel modulo p genau dann, wenn für alle Primteiler pi von p − 1 die
folgende Bedingung gilt: α(p−1)/pi MOD p 6= 1.
3. Sollte es sich bei α um ein erzeugendes Element aus Z∗n für ein n ∈ N und n 6∈ P handeln, so
ist auch b = αi MOD n ein erzeugendes Element aus Z∗n = {x|0 ≤ x < n, ggT(x, n) = 1}
und zwar dann und nur dann, wenn der ggT(i, ϕ(n)) = 1 ist. Daraus folgt weiters, dass –
sofern Z∗n zyklisch ist – es ϕ(ϕ(n)) erzeugende Elemente gibt. [MeVO96, Fact 2.132]
Ein analoges Kriterium existiert für primitive Polynome: Sei p ∈ P, f ∈ Zp [x] und m der Grad
des Polynoms f . Das irreduzible Polynom f ist primitiv genau dann, wenn für alle Primteiler
m
pi von pm − 1 die folgende Bedingung gilt: x(p −1)/pi MOD f (x) 6= 1. Werden im Körper F alle
Operationen modulo f (x) ausgeführt, so ist (das Polynom) x ein erzeugendes Element von F∗
ist. [MeVO96]
3.5 Finden von q-ten primitiven Einheitswurzeln
Eine q-te primitive Einheitswurzel ist wie folgt definiert: Sei F ein endlicher Körper. Eine Lösung
α der Gleichung xq − 1 = 0 in F[x] nennen wir q-te Einheitswurzel. Die Ordnung einer q-ten Einheitswurzel α ist die kleinste positive ganze Zahl k, so dass αk = 1 ist. Eine q-te Einheitswurzel
der Ordnung q nennt man primitive Einheitswurzel. [LiPi98]
Es kann auch eine q-te primitive Einheitswurzel modulo p wie folgt bestimmt werden, wobei
q|(p − 1) gelten muss: Man wählt zufällig ein h ∈ [1 : p − 1] und berechnet g := h(p−1)/q MOD p.
Falls g = 1 gilt, muss h erneut gewählt und g neu berechnet werden. Andernfalls hat
man mit g eine q-te primitive Einheitswurzel bestimmt. In Algorithmus 3.4 wird dieser
Algorithmus angegeben.
3.6 Faktorisieren von Polynomen
Wie aus Abschnitt 2.9 bereits bekannt ist, ist es schwer eine Zahl zu faktorisieren. Dahingegend
ist es laut [Knut01] viel leichter, die Faktoren eines Polynoms zu finden. Hierfür wird das Poly-
32
3
Endliche Körper
Algorithmus 3.4 Algorithmus zum Auffinden der q-ten primitiven Einheitswurzel
Zweck:
Algorithmus zum Auffinden der q-ten primitiven Einheitswurzel.
Input:
Die Primzahl q und der Modulus p wobei q|(p − 1) gelten muss.
Output: Eine q-te primitive Einheitswurzel modulo p.
1: function RootOfUnity(q, p)
2:
repeat
3:
h ∈R [1 : p − 1]
4:
g := h(p−1)/q MOD p
5:
until g = 1
6:
return g
7: end function
⊲ q|(p − 1)
nom zuerst normiert, da der Leitkoeffizient sich nicht auf die Faktorisierung auswirkt. Des weiteren wird hier eine Faktorisierung für quadratfreie Polynome gezeigt. In [Kapl05] und [GaGe03]
wird der Algorithmus 3.5 angegeben, welcher für normierte, quadratfreie Polynome funktioniert.
In [DaST93], [Cohe03] und [Koep06] werden noch weitere Algorithmen angegeben, welche aber
hier nicht näher besprochen werden.
Algorithmus 3.5 Faktorisierung nach Graden
Zweck:
Dieser Algorithmus faktorisiert ein quadratfreies Polynom.
Input:
f ∈ GF (q)[x] (f ist normiert, quadratfrei und vom Grad n > 0) und q ∈ P.
Q
Output: d1 , d2 , . . . mit i di = f : di ist Produkt aller Teiler vom Grad i.
1: function FaktorisierungNachGraden(f, q)
2:
r(x) := [x]
3:
h(x) := f (x)
4:
g := 0
5:
while g < n/2 and h(x) 6= [1] do
⊲ n ist der Grad von f
6:
g := g + 1
7:
r(x) := r(x)q MOD h(x)
8:
dg (x) := ggT(r(x) − [x], h(x))
9:
if dg (x) 6= [1] then
10:
h(x) := h(x)/dg (x)
11:
r(x) := r(x) MOD h(x)
12:
end if
13:
end while
14:
if n > 1 then
⊲ n ist der Grad von h
15:
dn (x) := h(x)
16:
end if
17:
return (d1 , . . . , dn )
18: end function
3.7 Ausgewählte Anwendungen endlicher Körper
Endliche Körper werden zur Lösung für unterschiedliche mathematische Probleme verwendet
bzw. in verschiedenen wissenschaftlichen Disziplinen angewandt. Beispielsweise für fehlerkorri-
3.7 Ausgewählte Anwendungen endlicher Körper
33
gierende Codes und Kryptographie, aber auch zur Fehlerkorrektur von Nachrichten beim Versenden über verrauschten Kanälen. [Kurz07, LiNi00]
Wie in [MeVO96] beschrieben, kann in der Codierungstheorie und bei kryptographischen Verfahren in endlichen Körpern gerechnet werden. Normalerweise wird das ElGamal-Verfahren in der
multiplikativen Gruppe Z∗p beschrieben, es kann aber auf einfachem Weg für beliebige endliche
zyklische Gruppen G erweitert bzw. angepasst werden.
Laut [MeVO96] ist es äußerst wichtig, die Gruppe G geeignet zu wählen, um sowohl Effizienz
als auch Sicherheit zu gewährleisten. Folgende Gruppen sind geeignet:
1. Die multiplikative Gruppe Z∗p von natürlichen Zahlen modulo einer Primzahl p.
2. Die multiplikative Gruppe F∗2m des endlichen Körpers F2m .
3. Die Gruppe der Punkte einer elliptischen Kurve über einen endlichen Körper.
4. Die multiplikative Gruppe F∗q eines endlichen Körpers Fq , wobei q = pm und p ∈ P.
5. Die Einheitengruppe Z∗n , wobei n einen zusammengesetzte natürliche Zahl ist.
Die Gruppe der Punkte einer elliptischen Kurve über einen endlichen Körper (Punkt 3) wird
in diversen einschlägigen Literaturen eingehen besprochen (beispielsweise [Mene93, ACDF+ 06,
BeBF02, BlSS04]); in dieser Arbeit wird nicht weiter darauf eingegangen.
Um das ElGamal Verschlüsselungsverfahren für endliche Körper zu verwenden, liefert [MeVO96]
den folgenden allgemeinen Algorithmus:
Schlüsselerzeugung
Wähle eine geeignete Gruppe G der Ordnung n mit Primitivwurzel α
a ∈R N, 1 ≤ a ≤ n − 1
Berechne αa
Öffentlicher Schlüssel: (α, αa )
Geheimer Schlüssel: a
Verschlüsselung
Bob verschlüsselt Nachricht m
Nachricht m ist ein Element der Gruppe G
Wähle k ∈R N, 1 ≤ k ≤ n − 1
Berechne γ = αk und δ = m · (αa )k
Verschlüsselter Text c ist das Paar (γ, δ)
Entschlüsselung
Alice entschlüsselt Nachricht m
Privater Schlüssel a
Berechne γ a und γ −a
Berechne die Nachricht m = (γ −a ) · δ
4 Sprach-Spezifikationen
In den Kapiteln zuvor wurde gezeigt, welche Algorithmen für verschiedene kryptographische Verfahren und Protokolle benötigt und welche Datentypen bei der Umsetzung kryptographischer
Protokolle häufig auftreten bzw. eingesetzt werden. Darauf aufbauend wird nun eine Spezifikation für eine einfache Programmiersprache entworfen; diese soll die EntwicklerInnen bei der
Umsetzung möglichst gut unterstützen. Es handelt sich hierbei um eine konzeptuelle Beschreibung, die als Grundlage für eine zukünftige Implementierung genutzt werden kann.
Um die Syntax dieser Sprache standardisiert anzugeben, wird die Erweiterte Backus-Naur-Form
(EBNF) nach ISO/IEC 14977 verwendet [ISO96].
Ein Überblick über die gesamte Syntax der Sprache wird in Anhang A gegeben.
4.1 Deklarationen
Wie aus anderen Programmiersprachen bekannt ist, muss eine Variable deklariert werden bevor
sie benutzt werden kann. Dafür wird folgende Syntax verwendet:
deklaration
→
id ’:’ typ .
id
→
buchstabe { buchstabe | ziffer } .
buchstabe
→
’A’ | ’B’ | ’C’ | ’D’ | ’E’ | ’F’ | ’G’ | ’H’ | ’I’ | ’J’ | ’K’ | ’L’
| ’M’ | ’N’ | ’O’ | ’P’ | ’Q’ | ’R’ | ’S’ | ’T’ | ’U’ | ’V’ | ’W’
| ’X’ | ’Y’ | ’Z’ | ’a’ | ’b’ | ’c’ | ’d’ | ’e’ | ’f’ | ’g’ | ’h’
| ’i’ | ’j’ | ’k’ | ’l’ | ’m’ | ’n’ | ’o’ | ’p’ | ’q’ | ’r’ | ’s’
| ’t’ | ’u’ | ’v’ | ’w’ | ’x’ | ’y’ | ’z’.
ziffer
→
’0’ | ’1’ | ’2’ | ’3’ | ’4’ | ’5’ | ’6’ | ’7’ | ’8’ | ’9’.
Das hier vorkommende Element id soll eine Bezeichnung aus Buchstaben und Zahlen sein, wobei
Variablennamen immer mit einem Buchstaben beginnen müssen. In typ wird ein Datentyp oder
eine Datenstruktur angegeben, wobei typ weiter zerlegt werden kann in primTyp und kompTyp.
Unter primTyp sollten primitive Datentypen wie INTEGER oder BOOLEAN und in kompTyp komplexere
Datentypen und -strukturen wie POLYNOM oder RECORD definiert werden.
typ
→
primTyp | kompTyp .
Somit kann eine Deklaration als variable1:INTEGER; oder variable2:BOOLEAN angegeben werden.
36
4 Sprach-Spezifikationen
4.2 Zuweisungen
Nachdem eine Variable deklariert wurde, kann sowohl schreibender als auch lesender Zugriff
darauf erfolgen. Um einer Variable einen Wert zuzuweisen, wird folgende Syntax verwendet:
zuweisung
→
id [ selektor ] ’:=’ expr .
Der hier verwendete selektor wird benötigt, um einem Element in einem ARRAY oder in einem
RECORD einen Wert zuweisen zu können. Der selektor kann öfters vorkommen, so kann auf
mehrdimensionale Arrays oder verschachtelten Strukturen in RECORDs zugegriffen werden. Auf
den genauen Aufbau von selektor wird später noch eingegangen. Die gesamte Syntax wird auch
noch in Anhang A genauer angegeben. Zu expr sollte hier nur erwähnt werden, dass es sich dabei
sowohl um arithmetische oder logische Ausdrücke, bzw. Konstanten handeln kann.Konstanten
können eine einfache Nummer, aber auch TRUE oder FALSE sein. Der genaue Aufbau von expr
wird im Anhang A detailliert angegeben. Eine Nummer setzt sich wie folgt zusammen:
nummer
→
ziffer { ziffer } .
Somit kann mittels var := 12; einer Variablen vom Typ INTEGER eine konstante Zahl zugewiesen
werden. Mittels arr[0] := var; wird dann der Wert von der Variable var auf die 0-te Stelle des
Arrays arr gespeichert.
4.3 Datentypen
Folgend werden die Datentypen beschrieben, welche zur Realisierung der vorgestellten kryptographischen Verfahren und Protokolle als sinnvoll und hilfreich erachtet werden.
Primitive Datentypen: primTyp
INTEGER: Bei den meisten kryptographischen Verfahren oder Protokollen werden Zahlen (n ∈ N)
verwendet. Für diese gibt es den Datentyp INTEGER. Da man grundsätzlich davon ausgehen
kann, dass in kryptographischen Verfahren große Zahlen benötigt werden, sollte der Datenbereich für ganze Zahlen keine feste Obergrenze besitzen und Werte beliebiger Größe
aufnehmen können (auch in Hinblick auf die Adaptierung kryptographischer Standards
in Richtung höherer Bitlängen als den heutigen). Man könnte diesen Datentyp mit der
BigInteger Klasse der Java-Sprache vergleichen, deren Datenbereich nur durch den vorhandenen Arbeitsspeicher des Rechners begrenzt ist [SUN10a].
INTEGER(n): Der Restklassentyp INTEGER(n) benötigt beim Deklarieren einen Modulus n des
Datentyps INTEGER, wobei bei der Initialisierung kontrolliert werden muss, ob der Modulus
positiv ist. Bei negativen Moduli sollte der Compiler einen Fehler liefern. Bei einer Zuweisung oder einer Berechnung müssen der zugewiesene Wert bzw. das Ergebnis modulo n
reduziert werden. Somit kann es nie vorkommen, dass eine Variable des Typs INTEGER(n)
negativ oder größer als n − 1 ist. Geeignete Algorithmen für arithmetische Operationen
mit dem Datentyp INTEGER(n) wurden in Kapitel 2 angegeben. Diese wurden für den in
Kapitel 5 vorgestellten Prototypen bereits implementiert und verwendet.
Es sei darauf hingewiesen dass beispielsweise beim ElGamal-Verfahren Berechnungen sowohl modulo n als auch modulo ϕ(n) durchgeführt werden. Um Fehler und Inkonsistenzen
4.3 Datentypen
37
vorzubeugen empfiehlt es sich daher für diese Anwendungen den Datentyp INTEGER(n)
nicht oder nur mit Vorsicht einzusetzen.
PRIME(a): Nachdem mit der Deklaration p:PRIME(a); der Variable p der Datentyp PRIME(a)
zugewiesen wurde, muss bei der Initialisierung kontrolliert werden, ob der zugewiesene
Wert eine Primzahl ist oder nicht. Algorithmus 2.2 ist hierfür geeignet. Der mitgelieferte
Parameter a dient als Sicherheitsparameter für die Berechnung. Sollte der Wert zur Laufzeit geändert werden, so soll die Laufzeitumgebung auch kontrollieren, ob es sich um eine
Primzahl handelt oder nicht.
BOOLEAN: Logische Variablen können mittels “b:BOOLEAN;” deklariert werden. Der zulässige Wertebereich umfasst die Konstanten TRUE und FALSE. Weiters können Vergleichsoperatoren
(Operatoren ==, !=, <, >, <=, >=), Konjunktionen (Operator AND) und Disjunktionen
(Operator OR) zum Ergebniswert TRUE oder FALSE führen.
Somit ergibt sich für die primitiven Datentypen folgende EBNF-Notation:
→
primTyp
’INTEGER’ [ ’(’ expr ’)’ ] | ’BOOLEAN’
| ’PRIME’ ’(’ expr ’)’.
Zusammengesetzte Datentypen und Felder: kompTyp
POLYNOM: Mit dem Datentyp POLYNOM können Polynome in einer Variablen x von beliebigem
Grad und mit Koeffizienten vom Typ INTEGER(n) deklariert werden. Die korrekte Angabe
eines Polynoms wird folgend dargestellt:
polynom
→
term
→
addOP
→
’[’ term { addOP term } ’]’.
[ nummer [ ’*’ ] ] ’x’ [ ’^’ expr ]
| nummer
’+’ | ’-’.
Somit können Polynome wie beispielsweise [4xˆ2 + x + 1], aber auch [4 ∗ xˆ2 + 1 ∗ x + 1]
erzeugt werden. Zusätzlich ist zu beachten, dass gleiche Potenzen mehrmals vorkommen
können und die Reihenfolge der einzelnen Elemente nicht relevant ist. Somit muss es beispielsweise auch erlaubt sein, ein Polynome in der Form [x + 4xˆ2 + 1 + 2xˆ2] anzugeben,
was intern zu dem Polynom [6xˆ2 + x + 1] führen sollte.
Neben addOP werden noch mulOP, relOP und equOP als Operationszeichen angeboten. Diese
werden im Anhang A noch definiert.
GF(p,f): Wie in Kapitel 3 gezeigt wurde, werden häufig Galoisfelder für arithmetische Operationen herangezogen. Diese werden mit dem Datentypen GF(p,f) deklariert, wobei die Variable p vom Datentyp PRIME(a) ist und die Charakteristik des Körpers beschreibt und f vom
Datentyp POLYNOM sein muss und den über Zp irreduziblen Modulus des Körpers GF (pn )
darstellt. Für Letzteres muss beim Deklarieren des Polynoms ein Irreduzibilitätstest (Algorithmus 3.2) durchgeführt werden. Sollte f dynamisch deklariert werden, kann das System
zur Übersetzungszeit nicht kontrollieren, ob es sich um ein irreduzibles Polynom handelt,
und die Laufzeitumgebung müsste diesen Test durchführen. Dies kann man verhindern, indem man bei der Deklaration eine Konstante verlangt und Variablen generell nicht zulässt.
gf
→
’GF’ ’(’ expr ’,’ polynom ’)’.
Die zusammengesetzte Datentypen und Felder setzen sich wie folgt zusammen:
38
4 Sprach-Spezifikationen
kompTyp
→
gf | record | array | ’POLYNOM’ | ’STACK’ | ’FIFO’
| randGenerator .
Die hier verwendeten Datenstrukturen record, array, STACK und FIFO werden im folgenden Abschnitt besprochen. Der randGenerator wird später im Abschnitt 4.6 definiert.
4.4 Datenstrukturen
In einigen Situationen reichen primitive Datentypen nicht aus. Um beispielsweise ein Zertifikat
abzubilden, müsste man mehrere (unterschiedliche) Datentypen zusammenfassen. Genauso sollte
es möglich sein, mehrere Daten in einem Element zu speichern, wie es bei Arrays beispielsweise
üblich ist. Um diese Fälle abzudecken, werden die Datenstrukturen RECORD, ARRAY, STACK und
FIFO vorgeschlagen:
RECORD: Ein RECORD ist eine Datenstruktur, deren Komponenten sowohl primitive Datentypen
als auch andere Datenstrukturen (insbesondere wiederum RECORDs) sein können.
record
→
deklList
→
’RECORD’ deklList ’END’.
{ deklaration ’;’}+ .
Der Zugriff auf einzelne Komponenten eines RECORDs erfolgt über den Punkt-Operator,
also beispielsweise “myRecord.field1”. Auf diese Weise kann auf jede Variable im RECORD
individuell zugegriffen werden – sowohl lesend, als auch schreibend. Die zuweisung hat
dafür das Symbol selektor, das auch das Schreiben auf ARRAYs ermöglicht:
selektor
→
(’[’ expr ’]’ | ’.’ id ) [ selektor ] .
ARRAY: Ein ARRAY speichert eine Reihe von primTyp oder kompTyp Werten und kann wie folgt
angelegt werden:
array
→
’ARRAY’ selektor ’OF’ ( primTyp | kompTyp ) .
Beispiel: Arrays können somit beispielsweise mittels arr:ARRAY[10] OF INTEGER; aber auch
mit arr2:ARRAY[var] OF RECORD; deklariert werden, wobei beim zweiten Aufruf die Variable var vom Typ INTEGER zuvor gesetzt werden muss.
STACK, FIFO: Der STACK soll – anders als ein Array – verschiedenartige Datentypen gleichzeitig
aufnehmen können. Man hat somit die Möglichkeit, sowohl POLYNOMe als auch Zahlen am
STACK abzulegen, da bei einer Berechnung ohnehin kontrolliert werden muss, ob es sich
um zwei kompatible Datentypen handelt. Wie bei einem Stack üblich, wird das zuletzt
hinzugefügte Element als nächstes verwendet (last-in-first-out (LIFO)-Strategie).
Wie beim STACK können auch im FIFO verschiedene Datentypen abgespeichert werden. Anders als beim STACK wird hier aber das zuerst hinzugefügte Element auch zuerst verwendet
(first-in-first-out-Strategie).
Um auf ein Element auf einen STACK zu legen wird der Aufruf PUSH verwendet, beim Hinzufügen eines Elementes zu einem FIFO wird ADD verwendet.
addStackFifo
→
id ’.’ (’PUSH’ | ’ADD’) ’(’ expr ’)’.
Die Strukturen STACK und FIFO können nicht direkt zu Berechnungen verwendet werden, man
muss zuvor auf die Elemente zugreifen. Um von STACK und FIFO Elemente zu lesen – und zu
4.5 Kontrollstrukturen
39
entfernen – wird vorgeschlagen, zwei Symbole GET und POP einzuführen. POP wird wie üblich bei
einem Stack das oberste und zuletzt hinzugefügte Element liefern. Mittels GET kann auf das erste
hinzugefügte Element in dem FIFO zugegriffen und dieses daraus entfernt werden.
→
primaryExpr
literal | id [ selektor ] [ ’.GET’ | ’.POP’ ]
| ’(’ expr ’)’ | funktion .
→
literal
’TRUE’ | ’FALSE’ | nummer | random | polynom .
Die verwendeten Symbole random und funktion werden noch in Abschnitt 4.6 bzw. in Abschnitt 4.7 näher besprochen. Hier sollte lediglich erwähnt werden, dass mit funktion vordefinierte Funktionen aufgerufen werden können, mit random kann ein Zufallszahlengenerator erzeugt werden. Anhand eines Beispiels sollte die Verwendung eines STACKs verdeutlicht werden.
Dafür werden zuvor einige Werte auf den STACK gelegt, anschließend wieder gelesen um eine
Berechnung durchzuführen. Das Ergebnis wird wieder auf den STACK gelegt.
stack:STACK;
stack.PUSH(9);
stack.PUSH(13);
stack.PUSH(stack.POP * stack.POP); //13 * 9
stack.PUSH(11);
stack.PUSH(stack.POP + stack.POP); //11 + 117
Nach Durchführung dieses Beispiels liegt nur mehr das letzte Ergebnis (128) am STACK.
4.5 Kontrollstrukturen
Um auf die verschiedenen Kontrollstrukturen zugreifen zu können, wird das Symbol statement
eingeführt, welches sich folgendermaßen zusammensetzt:
statement
→
( zuweisung | addStackFifo ) ’;’ | if | while | for .
IF: Bedingungen werden wie folgt realisiert:
if
→
’IF’ bedingung block [ ’ELSE’ ( if | block ) ] .
bedingung
→
’(’ expr ’)’.
block
→
’{’ statementList ’}’.
statementList
→
{ statement }+ .
Das Symbol block dient dazu, die (lokale) Sichtbarkeit von Variablen zu beschränken (scoping). Eine Variable welche ausserhalb eines Blocks definiert wird ist in allen eingeschachtelten Blöcken sichtbar, jedoch in keinem “übergeordneten” Block.Hier wird das Symbol
block definiert; dazu sollte erwähnt werden, dass Variablen, die in einem Block deklariert
werden, auch nur in diesem sichtbar sind. Das Symbol statement wird hier erarbeitet und
am Ende dieses Abschnittes angegeben.
WHILE: Die WHILE-Schleife kontrolliert die Bedingung beim Eintritt und führt den Block solange
aus, bis die Bedingung nicht mehr erfüllt ist. Sie wird folgendermaßen angegeben:
while
→
’WHILE’ bedingung block .
40
4 Sprach-Spezifikationen
FOR: Die FOR-Schleife sollte verwendet werden, wenn die Anzahl der Schleifendurchläufe bekannt
ist. Als Startwert muss eine Variable definiert und initialisiert werden. Diese hat immer den
Datentyp INTEGER. Durch die expr kann der Startwert auch eine Berechnung (beispielsweise:
var := a+b für die Variablen a und b des Datentyps INTEGER) sein. Sollte eine Variable
(FOR var := 1 TO 100 ...) verwendet werden, wird diese in der Schleife als Laufvariable
verwendet, sie ist nur in der Schleife gültig. Bei Verwendung von TO wird die Laufvariable
inkremetiert, bei DOWNTO wird die Variable dekrementiert.
for
→
’FOR’ id ’:=’ expr
(’TO’ | ’DOWNTO’)
expr block .
Durch ein Beispiel soll die Verwendung einer FOR-Schleife veranschaulicht werden.
a:INTEGER;
a := 0;
FOR var := 1 TO 100 {
a := a + var;
}
Nach der Schleife beinhaltet die Variable a die Summer der Zahlen von 1 bis 100 (a =
P100
i=1 i).
4.6 Zufallszahlenerzeugung
Zufallszahlen spielen in einer Vielzahl kryptographischer Anwendungen eine wichtige Rolle. Um
den EntwicklerInnen den Aufwand für die Wahl von Zufallszahlen zu ersparen, werden Datentypen bereitgestellt, welche unterschiedliche Arten von Zufallszahlen zur Verfügung stellen. Im
Einzelnen sind dies: “echte” Zufallszahlen, welche nach keiner deterministischen Vorschrift ermittelt werden, quasi-Zufallszahlen, welche dublettenfrei über einer definierten Menge erzeugt
werden, und Pseudozufallszahlen, die einem deterministischen Bildungsgesetz folgend ermittelt
werden.
Zufallszahlen
Um gute Zufallszahlen zu generieren, werden – wie in Abschnitt 2.2 angegeben – verschieden
äußere Einflüsse verwendet. Zufallszahlen müssen immer in einem bestimmten Bereich generiert
werden. Zur Erzeugung einer Zufallszahl muss dieser Bereich mitgeliefert werden, sofern er nicht
implizit gegeben ist.
Die Programmiersprache JAVA liefert geeignete Generatoren, welche zur Erzeugung von Zufallszahlen herangezogen werden können. Die JAVA-Klasse BigInteger bietet bei der Erzeugung
des Objektes die Möglichkeit, einen Generator mitzuliefern. Es können auch zufällige Primzahlen erzeugt werden (BigInteger(int bitLength, int certainty, Random rnd)) indem man
dem Konstruktor der BigInteger Klasse zusätzlich noch einen ganzzahligen Wert als Sicherheitsparameter für den Primzahltest mitliefert. Laut [SUN10a] gilt eine Wahrscheinlichkeit von
1 − 1/2certainty , dass das neue BigInteger Objekt eine Primzahl repräsentiert.
Pseudozufallszahlen
Bei Pseudozufallszahlen handelt es sich um Zufallszahlen, die deterministisch erzeugt werden
können und sich nach Erzeugung einer bestimmten Anzahl von Werten (Periodendauer) wiederholen, d.h. die Folge wird zyklisch. Für kryptographische Anwendungen wird verlangt, dass
4.6 Zufallszahlenerzeugung
41
die Kenntnis der bisher erzeugten Folge keine besseren Vorhersagen über die nächsten Werte
der Folge zuläßt, als durch bloßes Raten getroffen werden könnten. Wie in Abschnitt 2.2 bereits
erwähnt, kann man beispielsweise einen Zähler mittels AES verschlüsseln.
JAVA bietet mit der Klasse java.util.Random einen Pseudorandomgenerator an. Bei der Erzeugung des Generators kann ein 48 Bit langer Initialwert mitgeliefert werden (Random(long seed)).
Anschließend können mit den next()-Methoden (int, float, double, long) Zufallswerte erzeugt werden. Die Random Klasse ist nicht für kryptographische Anwendungen geeignet, dafür
bietet sich die Klasse java.security.SecureRandom an. Diese Klasse ist von Random abgeleitet
und liefert einen kryptographisch starken Zufallszahlengenerator [SUN10d]. In einer für kryptographische Anwendungen gedachten Implementierung sollte ausschließlich dieser Generator
verwendet werden.
Sprachkonstrukte für Zufallsvariablen
Nun wird die Syntax definiert, wie Zufallszahlen in der Applikation erzeugt und anschließend
verwendet werden können.
RANDOM und RANDOM(a:b): Mit RANDOM kann man einer Variablen einen zufälligen Wert hinzufügen, dafür muss aber der Wertebereich der Variable bekannt sein. Wenn beispielsweise die Variable vom Typ INTEGER(n) oder GF(p,f) ist, dann ist der Wertebereich implizit vorgegeben und man kann mittels RANDOM einen zufälligen Wert zuweisen. Wenn
der Wertebereich der Variable nicht bekannt ist (die Variable var ist beispielsweise vom
Typ INTEGER), dann wird bei der Zuweisung var := RANDOM; eine Fehlermeldung ausgegeben. Hier muss man den Bereich mit angeben. Beispielsweise wird mit der Zuweisung
var := RANDOM(0:100); eine Zufallszahl von 0 bis 100 erzeugt.
Mittels RANDOM(a:b) kann man einen zufälligen Wert aus einem definierten Bereich (von
a bis b, wobei gelten muss, dass a<b und sowohl a als auch b vom Datentyp INTEGER)
zuweisen. Hier wird außerdem kontrolliert, ob die zu setzende Variable eine Primzahl, also
vom Datentyp PRIME(a), ist. Sollte die Variable vom Typ PRIME(a) sein, dann wird eine
zufällige Primzahl (aus dem mitgelieferten Bereich) generiert.
random
→
’RANDOM’ [ ’(’ expr [ ’:’ expr ] ’)’ ] .
Folgend sollen ein paar Beispiele die Nutzung von random verdeutlichen:
var:INTEGER;
var := RANDOM(0:100);
var2:INTEGER(var);
var2 := RANDOM;
var3:INTEGER;
var3 := RANDOM(100);
Der Variablen var wird eine Zufallszahl von 0 bis 100 zugewiesen. Da der Wertebereich von var2 bekannt ist, kann mittels var2 := RANDOM; eine Zufallszahl von 0 bis
var−1 erzeugt werden. Der letzte Aufruf (var3 := RANDOM(100);) könnte auch mittels
var3 := RANDOM(0:99); realisiert werden.
PSEUDORANDOM(INTEGER, INTEGER, INTEGER): Dieses Sprachkonstrukt fungiert wie ein Datentyp. So kann man beliebig viele unterschiedliche Generatoren erzeugen. Dies ist insbeson-
42
4 Sprach-Spezifikationen
dere für Anwendungen interessant, wo mehrere Parteien (Alice, Bob, Carol, etc.) mit voneinander unabhängigen Zufallsgeneratoren agieren. Wenn ein Generator erzeugt wurde,
kann man mittels einer einfachen Zuweisung eine Zufallszahl erhalten. Dadurch können
Zufallszahlen wie folgt erzeugt werden:
a:INTEGER;
gen:PSEUDORANDOM(20,100,45); //Minwert 20, Maxwert 100 und Initwert 45
a := gen; //Zuweisung einer Zufallszahl zu a
Eine Deklaration der Form gen:PSEUDORANDOM(INTEGER, INTEGER, INTEGER) erzeugt einen
Pseudozufallszahlengenerator. Der erste INTEGER-Wert gibt die Untergrenze für die erzeugten Zahlen an, der zweite INTEGER-Wert entspricht einer Obergrenze. Wie aus Abbildung 2.1
bereits bekannt, kann man von 128-Bit-Länge (geliefert von AES) auf eine bestimmte
Länge kürzen. Hier bei PSEUDORANDOM gibt der Bereich aber keine Bitlänge an, sondern die
größte Zufallszahl. Dabei kann aber dasselbe Prinzip verwenden werden: Hierfür werden
Zufallszahlen generiert und verschiedene Prüfungen durchgeführt. Hat der Maximalwert
keine führende Null, so kann jede Zahl mit führender Null verwendet werden. Bei Zahlen
mit führender Eins muss kontrolliert werden, ob sie kleiner als der Maximalwert sind. Sollte der Maximalwert eine führende Null aufweisen, kann man Zahlen mit führenden Einsen
verwerfen; Zahlen mit führenden Nullen werden mit dem Maximalwert verglichen.
Wenn beispielsweise der Maximalwert 870 ist, werden Zufallszahlen der Länge 10 generiert.
Die Binärdarstellung von 870 ist 1101100110, man sieht dass sie keine führende Null hat.
Deshalb können alle Zahlen mit führenden Nullen übernommen werden. Bei Zahlen mit
führenden Einsen muss kontrolliert werden. In Abbildung 4.1 werden zwei Zahlen mit dem
Maximalwert verglichen. Bei check1 wird der Wert (876) verworfen, bei check2 wird der
Wert (868) verwendet.
Wenn 128-Bit Lange Zahlen erzeugt werden, dann sind mittels dieser Methode kurze Zufallszahlen nicht mehr effizient ermittelbar, da sehr viele erzeugte Zufallszahlen nicht verwendet werden. Um beliebige Maximalwerte zu benutzen, kann folgendes Konzept verwendet werden. Es werden Zufallszahlen (beispielsweise mittels AES) mit 2128 -Bit Länge
erzeugt. Es sei x eine solche Zufallszahl und m mit 0 < m ≤ 2128 der Maximalwert. Ein
Faktor f wird mit f = x/(2128 ) berechnet und erhält einen Wert aus dem Intervall [0, 1).
Durch Multiplikation mit dem Maximalwert wird eine Zufallszahl y = ⌊m · f ⌋ aus dem
Intervall [0 : m) errechnet.
Der dritte INTEGER-Wert gibt den Initialwert für den Generator an. Bei gleichem Initialwert
werden die gleichen Zufallszahlen generiert, so können daraus resultierende Protokollschritte, Ergebnisse bzw. Rechenschritte einfach reproduziert werden.
Da der Generator wie eine Variable angesprochen werden kann, ist bei einer einfachen
Zuweisung nicht erkenntlich, ob es sich um die Zuweisung eines Wertes oder ob es sich
um eine erzeugte Zufallszahl aus einem Generator handelt. Um dies zu verdeutlichen
könnte eine Namenskonvention eingeführt werden, wonach Generatoren nur einen bestimmten Variablennamen (beispielsweise müssen Generatorennamen mit gen beginnen)
bekommen dürfen.
Folgend wird die Syntax zur Deklaration eines Pseudozufallszahlengenerator angegeben.
4.7 Vordefinierte Funktionen
43
Abb. 4.1: Zufallszahlen mit Maximalwert (870)
randGenerator
→
’PSEUDORANDOM’ ’(’ expr ’,’ expr ’,’ expr ’)’.
4.7 Vordefinierte Funktionen
Einige Funktionen sollten in der Sprache vordefiniert verfügbar sein. Wie durch die Protokolle
in Kapitel 2 motiviert, sind Funktionen zur Berechnung des größten gemeinsamen Teilers oder
primitiver Einheitswurzeln oft benötigte Hilfsmittel bei der Umsetzung von kryptographischen
Protokollen, bzw. bei der Wahl ihrer Parameter. Die Syntax für diese vordefinierten Methoden
sieht wie folgt aus:
funktion
→
(’GCD’ | ’EXTGCD’ | ’PRIMITIVEELEMENT’ | ’ROOTOFUNITY’
| ’MULTINVERS’ | ’PRIMETEST’) ’(’ expr ’,’ expr ’)’
| ’EULERPHI’ ’(’ expr ’)’
| ’EXP’ ’(’ expr ’,’ expr [ ’,’ expr ] ’)’.
Durch die Syntax wird auch definiert wieviele Parameter den Funktionen übergeben werden
dürfen. Dies soll folgend für die einzelnen Funktionen geschehen:
GCD(a, b): Erhält zwei Parameter a und b vom Typ INTEGER und liefert den größten gemeinsamen Teiler von a und b als INTEGER zurück. Diese Funktion verwendet den Algorithmus 2.4.
MULTINVERS(a, b): Erhält zwei Parameter a und b vom Typ INTEGER oder POLYNOM und berechnet das multiplikative Inverse Element von a: a−1 MOD b. Falls ggT(a, b) > 1 gilt bzw.
für Polynome der ggT(a(x), b(x)) einen Grad > 0 hat, wird eine Fehlermeldung analog
zu einer Division durch 0 ausgegeben, da in diesem Fall das Argument a kein inverses Element modulo b besitzt. Wie bei der Funktion zuvor, kann auch hier der Algorithmus 2.5
verwendet werden.
EXTGCD(a, b): Erhält zwei Parameter a und b vom Typ INTEGER und liefert nicht nur den
größten gemeinsamen Teiler von a und b, sondern auch die zwei Koeffizienten x und y in
einem Array, sodass ggT(a, b) = a · x + b · y ist. Folgendes Beispiel soll dies verdeutlichen, wobei angenommen wird, dass a, b, ggT, x und y als INTEGER deklariert und sowohl
a als auch b bereits initialisiert wurden. Außerdem wird angenommen, dass arr vom Typ
ARRAY OF INTEGER bereits deklariert wurde und wenigstens 3 Elemente groß ist. Hier soll
der Algorithmus 2.5 verwendet werden.
44
4 Sprach-Spezifikationen
arr := EXTGCD(a,b);
ggT := arr[0];
x := arr[1];
y := arr[2];
PRIMETEST(a,b): Erhält die Parameter a und b vom Typ INTEGER und testet, ob die Variable a
eine Primzahl ist. Mit dem Parameter b soll der Sicherheitsparameter übergeben werden.
Diese Funktion gibt den (bool’schen) Wert TRUE zurück, falls das Argument a eine Primzahl
(laut [SUN10a] mit 1 − 1/2b Wahrscheinlichkeit) ist, ansonsten wird FALSE zurückgeliefert.
Diese Funktion kann den Algorithmus 2.2 verwenden oder die von [SUN10a] zur Verfügung
gestellte Methode.
IRREDTEST(a): Erhält den Parameter a vom Typ POLYNOM und testet ob es irreduzibel ist oder
nicht. Liefert den Wert TRUE zurück, falls das Polynom a in dessen Struktur irreduzibel ist, andernfalls wird FALSE geliefert. Bei dieser Funktion kann der Algorithmus 3.2
verwendet werden.
PRIMITIVEELEMENT(p, a): Hier wird der Modulus p vom Typ INTEGER und ein ARRAY OF
INTEGER a übergeben, welches die Faktorisierung von p − 1 beinhaltet. Diese Methode
bestimmt ein primitives Element. Ein Kriterium dafür wurde in Abschnitt 3.4 angegeben.
ROOTOFUNITY(q, p): Diese Methode bestimmt die q-te primitive Einheitswurzel Modulo p.
Dafür muss das q und p vom Typ INTEGER übergeben werden. Hier kann der Algorithmus 3.4 verwendet werden, wobei in dem Algorithmus noch zusätzlich getestet werden muss
ob q|(p − 1) gilt. Wenn dies nicht der Fall ist, sollte eine Fehlermeldung angezeigt werden.
EXP(a, b): Erhält zwei Parameter a und b vom Typ INTEGER, berechnet ab und liefert das
Ergebnis als INTEGER zurück. Hier kann die Methode aus [SUN10a] verwendet werden.
EXP(a, b, c): Erhält drei Parameter a, b und c vom Typ INTEGER, berechnet ab MOD c und
liefert das Ergebnis als INTEGER zurück. Hier kann der Algorithmus 2.7 verwendet werden.
5 Implementierung
In diesem Kapitel soll der praktische Teil der Arbeit vorgestellt werden. Dieser gliedert sich
in zwei Teile: im ersten Teil werden die implementierten Ressourcen beschrieben (Bibliothek,
Stackrechner, etc.). Im zweiten Teil wird ein Konzept für eine Applikation erarbeitet, welche die
bereitgestellten Ressourcen nutzen und in der Lehre eingesetzt werden kann. Dieses kann für
Implementierungen in Folgeprojekten als Basis herangezogen werden. Später in Kapitel 6 werden
die in Kapitel 1 vorgestellten Verfahren und Protokolle unter Verwendung des implementierten
Stackrechners durchgerechnet sowie in der in Kapitel 4 definierten Sprache vorgeführt.
Im Zuge dieser Arbeit wurde eine JAVA-Bibliothek entwickelt, welche eine Sammlung von Funktionen für Berechnungen in endliche Körper beinhaltet. Des Weiteren wurde ein Stackrechner
als Demonstration entwickelt, welcher diese Bibliotheken einsetzt. Die folgenden Abschnitte beschreiben die Datenstrukturen der wichtigsten Objekte (Restklasse, Polynom, Stackrechner), den
Aufbau und die Verwendung der Bibliothek, die Unterstützung mehrerer Sprachen im Stackrechner und die Verwaltung von Programm-Einstellungen.
Im Stackrechner kann sowohl im Körper GF (pn ) als auch in Z gerechnet werden. Die verschiedenen Elemente werden auf den Stack gelegt und anschließend werden die unterschiedlichen Berechnungen auf diese Elemente ausgeführt. Die Ergebnisse werden wieder auf den Stack gelegt,
die Berechnungen werden textuell in den Berechnungsschritten ausgegeben. Außerdem können
in den Berechnungsschritten Kommentare eingefügt werden. Eine nähere Beschreibung wird in
Abschnitt 5.3 angegeben.
In Java wird durch BigInteger eine Klasse angeboten, deren numerische Obergrenze nur durch
die Kapazität des Arbeitsspeichers des Rechners begrenzt ist. Diese Klasse bietet alle Grundrechnungsarten und zusätzliche Funktionen (Berechnung des größten gemeinsamen Teilers, Primzahltest, etc.) bereits an [SUN10a]. In der folgend beschriebenen Implementierung wird diese
Klasse verwendet.
5.1 Datenstrukturen
Damit soll verdeutlicht werden, wie die einzelne Daten in den Objekten abgebildet werden und
wie auf die einzelnen Elemente zugegriffen werden kann. Es wird detailliert auf die Objekte
Restklasse und Polynom eingegangen werden. Des Weiteren wird die Struktur des Stacks und
der mitprotokollierten Berechnungsschritte im Stackrechner vorgestellt.
Restklasse
Eine Restklasse benötigt bei der Initialisierung zwei Eingabeparameter vom Typ BigInteger.
Der erste Parameter entspricht dem Modulus der Restklasse; alle Berechnungsergebnisse wer-
46
5 Implementierung
den modulo diesem Wert reduziert. Der zweite Parameter beinhaltet den Repräsentanten der
Restklasse. Die Klasse liefert neben den Grundrechnungsarten noch geeignete Abfragemethoden
(getter) für den Modulus und den Repräsentanten des Objektes. Außerdem können Restklassen
potenziert werden. Dafür werden mehrere Methoden angeboten um die unterschiedlichen Algorithmen (Repeated Square and Multiplying, Montgomery Multiplikation, etc.) auszuführen.
Polynom
Ein Polynom wird als Array von Restklassen abgebildet. Die Koeffizienten des Polynoms der
Form f (x) = [a0 + a1 · x + · · · + an · xn ] werden in das Array gespeichert, so dass sich im Array
A an der Stelle A[i] (für ein i ∈ N und i < der Länge des Arrays) der Koeffizient ai befindet. Da
sich das Polynom im Körper GF (pn ) befindet werden die Koeffizienten immer modulo p berechnet. Der Index des Arrays bestimmt den Grad des Polynoms. Die Syntax des Polynoms ist aus
Kapitel 4 bekannt, und das Polynom kann als String in direkt dieser Form an den Konstruktor
der Polynom-Klasse übergeben werden. Dieser zerlegt das Polynom in seine Koeffizienten und
speichert es intern in einem Array ab. Als Eingabeparameter benötigt die Polynom Klasse einen
String und einen BigInteger Wert. Der String beschreibt das Polynom, der BigInteger Wert
den Modulus des Polynoms. In der Klasse wird der String geparst und als Array dargestellt. Das
Polynom bietet alle Grundrechnungsarten an. Es gibt auch Abfragemethoden für den Grad oder
die Koeffizienten des Polynomes, die ein Restklassenarray liefert. Es wurden auch logische Abfragen implementiert, so kann man mittels isOne(), isZero() oder isIrreduzibel() das Polynom
untersuchen. In der Methode isOne() wird getestet, ob das Polynom 1 ist; sofern kein anderer
Koeffizient gesetzt oder größer als 0 ist und der Koeffizient vom Grad 0 gleich 1 ist, liefert diese
Funktion true ansonsten false zurück. Die Methode isZero() kontrolliert ob alle Koeffizienten des Polynomes gleich 0 sind. Die Funktion multiplikativesInvers(Polynom p, Polynom f)
berechnet das multiplikative Inverse von p(x), also p(x)−1 MOD f (x), (hierbei kommt der erweiterte Euklidische Algorithmus zum Einsatz). Des Weiteren wurden Funktionen zur Bestimmung des Divisionsrestes a(x) MOD b(x) durch die Methode mod(Polynom a, Polynom b), zur
Berechnung des größten gemeinsamen Teilers nach dem erweiterten Euklidischen Algorithmus
(erwGGT(Polynom p1, Polynom p2, boolean normiert)) oder zur Erzeugung eines normierten Polynoms (normierePolynom()) realisiert. Außerdem können mit getLeadingCoefficient() der Koeffizient höchsten Grades und mit findDegree() die höchste im Polynom vorkommende Potenz
und somit der Grad des Polynoms gefunden werden.
Stackrechner
Da im Stackrechner nicht nur in Zp [x] sonder auch in Z gerechnet werden kann, wurde der Stack
als Array mit dynamischer Größe und Einträgen vom Typ Object realisiert. So können Polynome
und Zahlen am selben Stack abgelegt werden und es wird bei der Berechnung kontrolliert welche
Datentypen vorliegen. Beim Stackrechner können die Elemente, die zur Berechnung notwendig
sind, am Stack belassen werden. Da die interne Struktur kein Stack sondern ein dynamisches
Array (Vector-Klasse in Java) ist, konnte dies mit der vom Vector zur Verfügung gestellten
get(int)-Methode einfach realisiert werden, welcher der Index des zu lesenden Eintrages zu
übergeben ist. Da die Mitschrift der Berechnungsschritte in die Zwischenablage gespeichert
werden kann, wurde als Datenstruktur ein einfacher String verwendet.
5.2 Bibliothek
47
5.2 Bibliothek
Für zukünftige Erweiterungen des Stackrechners, bzw. für die Nutzung in Softwareprojekten,
steht eine Funktionsbibliothek zur Verfügung, deren Methoden die in Kapitel 2 beschriebenen
Algorithmen implementieren.
Auf den nächsten Seiten findet sich eine kurze Übersicht dieser vorbereiteten Utensilien. Die
folgenden Funktionen wenden die in Kapitel 2 beschriebenen Algorithmen an:
• Multiplikation
Montgomery Multiplikation
Karatsuba Multiplikation
• Primzahlerzeugung und -test
Miller-Rabin
Algorithmus von Gordon zur Erzeugung starker Primzahlen
• Erweiterter Euklidischer Algorithmus (sowohl für Zahlen als auch für Polynome)
• Schnelles Potenzieren
Repeated Square and Multiplying
Balanced Montgomery Powering Ladder
Der in Abschnitt 5.3 vorgestellte Demonstrator (Stackrechner) verwendet diese Bibliothek zur
Ausführung der Berechnungen.
Aufbau
Wir beschreiben im Folgenden den Aufbau der Bibliothek und wie die einzelnen Funktionen
verwendet werden können. Einige Methoden sind sowohl statisch (Objekt.add(p1.p2)) als auch
dynamisch (p1.add(p2)) implementiert. Somit ist es möglich, diese sowohl als Bibliotheksfunktionen als auch als Objektmethoden nutzen zu können.
math.Euklid: In der Klasse Euklid wurde der Euklidische Algorithmus zur Bestimmung des
größten gemeinsamen Teilers implementiert. Des weiteren wurde für verschiedene Objekte
der erweiterte Euklidische Algorithmus realisiert, der bei zwei gegebenen Zahlen (bzw.
Polynomen) a, b ein Tripel g, x, y liefert, sodass g = ggT(a, b) = a · x + b · y gilt. Im Detail
wären dies:
public static int ggT(int a, int b): Berechnet den größten gemeinsamen Teiler
unter Verwendung des Euklidischen Algorithmus. Der größte gemeinsame Teiler wird als
int returniert.
public static Polynom ggT(Polynom n, Polynom a, boolean normiert):
Berechnet
den größten gemeinsamen Teiler der Polynome n und a durch Verwendung des Euklidischen Algorithmus laut [Kurz07]. Wenn für den Eingangsparameter normiert == true
übergeben wird, wird das Ergebnis in normierter Form zurückgeliefert. Das Ergebnis wird
als Polynom zurückgeliefert.
public static int[] erwGGT(int a, int b): Hier wird mittels erweitertem Eukli-
dischen Algorithmus zusätzlich zum größten gemeinsamen Teilers noch das Inverse zu
48
5 Implementierung
a modulo b berechnet. Zurückgeliefert wird ein int Array in dem die inversen Elemente
und der größte gemeinsame Teiler enthalten sind: An der Stelle 0 des Arrays befindet sich
der größte gemeinsame Teiler. An der Stelle 1 befindet sich das inverse Element für a, an
der Stelle 2 das inverse Element für b. Sollte die Funktion mittels array = erwGGT(a,b);
aufgerufen werden, so gilt dass array[0] = a · array[1] + b · array[2].
public static Polynom[] erwGGT(Polynom a, Polynom b): Ermittelt mit Hilfe des erweiterten Euklidischen Algorithmus das Inverse zu a modulo b. Liefert ein Polynom Array
mit drei Werten: An der Stelle 0 des Arrays befindet sich der größte gemeinsame Teiler.
An der Stelle 1 befindet sich das inverse Element für a, an der Stelle 2 das inverse Element für b. Sollte die Funktion mittels array = erwGGT(a,b); aufgerufen werden, so gilt
dass array[0] = a · array[1] + b · array[2]. Sollte ein Fehler aufgetreten sein, wird null
zurückgeliefert.
public static BigInteger[] erwGGT(BigInteger a, BigInteger b): Hier wird mittels
erweitertem Euklidischen Algorithmus das Inverse zu a modulo b berechnet. Zurückgeliefert
wird ein BigInteger Array in dem die Koeffizienten und der größte gemeinsame Teiler
vorhanden sind: [0] = ggT; [1] = Koeffizient für a; [2] = Koeffizient für b.
math.Funktionen: In dieser Klasse werden Funktionen sowohl zur Multiplikation als auch zur
Potenzierung angeboten. Des Weiteren wird eine Methode angeboten, um einen BigInteger
Wert binär darzustellen. Letztere kann für einige Algorithmen auch außerhalb dieser Klasse
hilfreich sein.
public static BigInteger mult(BigInteger x, BigInteger y): Verwendet die Methoden der BigInteger Klasse, um das Produkt x · y zu berechnen. Es wird als Ergebnis
ein BigInteger Objekt zurückgliefert.
public static BigInteger mult(BigInteger x, BigInteger y, BigInteger m): Verwendet die Methoden der BigInteger Klasse um das Produkt x · y MOD m zu berechnen.
Es wird als Ergebnis ein BigInteger Objekt zurückgliefert.
public static Polynom potenzPolynom(Polynom x, BigInteger y, Polynom m: Ermit-
telt die Potenz eines Polynoms x mit einer Zahl y und berechnet modulo m durch Anwendung des Repeated Square and Multiplying Algorithmus. Liefert die Potenz xy MOD m als
Polynom Objekt oder wirft eine Exception wenn x und m nicht im selben Zp sind, oder wenn
die Restklassen nicht richtig erstellt werden konnten (beispielsweise durch null-Werte).
public static BigInteger potenz(BigInteger x, int y): Verwendet die Methoden
der BigInteger Klasse um die Potenz xy zu berechnen. Das Ergebnis wird als BigInteger
returniert.
public static BigInteger potenz(BigInteger x, BigInteger y, BigInteger m): Verwendet die Methoden der BigInteger Klasse um die Potenz xy MOD m zu berechnen.
Das Ergebnis wird als BigInteger returniert.
public static Vector<Byte> toBinaerBigInteger(BigInteger x): Liefert die binäre
Abbildung des BigInteger Wertes und schreibt diese in einen Byte Vector, wobei das
letzte Element im Vektor das höchstwertigste Bit ist.
math.Restklasse: In Abschnitt 5.1 wurde gezeigt, wie sich die Datenstruktur der Restklasse
zusammensetzt. Hier wird auf die einzelnen Methoden eingegangen. Die Java-Klasse
5.2 Bibliothek
49
Restklasse stellt sich aus dem Repräsentanten r und dem Modulus n dar. Diese beiden
Variablen werden bei den Methodenbeschreibungen verwendet.
public static Restklasse add(Restklasse x, Restklasse y): Diese Methode addiert
zwei Restklassen und gibt x + y zurück oder wirft eine Exception, falls x und y nicht im
selben Ring Zp sind oder eine Variable nicht gesetzt ist. So wird beispielsweise durch den
Aufruf z = Restklasse.add(Restklasse x, Restklasse y); die Summe z = (x+y) MOD n
berechnet, sofern n der Modulus beider Restklassen x und y ist. Das Ergebnis wird als
Restklasse Objekt zurückgeliefert.
public Restklasse add(Restklasse x): Addiert zum aktuellen Objekt (Repräsentant
r) die Restklasse x, vorausgesetzt, dass der Modulus von x mit dem Modulus der Restklasse r übereinstimmt. Andernfalls wird eine Exception geworfen. Ein Aufruf r.add(x);
berechnet somit r = (r + x) MOD n sofern auch x den Modulus n besitzt
public static Restklasse divide(Restklasse x, Restklasse y): Berechnet das Pro-
dukt x · y −1 MOD n, wobei n der Modulus ist, welchen x und y gemeinsam haben müssen.
Falls das inverse Element nicht existiert (also y|n) oder die Moduli verschieden sind wird
eine Exception geworfen. Zurückgeliefert wird das Ergebnis als Restklasse Objekt.
public static Restklasse sub(Restklasse x, Restklasse y): Subtrahiert die zwei
Restklassen (x − y), bildet eine neue Restklasse und gibt diese zurück. Eine Exception
wird geworfen, sollten die beiden Restklassen unterschiedliche Moduli besitzen.
public Restklasse sub(Restklasse x): Subtrahiert vom aktuellen Objekt (this) den
mitgelieferten Wert x. Der Aufruf r.sub(x); berechnet r = r − x. Das Ergebnis wird als
Restklasse Objekt zurückgeliefert.
public static Restklasse mult(Restklasse x, Restklasse y): Kontrolliert, ob die
beiden Restklassen denselben Modulus n besitzen und returniert das Produkt x · y MOD n.
Sollten die Restklassen unterschiedliche Moduli besitzen oder eine der beiden Restklassen
nicht gesetzt sein (null) wird eine Exception geworfen. Das Ergebnis wird als Restklasse
Objekt zurückgeliefert.
public BigInteger getModulus(): Liefert den Modulus n des aktuellen Objekts als
BigInteger Objekt.
public BigInteger getValue(): Liefert den Repräsentanten r der Restklasse als
BigInteger Objekt.
public BigInteger potenzMod(BigInteger x): Potenziert das aktuelle Objekt (this)
mit dem mitgelieferten Wert x modulo n des aktuellen Objekts. Mit r.potenz(x); wird
somit die Berechnung r = rx MOD n ausgeführt.
public Restklasse potenzMod(Restklasse x): Potenziert das aktuelle Objekt (this)
mit dem mitgelieferten Wert x modulo des Modulus des aktuellen Objekts. Diese Methode
unterscheidet sich von der potenz(BigInteger x) Methode lediglich vom Übergabeparameter. Das Ergebnis wird als Restklasse Objekt zurückgeliefert.
math.Faktorisieren: In dieser Klasse wurde der Pollard ρ-Algorithmus zur Faktorisierung
einer Zahl implementiert.
50
5 Implementierung
public static Vector<Integer> factNummer(int a, int x1, int n): In dieser Funktion wird die Zahl n faktorisiert, dafür werden zwei zusätzliche Parameter benötigt: Der
Eingabeparameter a sollte eine Zufallszahl sein, wobei gelten muss, dass a 6∈ {0, 2}. Der
zweite Eingangsparameter x1 sollte eine Zufallszahl aus {0, . . . , n − 1} sein. Diese Methode
findet (wie aus Abschnitt 2.9 bereits bekannt) nicht zwingend eine Faktorisierung. Als
Rückgabewert wird ein Vector mit Integer Werten geliefert, der die gefundenen Faktoren
beinhaltet. Können keine Faktoren gefunden werden, wird eine Exception geworfen.
polynome.Polynom: Der Konstruktor der Java-Klasse Polynom erwartet als Eingabeparameter einen BigInteger Wert n und einen String welcher ein Polynom textuell als gewichtete
Summe von Potenzen repräsentiert. Also beispielsweise “xˆ5+3xˆ2+2x+1”. Das Polynom
ist Element aus der Struktur Zn [x], weshalb sich die Koeffizienten in Zn befinden. Sollte
sich ein Koeffizient nicht in Zn befinden, wird modulo n reduziert.
public static Polynom add(Polynom poly1, Polynom poly2): Addiert die zwei gegebenen Polynome und liefert die Summe. Diese Methode wirft eine Exception, wenn die
Eingabeparameter nicht in der selben Struktur Zn [x] liegen, oder wenn beide Referenzen
null sind. Wenn eines der beiden Polynome == null ist, wird das andere returniert.
public void add(Polynom poly): Addiert das gegebene Polynom (poly) zu dem aktuellen (this) Polynom (f = f + poly). Falls poly == null übergeben wird bleibt das Poly-
nom (f ) unverändert.Sollten die Polynome unterschiedliche n-Werte aufweisen, wird eine
Exception geworfen.
public static boolean checkZp(Polynom poly1, Polynom poly2): Diese Methode kon-
trolliert, ob die zwei gegebenen Polynome in der gleichen Struktur Zn [x] liegen und liefert
true falls dies der Fall ist. Ansonsten wird false zurückgegeben.
public Polynom clone(): Erzeugt eine Kopie vom aktuellen Polynom und liefert diese
Kopie zurück.
public static Polynom[] div(Polynom dividend, Polynom divisor): Führt eine Divi-
sion mit Rest durch, durch Verwendung des Algorithmus laut [Kurz07]. Zurückgegeben
wird ein Array mit 2 Einträgen vom Typ Polynom, wobei der erste der Quotient und der
zweite Eintrag der Divisionsrest ist. Sollte ein Eingabeparameter null sein oder die Polynome liegen nicht in der selben algebraischen Struktur Zn wird eine Exception geworfen.
public void div(Polynom divisor): Dividiert das aktuelle Objekt f durch den gegebenen divisor. Sollte der divisor == null sein oder die Polynome liegen nicht in der selben
algebraischen Struktur Zn wird eine Exception geworfen.
public static Polynom[] erwGGT(Polynom a, Polynom b): Berechnet den größten gemeinsamen Teiler von a und b und liefert zusätzlich das Inverse zu a modulo b. Liefert
ein Polynom Array mit drei Werten: An der Stelle 0 des Arrays befindet sich der größte
gemeinsame Teiler. An der Stelle 1 befindet sich das inverse Element für a, an der Stelle
2 das inverse Element für b. Sollte die Funktion mittels array = erwGGT(a,b); aufgerufen
werden, so gilt dass array[0] = a · array[1] + b · array[2]. Sollte ein Fehler aufgetreten sein,
wird eine Exception geworfen.
public static Polynom[] erwGGT(Polynom a, Polynom b, boolean normiert): Berechnet den größten gemeinsamen Teiler von a und b und liefert zusätzlich das Inverse zu
5.2 Bibliothek
51
a modulo b. Wenn normiert == true gilt, wird das Ergebnis normiert und die jeweiligen Koeffizienten angepasst. Es wird ein Polynom Array zurückgeliefert, dessen Belegung
äquivalent zur Methode erwGGT(Polynom a, Polynom b) ist. Sollten null Werte übergeben
werden oder Fehler in der Berechnung auftauchen wird eine Exception geworfen.
public Restklasse getLeadingCoefficient(): Findet den Koeffizient höchsten Grades des Polynoms und returniert diesen. Liefert null, wenn das Polynom selbst oder alle
Koeffizienten darin null sind.
public int findDegree(): Findet die höchste im Polynom vorkommende Potenz und
somit den Grad des Polynoms.
public BigInteger getModulus(): Liefert den Modulus des Polynoms als BigInteger
Objekt zurück.
public Restklasse[] getValues(): Gibt die Koeffizienten des Polynoms als Restklassen
Array zurück.
public static boolean isIrreduzibel(Polynom f): Testet ob das gegebene Polynom
f (x) ∈ Zn [x] irreduzibel ist. Wirft eine Exception, falls n nicht prim ist (die Irrtumswahrscheinlichkeit liegt bei (konstant) 2−10 ). Liefert true falls f (x) irreduzibel über Zn ist,
ansonsten false.
public static boolean isZero(Polynom poly): Kontrolliert, ob das gegebene Polynom
konstant 0 ist.
public static Polynom mod(Polynom p, Polynom f): Berechnet den Divisionsrest von
p und f (p(x) MOD f (x)) und returniert ihn als Polynom. Eine Exception wird geworfen,
wenn sich die Polynome nicht in der gleichen Struktur Zn [x] befinden.
public static Polynom multiplicativeInverse(Polynom p, Polynom f): Ermittelt das
multiplikative Inverse von p(x), also p(x)−1 MOD f (x). Hierfür wird analog wie bei Zahlen
der erweiterte Euklidische Algorithmus herangezogen. Die Methode wirft eine Exception
falls das Inverse nicht existiert.
public void multiply(Polynom poly): Multipliziert das vorliegende Polynom f mit
dem als Parameter übergebenen Polynom.
public static Polynom multiply(Polynom poly1, Polynom poly2): Multipliziert zwei
Polynome. Wirf eine Exception, wenn die zwei Polynome nicht in der selben algebraischen
Struktur Zn [x] liegen.
public Restklasse makePolynomMonic(): Normiert das Polynom und liefert den Leitko-
effizienten als Restklasse zurück.
public static Polynom power(Polynom a, Polynom b, Polynom m): Kontrolliert, ob b
eine Zahl ist (ein Polynom vom Grad 0) und berechnet die Potenz. Sollte b ein Polynom
höheren Grades sein, wird eine Exception geworfen. Andernfalls wird a(x)b MOD m(x)
berechnet und zurückgeliefert.
public static Polynom power(Polynom a, BigInteger b, Polynom m): Berechnet die
Potenz eines Polynoms a mit einer BigInteger Zahl b und ermittelt a(x)b MOD m(x).
Liefert das Ergebnis als Polynom zurück. Eine Exception wird geworfen, sollte ein Fehler
auftreten.
52
5 Implementierung
public void sub(Polynom p): Subtrahiert vom aktuellen Polynom f (x) das gegebene
Polynom p(x) (f (x) = f (x) − p(x)
public static Polynom sub(Polynom minuend, Polynom subtrahend): Berechnet die
Differenz minuend − subtrahend und gibt sie als Polynom Objekt zurück.
Neben den berechnenden Klassen zur Ausführung einzelner Algorithmen, werden auch verschieden funktionale Klassen angeboten, die für die Implementierung einer Applikation hilfreich sein
können. Sie umfassen Sprachverwaltung (Änderung und Erweiterung von Textinhalten), Dialogsteuerung (Ausgabe von Meldungen und Einlesen von NutzerInneneingaben) sowie Bildverwaltung (Laden von und Zugreifen auf Bilder, welche in der Applikation angezeigt werden).
Folgend sollen die einzelnen Klassen und Methoden im Detail vorgestellt werden:
util.StringCreator: Die StringCreator Klasse ermöglicht eine einfache Änderung der Sprache für die BenutzerInnenschnittstelle. Der StringCreator wird beim Starten in der
Hauptklasse erzeugt. In der Applikation sollten anschließend alle Textstellen von diesem
StringCreator geladen bzw. erzeugt werden. So kann man durch weitere XML-Dateien
die Sprache der Applikation anpassen und weitere Sprachen hinzufügen.
public StringCreator(String languageFile): Der Konstruktor der Klasse öffnet den
mitgelieferten Pfad zur XML-Datei und erzeugt daraus ein Java org.jdom.Document Objekt. Dieses Objekt bietet entsprechende Methoden an (beispielsweise getDescendants()
um auf die einzelnen XML-Tags in dem Dokument zugreifen zu können. Kann der Pfad
nicht geöffnet werden (weil der Pfad nicht existiert oder die Datei kein gültiges XML
Dokument ist), so wird eine entsprechende Exception geworfen.
public static final String getTagFromXML(String tag): Liest den Inhalt des gelieferten tags aus dem bei der Initialisierung erzeugten Dokument aus. Eine Exception wird
geworfen, sollte dieser tag nicht vorhanden sein. Sollte der tag gefunden werden, wird der
entsprechende Inhalt als String zurückgeliefert.
util.ImagesLoader: Der ImagesLoader verwaltet alle Bilder, die in der Applikation Verwendung finden. Er stellt Methoden zum Laden einzelner Bilder und zum Zugreifen auf den
bestehenden Bilderpool bereit. Wenn ein Bild das erste mal geladen wird, wird es in den
Pool abgespeichert, sodass jeder weitere Zugriff nicht auf das Dateiensystem zugreifen
muss, sondern direkt aus dem Pool geladen werden kann. Ein Anwendungsbeispiel für das
Laden eines Bildes (“save.png”) mit Hilfe des ImagesLoaders wäre:
this.setIcon(ImagesLoader.getInstance().getImageIcon("/save.png"));
public static ImagesLoader getInstance(): Liefert das ImagesLoader Objekt zurück.
Sollte dieses zuvor noch nicht erzeugt worden sein, wird es erstellt.
public ImageIcon getImageIcon(String resourceName): Erhält den Pfad zur Bilddatei
und lädt das Bild – sofern noch nicht vorhanden – vom Dateisystem in den Pool des
ImagesLoader Objektes. Liefert das gewünschte Bild als ImageIcon Objekt zurück. Sollte
das Bild im Dateisystem nicht vorhanden sein, wird eine Exception geworfen.
public Image getImage(String url): Erhält den Pfad zur Bilddatei und lädt das Bild
– sofern noch nicht vorhanden – in den Pool. Liefert das Image Objekt zurück. Sollte das
Bild nicht vorhanden sein, wird eine Exception geworfen.
5.2 Bibliothek
53
public Vector<String> getLoadedImageResourceNames(): Liefert den Pool aller bisher
geladenen Bilder als dynamisches Array von Strings zurück.
ui.dialogs.UIDialogFactory: Die UIDialogFactory vereinfacht die Handhabung von BenutzerInnendialogen, indem sie für die Realisierung von Standarddialogen eine Factory anbietet.
So können beispielsweise Dialoge für einfache Eingaben wie folgt erstellt werden:
String in = UIDialogFactory.showInputDialog(frame_.getContentPane(),
"Geben Sie bitte den Parameter ein:", 10, "Parametereingabe",
JOptionPane.QUESTION_MESSAGE);
Der erzeugte Eingabedialog wird in Abbildung 5.1 gezeigt.
Abb. 5.1: Durch die UIDialogFactory erzeugter Dialog
public static UIDialogFactory getInstance(): Liefert die Instanz des aktuellen
UIDialogFactory Objektes. Sollte sie zuvor noch nicht erzeugt worden sein, wird sie generiert.
public static void showErrorDialog(JFrame jf, Object nachricht, String titel):
Zeigt einen Fehlerdialog mit der gegebenen nachricht und dem titel an.
public static void showInfoDialog(JFrame jf, Object nachricht, String titel):
Zeigt einen Infodialog mit der gegebenen nachricht und dem titel.
public static String showInputDialog(Component comp, Object nachricht, Object
standardWert, String titel, int typ): Erstellt einen einfachen Eingabedialog mit einer
nachricht, einem titel und einem Eingabefeld. Der typ entspricht den Typen aus dem
javax.swing.JOptionPane Objekt (ERROR_MESSAGE, QUESTION_MESSAGE, WARNING_MESSAGE,
INFORMATION_MESSAGE oder PLAIN_MESSAGE) und kann aus diesem – wie beim Beispiel zuvor
– übergeben werden. Das Eingabefeld wird zu Beginn mit dem standardWert gefüllt. Die
Methode gibt den eingegebenen Text als String zurück.
public static String showInputDialog(Component comp, Object nachricht, String
titel, int nachrichtTyp): Zeigt einen Eingabedialog mit titel und nachricht im mitgelieferten nachrichtTyp an und gibt den eingegeben Text als String zurück.
public static String showInputDialog(Component comp, Object nachricht, String
titel, int nachrichtTyp, Icon bild, String[] werte, String initSelectValue): Diese Methode erstellt einen Eingabedialog mit titel und nachricht im mitgelieferten
nachrichtTyp. Weiters kann ein bild als Icon Objekt mitgeliefert werden (ein Beispiel
dafür kann in Abbildung 5.2 gesehen werden). Das String Array werte beinhaltet Texte,
welche als JComboBox, JList, oder JTextField dargestellt werden (“It is up to the UI to
decide how best to represent the selectionValues, but usually a JComboBox, JList, or
JTextField will be used.”[SUN10c]). Die Auswahl wird als String returniert.
54
5 Implementierung
Abb. 5.2: Anzeige eines Icons in einem von UIDialogFactory erzeugten Dialog
public static int showYesCancel(JFrame jf, Object n, String t): Erzeugt einen
Dialog mit der Nachricht n, dem Titel t und zwei Knöpfen ’Ja’ und ’Abbrechen’ (siehe
Abbildung 5.2). Die Methode liefert als Rückgabewert den int-Wert YES_OPTION oder
CANCEL_OPTION des JOptionPane Objektes.
public static int showYesNoCancel(JFrame jf, Object n, String t): Erzeugt einen
Dialog mit der Nachricht n, dem Titel t und den Knöpfen ’Ja’, ’Nein’ und ’Abbrechen’
(siehe Abbildung 5.2). Liefert die int-Werte YES_OPTION, NO_OPTION oder CANCEL_OPTION
des JOptionPane Objektes zurück.
Abb. 5.3: showYesCancel und showYesNoCancel Dialoge
Verwendung
Die meisten Methoden wurde sowohl statisch als auch dynamisch implementiert. Für erste Tests
währende der Entwicklung wurde eine einfache Testumgebung implementiert, die unter ui.Main
gestartet werden kann. Die Swing Oberfläche wird in ui.Wik bzw. ui.WikFrame definiert. Abbildung 5.4 zeigt einen Screenshot dieser Testapplikation. Die Menü- und Symbolleiste wurden für
eine potenzielle künftige Verwendung angelegt, haben in der aktuellen Version des Programms
aber keine Funktion.
Ausblick
Dieser Abschnitt enthält Anregungen für einen zukünftigen Ausbau der Bibliothek, welche im
Rahmen der vorliegenden Arbeit entstanden ist.
Zufallszahlengenerator: Es wurde durch die Klasse math.RandomGenerator eine Funktion zur
Generierung von Zufallszahlen zu Verfügung gestellt. Um Pseudozufallszahlen erzeugen zu
können, muss diese erweitert werden; gegenwärtig enthält die Bibliothek keine diesbezügliche Funktionalität. Beispiele um mittels AES beliebig lange Zufallszahlen generieren zu
können, wurden in Abbildung 2.1 und Abbildung 4.1 angegeben. Außerdem wurde bereits
in Abschnitt 2.2 auf die Möglichkeit der Verwendung der BSAFE Bibliothek hingewiesen.
Chinesischer Restsatz: In Abschnitt 2.8 wurde Garners Algorithmus (Algorithmus 2.8) angegeben. Außerdem wurde auf den in [MeVO96] angegebenen Gauss’schen Algorithmus
5.2 Bibliothek
55
Abb. 5.4: Umgebung zum Testen der einzelnen Funktionen
verwiesen. Der Stackrechner in der vorliegenden Fassung kann im Rahmen von NachfolgeArbeiten (Diplomarbeiten oder Praktika) um diese Funktionaliäten erweitert werden.
Finden von q-ten primitiven Einheitswurzeln: In Abschnitt 3.5 wird ein einfacher Algorithmus zum Auffinden von q-ten primitiven Einheitswurzeln angegeben. Auch diese Funktion kann in Folgeprojekten hinzugefügt werden.
StringCreator
Die StringCreator Klasse und ihre Methoden wurden bereits vorgestellt. Nun soll die Handhabung des StringCreators detaillierter erklärt werden. Der StringCreator ermöglicht es, unterschiedliche XML-Dateien einzulesen und daraus unterschiedliche Textstücke zu erzeugen. In
der Applikation, welche auf die Funktionsbibliothek zugreift, sollten Textstücke immer über
den StringCreator erzeugt werden. So kann sichergestellt werden, dass bei Austauschen der
XML-Sprachdatei alle Texte in der Applikation (wie beispielsweise Menübeschriftungen, Statusmeldungen oder Texte in Hinweisfenstern) geändert werden. Mit Hilfe des StringCreators wurde
eine Unterstützung für eine multilinguale BenutzerInnenoberfläche realisiert, in der man durch
Austauschen der XML-Sprachdatei die Sprache der Applikation wechseln kann. Will man die
Oberfläche in einer anderen Sprache sehen, so braucht man nur die vorhandene XML-Datei zu
übersetzen und in den Ordner ./xml/ abzulegen. Das Programm sucht diesen Ordner nach vorhandenen XML-Dateien ab und listet die gefundenen Ergebnisse. Auf diese Weise kann man die
verfügbaren Sprachdateien einfach erweitern. In der Applikation kann anschließend ausgewählt
werden, welche der XML-Sprachdateien verwendet werden soll. Mittels System Preferences
56
5 Implementierung
(siehe weiter unten) wird verwaltet, welche Sprachdatei beim Programmstart verwendet werden
soll. Da die Textdatei bei einem Start der Applikation eingelesen wird, muss das Programm nach
einer Änderung neu gestartet werden, erst danach wird der Text in der neuen Sprache angezeigt.
Für die Realisierung dieser Sprachverwaltung wurden für alle Textstücke geeignete Tags in der
XML Datei abgespeichert. Mittels JDOM [Hunt10] wird die Datei durchsucht und ausgelesen.
Möchte man das Programm so erweitern, dass weitere Texte benötigt werden, dann ist folgendes
zu berücksichtigen:
1. Der gewünschte Text muss in einem geeigneten Tag in allen XML Dateien angelegt werden.
Sollte der Eintrag in einer Datei fehlen, kommt es – sofern diese Sprachdatei ausgewählt
ist – beim Starten zu einer Fehlermeldung. Das Anlegen eines neuen Textes könnte in der
XML Datei wie folgt aussehen:
<titles>
<title-neuerTitel>Mein neuer Titel</title-neuerTitel>
</titles>
2. In der Klasse util.StringCreator sollte ein String angelegt werden, der den Namen des
Tags aus der XML-Datei beinhaltet.
public static final String getNeuerTitel = "title-neuerTitel";
3. Bevor man den neuen Text verwenden will, muss man das StringCreator Objekt mit dem
Pfad zur Sprachdatei erzeugen
new StringCreator("./xml/languageDE.xml");
Das Erzeugen des StringCreator Objektes muss beim Starten der Applikation erfolgen.
4. Nun muss man die StringCreator Methode getTagFromXML(String tag) verwenden um den
Text zu aus der XML-Sprachdatei zu lesen.
String text = StringCreator.getTagFromXML(StringCreator.getNeuerTitel);
Diese Methode sucht im Dokument die dem übermittelten Tag untergeordneten XML-Tags
(“Nachkommen”), liest diese aus und returniert den Inhalt. Sollten mehrere gleichnamige
Tags vorhanden sein, wird der erste zurückgeliefert, andere werden ignoriert. Sollte die
Methode den angegebenen Tag nicht finden, wird eine java.util.NoSuchElementException
geworfen.
Verwaltung von Programmeinstellungen
In der Applikation werden Daten wie die Position und Größe des Fensters oder die ausgewählte
Sprache automatisch (ohne BenutzerInneninteraktion) gespeichert. Dafür wird das Preferences
-Objekt aus dem java.util.prefs Paket verwendet. Beim Öffnen des Programms wird ein
Preferences-Objekt statisch erzeugt; in dieses Objekt können Texte, logische Werte oder Zahlen (int, long, float, double) abgespeichert werden. Man muss geeignete Schlüssel (Text)
definieren, um auf die einzelnen Variablen zuzugreifen bzw. auf diese zu schreiben. Wenn in
5.3 Demonstrator
57
dem Objekt die Variablen beim Start der Anwendung gesetzt sind, werden sie ausgelesen (beispielsweise die Position des Fensters oder die verwendete Sprache), sonst werden Standardwerte
verwendet. Bei einer Änderung (der Sprache oder der Position des Fensters) wird die Variable
in dem Preferences Objekt neu gesetzt. Wenn die Anwendung erneut gestartet wird, werden die
neuen Daten ausgelesen. Das Anlegen des Preference-Objekts und einiger Schlüsselwörter als
Variablennamen sieht wie folgt aus:
//Deklarieren des Objektes
final static Preferences prefs = Preferences.userRoot();
//Deklarieren der Schlüsselwörter
final static String pref_frame_width = "frame_width";
final static String pref_frame_height = "frame_height";
final static String pref_frame_maximize = "frame_maximize";
final static String pref_frame_language = "frame_language";
Möchte man jetzt den StringCreator mit der entsprechenden Sprachdatei erzeugen oder die
Größe des Fensters der Applikation einstellen, so liest man die benötigten Daten aus den dem
Preferences-Objekt aus. Sollte die entsprechende Variable noch nicht gesetzt sein (beispielsweise
wenn die Applikation das erste Mal gestartet wird) werden Standardwerte verwendet.
//aus dem Objekt lesen, wenn Variable nicht vorhanden, benutze Standardwerte
//Erzeugen des StringCreators
new StringCreator(prefs.get(pref_frame_language, "./xml/languageDE.xml"));
//Größe des Fensters festlegen
frame_.setSize(new Dimension(prefs.getInt(pref_frame_width, standardWidth),
prefs.getInt(pref_frame_height, standardHeight)));
if (prefs.getBoolean(pref_frame_maximize, false)) {
frame_.setExtendedState(Frame.MAXIMIZED_BOTH);
}
Wenn man die entsprechenden Variablenwerte ändern möchte (nachdem das Fenster bewegt oder
die Sprache verändert wurde) geht man wie folgt vor:
//in das Preferences Objekt schreiben
prefs.putBoolean(pref_frame_maximize, true);
prefs.putInt(pref_frame_height, this.getComponent().getHeight());
prefs.putInt(pref_frame_width, this.getComponent().getWidth());
prefs.put(pref_frame_language, dateiname);
5.3 Demonstrator
Im Rahmen dieser Arbeit wurden ein Rechner implementiert, der die zuvor vorgestellte Bibliothek verwendet. Um die Eingabe zu vereinfachen wurde ein Stack verwendet, wobei sämtliche
Operationen als Eingabeparameter die jeweils obersten Elemente auf dem Stack erhalten. Der
58
5 Implementierung
Stack selbst ist manipulierbar, so kann man die Elemente zyklisch hinauf und hinunter verschieben oder die zwei obersten Elemente vertauschen.
Abbildung 5.5 zeigt den ui.panels.StackRechner nach dem ersten Start. Auf der rechten Seite
befinden sich vier Knöpfe. Die ersten Zwei verschieben den Stack zyklisch nach oben oder unten.
Der dritte löscht den gesamten Stackinhalt. Der unterste Knopf öffnet den Sprachauswahldialog
(siehe Abbildung 5.6). Sollte man die Sprache ändern wollen, muss der Stackrechner neu gestartet
werden. Erst dann werden die Änderungen übernommen.
Abb. 5.5: Stackrechner nach dem Start
Abb. 5.6: Dialog zur Sprachauswahl
Möchte man Berechnungen durchführen, müssen zuerst Elemente auf den Stack gelegt werden. Dafür muss man in das Eingabefeld ein Polynom oder eine Zahl eingeben. Möchte man
ein Polynom eingeben, muss auch den Modulus des Polynoms angegeben werden. Sollte eine
Zahl hinzugefügt werden, muss das Eingabefeld für die Charakteristik des zu Grunde liegenden
Polynomrings leer bleiben (siehe Abbildung 5.7). Mittels Betätigung der Eingabetasten oder
Mausklick auf den Push-Knopf kann das Element nun auf den Stack gelegt werden. Möchte
man ein Element vom Stack lesen, verwendet man den Pop-Knopf. Dieser nimmt das oberste
Element aus dem Stack und legt es in das Eingabefeld. Handelt es sich dabei um ein Polynom,
so wird auch der Modulus in das entsprechende Eingabefeld eingefügt.
Die meisten Berechnungen benötigen mindestens zwei Elemente am Stack, einige Berechnungen
benötigen auch drei (beispielsweise Potenzieren bzw. Multiplizieren mit Moduloreduktion) oder
5.3 Demonstrator
59
Abb. 5.7: Beispiele für die Eingabe von Polynomen und Zahlen
nur ein Element (Primzahltest von Zahlen bzw. Irreduzibilitätstest von Polynomen). Sollten
nicht ausreichend viele Elemente – oder Elemente vom falschen Typ – am Stack vorhanden sein,
werden Fehlermeldungen angezeigt, wie in Abbildung 5.8 ersichtlich ist.
Abb. 5.8: Fehlermeldungen
Der Stackrechner nimmt für Berechnungen die Elemente vom Stack und legt das Ergebnis wieder auf den Stack. Die Elemente, die zur Berechnung herangezogen werden, werden vom Stack
gelöscht. Wenn man diese Elemente am Stack belassen möchte, kann man die JCheckBox ’Elemente am Stack behalten’ aktivieren. Beim nächsten Rechenvorgang werden die zur Berechnung benötigen Variablen am Stack belassen und das Ergebnis – oder die Ergebnisse – werden
zu oberst auf den Stack gelegt. Gleiches gilt, wenn man die Pop Funktion benutzt: Wenn die
JCheckBox aktiviert ist, wird zwar das Element aus dem Stack ausgelesen und in die Eingabefelder geschrieben, es wird aber nicht vom Stack gelöscht.
In Abbildung 5.9 sieht man den Stack des Stackrechners, der in drei Spalten dargestellt wird.
Links sieht man welche Elemente am Stack welcher Variablen zugeordnet sind. Dies dient einer
leichteren Zuordnung zwischen der Beschriftung der Knöpfe für Berechnungen und den Elementen am Stack, welche entsprechend mit X, Y und Z markiert sind. In der mittleren Spalte
sieht man die einzelnen Elemente am Stack. Rechts ist erkennbar, in welcher Struktur sich das
jeweilige Element befindet (es wird beispielsweise angegeben, in welchem Polynomring sich das
Polynom befindet). Sollte in dieser Spalte nichts angezeigt werden, dann handelt es sich um eine
ganze Zahl. In Abbildung 5.9 ist das Element ganz unten (“1”) keine Zahl, sondern ein Polynom vom Grad 0 im Polynomring Z2 [x], das zweite Element am Stack (die Variable Y ) ist eine
Zahl. In Abbildung 5.10 ist das Tastenfeld des Stackrechners abgebildet. Man erkennt darauf
die Grundrechnungsarten Addition, Subtraktion und Multiplikation, welche in Tabelle 5.1 näher
beschrieben werden. Die restlichen Funktionen werden in der Tabelle 5.2 dargestellt.
In Tabelle 5.3 werden die Tasten Push und Pop beschrieben. Die weiteren Funktionstasten
werden in Tabelle 5.4 näher erläutert.
60
5 Implementierung
Abb. 5.9: Stack des Stackrechners
Tab. 5.1: Beschreibung der Grundrechnungsarten
Kommandos
Beschreibung
Berechnet die Summe von X und Y .
Hier muss man auf das Stackprinzip aufpassen: Es werden zwei Variable auf den Stack gelegt und die Differenz
errechnet. Dabei wird aber die zuletzt hinzugefügte Variable von der zuvor hinzugefügten subtrahiert (Y - X ), intuitiv könnte man eine umgekehrte Verwendung erwarten.
Berechnet das Produkt X · Y .
Berechnet das Produkt X · Y MOD Z.
Stackbelegung
zuvor
danach
X = 21
X = 49
Y = 28
X = 21
X=7
Y = 28
X = 21
Y = 28
X = 21
Y = 28
Z = 13
X = 588
X=3
Zusammenfassend: Wenn man eine Funktion auswählt, werden die Variablen vom Stack gelesen, das Ergebnis wird auf den Stack geschrieben und die Berechnungsschritte werden mitprotokolliert. Dieses Protokoll kann bearbeitet und Notizen können hinzugefügt werden (Diese
Änderungen bleiben durch weiteren Rechenvorgänge unberührt). Wenn man das Protokoll in
die Zwischenablage kopieren möchte, reicht der Mausklick auf den Knopf rechts von den Berechnungsschritten (siehe Abbildung 5.13).
Benutzte Bibliotheken
Nun wird erläutert, welche Bibliotheken – neben der in dieser Arbeit vorgestellten – der Stackrechner benutzt. Des Weiteren wird erklärt, welche Bibliotheken untersucht, aber aus diversen
Gründen nicht verwendet wurden. Zum Einsatz gekommen sind folgende Bibliotheken:
JDOM: Die Bibliothek JDOM wird genutz, um die XML Sprachdateien auszulesen. Die Bibliothek kann unter [Hunt10] heruntergeladen werden und bietet geeignete Objekte um
eine XML Datei zu parsen und auf einzelne Elemente zuzugreifen. Diese Bibliothek wird
hauptsächlich in der Klasse StringCreator verwendet.
JGOODIES: Werden von [Lent10] zur Verfügung gestellt und für die Darstellung der Oberfläche verwendet. Die Bibliothek beinhaltet verschiedene Designs. Im Stackrechner wird
das sogenannte com.jgoodies.looks.plastic.Plastic3DLookAndFeel Design verwendet. Durch eine geringfügige Änderung im Quellcode kann auf ein anderes Design gewechselt werden. Möglich wäre auch eine BenutzerInneninteraktion, wobei die Auswahl in den
Programmeinstellungen (Objekt Preferences) gespeichert werden könnte.
5.4 Konzept
61
Tab. 5.2: Beschreibung des Tastaturfeldes
Kommandos
Beschreibung
Berechnet den größten gemeinsamen Teiler von beiden
Variablen X und Y.
Berechnet neben dem größten gemeinsamen Teiler noch
die Variablen a und b, sodass ggT (X, Y ) = a · X + b · Y
und legt alle Ergebnisse auf den Stack.
Berechnet, sofern es sich bei Y um eine Zahl handelt, die
Potenz X Y MOD Z und legt das Ergebnis auf den Stack.
Errechnet
das
multiplikative
Inverse
Element
X −1
(mod Y ), sofern eines vorhanden ist. Sollte
es nicht berechenbar sein, zeigt die Funktion einen
entsprechenden Dialog an (siehe beispielsweise Abbildung 5.11).
Wechselt die Variablen X und Y und wird lediglich zur
Manipulation des Stacks verwendet.
Berechnet X MOD Y .
Diese Funktion ist nur auf Zahlen anwendbar und kontrolliert, ob das oberste Element im Stack eine Primzahl ist. Sollte die Zahl kleiner als 1016 sein, wird
mittels Probedivision getestet. Sollte die Zahl größer
sein, wird die BigInteger Methode angewendet und
man muss einen Sicherheitsparameter eingeben (siehe Abbildung 5.12). Diese Methode entscheidet mit
1 − ( 21 )Sicherheitsparameter Wahrscheinlichkeit, ob es sich
um eine Primzahl handelt.
Dies ist nur auf Polynome anwendbar und prüft ob es sich
beim obersten Element im Stack um ein irreduzibles Polynom handelt. Das entsprechende Ergebnis (beispielsweise: “[xˆ2 + x] ist reduzibel in Z2[x]” bzw. “[x + 1] ist irreduzibel in Z2[x]”) wird in den Berechnungsschritten ausgegeben.
Stackbelegung
zuvor
danach
X = 21
X=7
Y = 28
X = 21
X = -1
Y = 28
Y=1
Z=7
X = 21
X = 17
Y = 28
Z = 19
X = 34
X = 13
Y = 21
X = 21
X = 28
Y = 28
Y = 21
X = 21
X=8
Z = 13
Der Stack wird hier nicht
verändert. In den Berechnungsschritten wird angezeigt, ob es sich bei dem
obersten Element um eine
Primzahl handelt oder nicht.
Außerdem wird – sofern er
benötigt wird – der Sicherheitsparameter mit ausgegeben.
Der Stack wird auch hier
nicht verändert. In den Berechnungsschritten wird angezeigt, ob es sich bei dem
obersten Element um eine irreduzibles Polynom handelt
oder nicht.
Tab. 5.3: Beschreibung der Push und Pop Taste
Kommandos
Beschreibung
Legt ein Element auf den Stack.
Liest ein Element vom Stack.
Eingabefeld
zuvor
danach
112
112
Stackbelegung
zuvor
danach
X = 23
X = 112
Y = 23
X = 112
X = 23
Y = 23
5.4 Konzept
Der vorliegende Abschnitt skizziert die Applikation, welche das Endergebnis der vorliegenden
Arbeit darstellt. Durch Folgeprojekte sollte – aufbauend auf diese Diplomarbeit – das hier vorgestellte Konzept umgesetzt werden.
Realisierung eines Rechners
In Kapitel 4 wurde eine Sprachsyntax definiert, welche der umzusetzende Rechner verwenden
kann. Die Besonderheit dieser Sprache ist, dass man das ’Programm’ nicht bis zum Schluss fertig
schreiben muss. Stattdessen verarbeitet der Interpreter die Programmteile zeilenweise. So bekommt man Zwischenergebnisse sofort geliefert. Bei Schleifen oder Bedingungen wird natürlich
mehr als eine Zeile verwendet werden müssen. Aber grundsätzlich gilt, dass nach einem Zeile-
62
5 Implementierung
Tab. 5.4: Beschreibung der Funktionstasten
Kommandos
Beschreibung
Verschiebt die Elemente am Stack zyklisch nach oben.
Verschiebt die Elemente am Stack zyklisch nach unten.
Löscht alle Elemente vom Stack.
Stackbelegung
zuvor
danach
X = 112
X = 23
Y = 23
Y = 32
Z = 32
Z = 112
X = 23
X = 32
Y = 112
Y = 23
Z = 32
Z = 112
X = 112
leer
Y = 23
Z = 32
Öffnet den Sprachauswahldialog.
Kopiert die Berechnungsschritte in die Zwischenablage.
Abb. 5.10: Tastenfeld des Stackrechners
numbruch die Zeile geparst werden muss. Sollte die Zeile abgeschlossen sein – keine mehrzeilige
Anweisung enthalten sein – wird die Zeile ausgewertet bzw. das Ergebnis berechnet und sofort
am Bildschirm angezeigt. Bei mehrzeiligen Anweisungen wird bis zum Schließen des Blocke mit
der Auswertung gewartet. Sollten Variablen deklariert und initialisiert werden, werden auch
diese direkt nach ihrer Deklaration mit ihren Werten am Bildschirm angezeigt. Die Variablen
werden intern in einer Hash-Tabelle gespeichert. Bei Schleifen oder Bedingungen sollte erst zu
Schleifen- bzw. Bedingungsende eine Ausgabe erfolgen. Hier werden aber keine Variablenwerte
angegeben sondern lediglich das Verlassen einer Schleife bzw. eines Blockes wird mit einer Ausgabe symbolisiert. Wenn man eine bereits initialisierte Variable in die Eingabe schreibt, sollte
der Wert der Variable ausgegeben werden.
Der Rechner sollte eine Speicherfunktion besitzen, sodass alle Berechnungsschritte gesichert
werden können. Das Speichern könnte auf zwei verschiedene Arten realisiert werden:
1. Es werden nur die Eingaben gespeichert. Wird diese Datei geladen, so erfolgt die
Ausführung jedes Ausführungsblocks mit der Ausgabe der jeweiligen Zwischenergebnisse.
Diese Variante wäre einfach zu realisieren, da man nur die Eingaben speichern muss und
die Ausgaben vernachlässigen kann. Der Nachteil hierbei ist natürlich, dass beim Einlesen
alles neu berechnet werden muss. Wenn man längere Rechenfolgen in den Rechner lädt,
kann dies aufgrund der Neuberechnung zu längeren Bearbeitungszeiten führen.
2. Sowohl Ein- als auch Ausgaben werden gespeichert. Hierfür muss insbesondere der innere Programmzustand, d.h. die aktuell definierten Variablen einschließlich deren Belegung
5.4 Konzept
63
Abb. 5.11: Protokoll bei Bestimmung des multiplikativen Inversen Elementes
Abb. 5.12: Eingabedialog für Sicherheitsparameter beim Primzahltest
(Hash-Tabelle) mit abgespeichert werden. Beim Einlesen kann man diese aus der Datei
laden. Die Ein- bzw. Ausgaben können auch von der Datei ausgelesen und im Rechner
angezeigt werden. Der Vorteil dieser Variante ist, dass bei längeren Rechenfolgen die Applikation beim Einlesen der Datei diese nicht zeilenweise durchgehen muss, sondern sowohl
die Ausgaben (Zwischenergebnisse) als auch der innere Programmzustand aus der Datei
lesen kann, womit die innere Datenstruktur nicht neu errechnet werden muss. Das Speichern der Datei hingegen wird länger dauern, da man die gesamte Ein- und Ausgaben und
zusätzlich auch noch die interne Datenstruktur formatieren und speichern muss.
Es ist erkennbar, das jede der beiden Varianten spezifische Vor- und Nachteile hat. Die Entscheidung für eine der beiden Versionen kann in Folgeprojekten getroffen werden, hierbei kann
möglicherweise die erwartete Größe abgeschätzt und herangezogen werden: Für kurze Rechenvorgänge eignet sich die Variante 1 besser, für längere Rechenfolgen überwiegen die Vorteile
der Variante 2. Natürlich wäre es auch möglich, beide Varianten zu realisieren. Die Applikation
erkennt lange Rechenfolgen und verwendet die entsprechende Variante automatisch.
Angesichts des geplanten Einsatzes in der Lehre, wäre auch eine Export-Funktion für das von
Adobe Systems entwickelte PDF-Format hilfreich, damit Studierende ihre Berechnungsschritte bzw. Lösungen ausdrucken, zu den Übungseinheiten mitnehmen und anschließend in ihren
Arbeitsunterlagen abheften können.
Eine weitere Verbesserung der Gebrauchstauglichkeit der BenutzerInnenschnittstelle würde folgende Funktion darstellen: AnwenderInnen sollten individuelle Tastenkombinationen definieren
können, um oft benutzte Funktionen, Datentypen oder Kontrollstrukturen schnell in den Eingabebereich schreiben zu können.
Eingeschränkter Funktionsumfang für Studierende
Der zu realisierende Rechner sollte eine Funktion zum Speichern und Laden von Berechnungsfolgen besitzen. Um einer Weitergabe derartiger Dateien zwischen Studierenden untereinander
64
5 Implementierung
Abb. 5.13: Kopieren der Berechnungsschritte in die Zwischenablage
entgegenzuwirken, sollte es unterschiedliche Speicher- bzw. Lademöglichkeiten für Studierende
und Lehrende geben. Diese und weitere Überlegungen legen eine Trennung von zwei NutzerInnengruppen nahe. Die entsprechenden zwei Versionen – eine für Lehrende und eine für Studierende – können mittels ANT [Apa10] kompiliert werden. So können mit Hilfe eines Preprocessors (beispielsweise JavaPP [Krop10]) die Quelldateien derart manipuliert werden, dass die
Studierenden-Version eine andere Menüstruktur (Laden bzw. Speichern nicht möglich) besitzt
oder Code, welchen die Studierenden nicht bekommen sollten, gar nicht kompiliert und somit
auch nicht ausgeliefert wird (so kann dieser auch nicht nachträglich aktiviert werden). Reverse
Engineering sollte mittels Code Obfuscating (siehe [Lafo10]) erschwert werden. Die Funktionalitäten der Studierenden-Version sollte verhindern, dass fremde Ergebnisse geladen, bearbeitet
und als Eigenleistung eingereicht werden können. Dies kann bewerkstelligt werden indem bei
der Version für Studierende folgende Überlegungen berücksichtigt werden:
• Das Speichern von Dateien wird nicht erlaubt. So kann man bei der Version für Studierende das Arbeitsblatt nicht abspeichern und somit auch nicht weitergeben oder wiederverwenden. Dies bringt aber auch einen Nachteil mit sich: man kann Studierenden nicht
den Auftrag erteilen, Übungen auszuarbeiten und abzuschicken. Außerdem müssten lange
Rechenfolgen immer wieder erneut eingetippt werden.
• Das Laden von Dateien wird nicht erlaubt. So können Studierende die Übungen berechnen
und diese den ÜbungsleiterInnen zuschicken. Ein Nachteil hierbei wäre, dass die Studierenden die Übungsaufgaben in einer Sitzung fertigstellen müssen, da ein späteres Laden
und Fortsetzen der Arbeit an der Datei nicht möglich wäre.
• Das Arbeitsblatt wird signiert. Studierende müssen sich dazu beim Installieren des Rechners
registrieren und das Arbeitsblatt wird signiert. Sollte man ein Arbeitsblatt mit fremder
Signatur öffnen, kann man dieses nicht editieren. Man könnte auch zulassen, dass das
Arbeitsblatt editiert werden kann, beim Abspeichern bleibt aber die alte Signatur genauso
erhalten wie die neue. ÜbungsleiterInnen könnten so einsehen, dass dieses Arbeitsblatt
mehrere Signaturen besitzt und somit nicht von einer Person alleine erstellt wurde.
• Eine eindeutige Identifikationsnummer wird verwendet. Das Arbeitsblatt wird mit dieser
ID signiert. Sollte man eine ’fremde’ Datei öffnen, kann man die darin enthaltenen Rechen-
5.4 Konzept
65
schritte nicht ändern sondern nur lesen. Die ID wird vom Institut verwaltet und ausgegeben. Studierende müssen sich auf einer Homepage registrieren und erhalten anschließend
eine ID zugeschickt.
Da es Studierenden – nach diesen Überlegungen – doch möglich sein sollte, ihre Arbeitsblatts abzuspeichern, um diese an Lehrende senden zu können, macht das Verbieten des Speicherns wenig
Sinn. Sollte das Öffnen von Arbeitsblatts unterbunden werden, müssen Studierende die Aufgaben in einer einzigen Sitzung vollenden. Auch dies scheint wenig praktikabel. Das Signieren der
Arbeitsblatts mit einer eindeutigen Identifikationsnummer verbunden mit der Funktion, dass nur
auf dem Rechner erstellte Arbeitsblatts editiert werden können, würde zwar das Austauschen von
Lösungen unter Studierenden erlauben, aber alle müssten ihre Lösungen selbst durchrechnen.
Beispiel für Preprocessor
In diesem Abschnitt wird eine Möglichkeit vorgestellt, um die zuvor besprochenen verschiedenen
Funktionen zu realisieren. Um sicherzustellen, dass Studierende den ausgelieferten Code nicht
unautorisiert modifizieren können, soll der versteckte Code gar nicht mit ausgeliefert werden.
Dies kann mit einem Preprocessor leicht realisiert werden.
Im unten angeführten Beispiel wird angenommen, dass es in der Studierendenversion nicht
möglich sein soll, ein Arbeitsblatt abzuspeichern (natürlich können auch andere Funktionseinschränkungen – wie im vorigen Abschnitt besprochen – realisiert werden). Das Beispiel verwendet
die Syntax des JavaPP [Krop10] Preprocessor. Anfangs muss in Apache ANT ein neuer Befehl
(hier javapp) eingeführt werden, anschließend werden zwei Ziele (eine für die Vollversion für
Lehrende und eine für die eingeschränkte Version für Studierende) definiert. Im Ziel preprocess
-lehrende wird die Variable vollversion definiert, welche im Javacode abgefragt wird.
<taskdef name="javapp" classname="ca.slashdev.javapp.JavaPpTask" />
<target name="preprocess-lehrende">
<javapp destdir="staging" prefix="//#">
<fileset dir="src" includes="**/*.java" />
<property name="vollversion" value="1" />
</javapp>
</target>
Die Variable vollversion wird in dem Ziel preprocess-studierende nicht definiert.
<target name="preprocess-studierende">
<javapp destdir="staging" prefix="//#">
<fileset dir="src" includes="**/*.java" />
</javapp>
</target>
Beim Ausführen des Ziels preprocess-lehrende soll die Vollversion kompiliert werden. Um dies
zu realisieren, muss man die Java-Dateien anpassen. Hier wird das in den Zielen preprocess
-lehrende und preprocess-studierende definierte Präfix (//#) verwendet, welches durch den
Präprozessor ausgewertet, und der Code dementsprechend verändert wird.
Der Java Code könnte wie folgt aussehen:
66
5 Implementierung
//#if defined(vollversion)
private JButton jButtonSpeichern = new JButton();
jButtonSpeichern.addActionListener(new ActionListener(){
@Override
public void actionPerformed(ActionEvent e) {
speichereArbeitsblatt();
}
});
private JMenuItem jMenuSpeichern = new JMenuItem();
//#endif
.
.
.
//#if defined(vollversion)
jToolBarMain_.add(jButtonSpeichern, null);
jMenuFile.add(jMenuSpeichern);
//#endif
.
.
.
//#if defined(vollversion)
private void speichereArbeitsblatt(){
.
.
.
}
//#endif
Nachdem dies in den Java Code eingebaut wurde, wird beim Kompilieren der Studierendenversion beispielsweise die Methode speichereArbeitsblatt() nicht mitkompiliert und kann somit
nicht rekonstruiert oder durch direkten Eingriff in den Bytecode zugänglich gemacht werden.
Auf diese Weise können gezielt Teile ausgeblendet werden, welche in der Studierendenversion
nicht vorhanden sein sollen.
Graphische BenutzerInnenschnittstelle
Die Oberfläche des Rechners sollte einfach gehalten werden und den Regeln der Usability Rechnung ([HeLP97, John00, Dahm06]) tragen. Die graphischen Hauptelemente stellen Menüleiste,
Symbolleiste und ein Textfeld dar. Menü- und Symbolleiste werden folgend besprochen, das Textfeld dient nur der Ein- und Ausgabe von Daten und stellt darüber hinaus keinerlei Ansprüche.
Menüleiste: Die Menüleiste soll alle vorhandenen Funktionen des Rechners beinhalten und
sinnvoll gruppieren. Eine nahe liegende Anordnung wäre:
5.4 Konzept
67
• Datei
Neu: Erstellt ein neues Arbeitsblatt.
Öffnen: Öffnet ein bereits vorhandenes Arbeitsblatt.
Speichern: Speichert das aktuelle Arbeitsblatt.
Beenden: Beendet die Applikation.
• Operationen
Datentypen: Zeigt alle möglichen Datentypen an und fügt sie ein.
Funktionen: Zeigt alle vorhandenen Funktionen und fügt diese ein.
• Hilfe: Öffnet das Hilfefenster.
Das ’Beenden’ der Applikation erfordert eine Überprüfung des ’Speicherzustandes’ des
Arbeitsblatts. Bevor die Applikation durch BenutzerInneneingabe beendet wird, soll vom
Programm geprüft werden, ob das geöffnete Arbeitsblatt in der aktuell angezeigten Version
bereits gespeichert wurde. Sofern das nicht der Fall ist, sollen die BenutzerInnen gefragt
werden, ob vor dem Schließen der Applikation gespeichert werden soll. Wird dies bestätigt,
soll erst gespeichert und anschließend die Applikation geschlossen werden. Wird die Frage
abgelehnt – oder ist der aktuelle Stand des Arbeitsblatts bereits gespeichert worden – soll
die Applikation ohne weitere Aktion geschlossen werden.
Die Unterpunkte ’Datentypen’ und ’Funktionen’ sollen ein Drop-Down-Menü innerhalb
der Menüleiste öffnen. Wenn einer der gelisteten Punkte ausgewählt wird, dann soll die
entsprechende Datentyp-Deklaration bzw. der Funktionsaufruf direkt im Arbeitsblatt an
der aktuellen Cursor-Position eingefügt werden. Zusätzlich wäre es sinnvoll, für häufig angewandte Datentypen und Funktionsnamen geeignete Tastaturkombinationen zu erstellen.
Symbolleiste: Die Symbolleiste könnte folgende Funktionen anbieten:
•
Neu: Erstellt ein neues Arbeitsblatt.
•
Öffnen: Öffnet ein bestehendes Arbeitsblatt.
•
Speichern: Speichert das aktuelle Arbeitsblatt.
•
Berechnungsschritte Aktualisieren: Aktualisiert das gesamte Arbeitsblatt indem
es komplett neu berechnet wird.
•
Hilfe: Ruft einen Hilfedialog mit vorhandenen Hilfestellungen und einem BenutzerInnenhandbuch auf.
Variablendeklaration: Zur Deklaration von Variablen können die in 4.3 und 4.4 definierten
Datentypen verwendet werden: INTEGER, INTEGER(INTEGER), PRIME(INTEGER), BOOLEAN,
POLYNOM, GF, RECORD, ARRAY, STACK, FIFO.
Eingabe von Berechnungen: In Kapitel 4 wurde die Syntax der Sprache definiert. Die dafür
zur Verfügung stehenden Funktionen (GCD, PRIMETEST, ROOTOFUNITY, etc.) können dort
nachgeschlagen werden. Unter Aufruf dieser Funktionen können AnwenderInnen Berechnungen formulieren und ausführen. Hierfür soll eine einfache BenutzerInnenoberfläche zur
Verfügung stehen, welche die folgend beschriebenen Aktionen ermöglicht.
68
5 Implementierung
Nach dem Starten der Applikation wird ein leeres Arbeitsblatt angezeigt, in dem die BenutzerInnen die einzelnen Berechnungsschritte direkt eingeben können.
Das Ergebnis jedes einzelnen Rechenschrittes soll unmittelbar ausgegeben werden sobald
ein Zeilenumbruch eingegeben wird. Bei Rechenfolgen mit mehr als einem Rechenschritt
soll jede Zeile einzeln berechnet und das Ergebnis ausgegeben werden, wie in Abbildung 5.14 gezeigt wird. Für Variablendeklarationen soll keine Ausgabe erfolgen, sehr wohl
aber wenn eine Variable mit einem Wert initialisiert wird. Die Auswertung einer Variable liefert ihren Wert. Eine Schleife liefert keine Ausgaben, es wird aber bestätigt, wenn
die Schleife zu Ende gerechnet wurde. Anschließend kann man die einzelnen Variablen
ausgeben lassen.
Abb. 5.14: Konzept des Rechners für endliche Körper
Wenn bei längeren Rechenfolgen eine oder mehrere Variablenbelegungen geändert werden,
sollte nicht automatisch die gesamte Rechenfolge – das gesamte Arbeitsblatt – neu berechnet werden, sondern lediglich das Zwischenergebnis der veränderten Zeile aktualisiert
werden. Dasselbe gilt für Änderungen von Rechenschritten, welche die Werte der Variablen beeinflussen. BenutzerInnen können jederzeit manuell die gesamte Neuberechnungen
ausführen; hierfür soll ein eigenes Symbol in der Symbolleiste zur Verfügung stehen (“Berechnungsschritte aktualisieren”). Diese Aktion sollte auch über die Tastenkombination
Strg + Eingabetaste aufrufbar sein.
5.4 Konzept
69
Überlegungen zur Sprachauswertung
In Kapitel 4 wurde eine Programmiersprache welche Berechnungen in endlichen Körpern unterstützt vorgeschlagen. Für die Ausführung dieser Sprache wurden zwei mögliche Lösungsansätze untersucht: Compiler und Interpreter.
1. Compiler mit angepasster Assembler Sprache:
Im Rahmen der Übersetzung kann ein Assemblercode erstellt werden, welcher von (entsprechend konstruierten) Interpretern ausgeführt werden kann. Dafür müsste die Assembler
Sprache erweitert werden, da in der üblichen Sprache einige Datentypen nicht vorhanden
sind. So müssten beispielsweise Rechenoperationen eingefügt werden, die mit Polynomen
operieren können. Aus der vorhergehenden Sprachspezifikation ist bereits bekannt, dass
Ausdrücke oder Zuweisungen wie
f := [8xˆ2 + 5 + 2x];
g := [4xˆ2 − 5];
n := f ∗ gˆ(−1);
möglich sind. gˆ(−1) muss nicht unbedingt von der Ausführungsumgebung (Interpreter)
berechnet werden, es wäre auch denkbar, die Berechnung schon vor der Codegenerierung
auszuführen, so dass man ein Polynom g1 = g −1 berechnet und dann n = f · g1 zur
Codegenerierung verwendet.
Um ein paar Beispiele angeben zu können, wird die Assemblersprache für den MIPS Prozessor (Entwickelt von MIPS Technologies Inc.) verwendet. Auf diese Sprache wird nicht
näher eingegangen, da lediglich die Befehle zum Addieren veranschaulicht werden. Die
meisten Befehle dieser Sprache lesen und schreiben in Registern. Bei den Befehlen werden
Register mit einem Dollarzeichen ($) gekennzeichnet, ein Kommentar wird mit # eingeleitet. So führt die Anweisung add $a0, $a1, $a2 die Berechnung $a0 = $a1 + $a2 aus.
Einige Befehle erlauben auch konstante Eingabewerte (beispielsweise addi). Bei dieser
Sprache müsste beispielsweise der Befehl addi geändert werden, sodass als Übergabewert
nicht nur Konstanten sondern auch Polynome möglich sind. Alternativ könnte ein zusätzlicher Befehl eingefügt werden, der nur für Polynome verwendet wird. Auch der add Befehl
müsste erweitert werden, da die Register auch Polynome enthalten können.
Eine notwendige Erweiterung betrifft die Befehle für die Modulo-Operationen. So könnte
man Befehle addm und divm einführen, wobei $a1 und $a2 die Register mit den Operanden
sind und das Ergebnis in $a0 abgespeichert wird. Um das multiplikative Inverse zu berechnen, könnte der Befehl invm verwendet werden, welcher das Inverse von $a1 berechnet
und in $a0 abspeichert.
addm $a0,$a1,$a2,$modul # $a0 = $a1· $a2 MOD $modul
divm $a0,$a1,$a2,$modul # $a0 = $a1 / $a2 MOD $modul
invm $a0,$a1,$modul
2. Interpreter in der Programmiersprache Java
Wenn ein in Java programmierter Interpreter verwendet werden soll, wird die Eingabe
zeilenweise geparst. Der Interpreter führt dann den Code direkt in Java aus und liefert
70
5 Implementierung
das Ergebnis zurück. Dabei kann der Interpreter die bereits implementierte Bibliothek
verwenden. Dieser Ansatz vermeidet den Umweg über eine Assemblersprache; Interpreter
und Bibliothek sind in Java implementiert und können auf sehr einfache Weise miteinander kommunizieren. Die genauen Anforderungen an den Interpreter müssten im Zuge der
künftigen Weiterentwicklung im Detail untersucht werden.
Der Interpreter in Java scheint mit wesentlich geringerem Aufwand realisierbar zu sein als der
Ansatz in der Assemblersprache. Es bleibt jedoch den EntwicklerInnen möglicher Nachfolgeprojekte überlassen, sich für einen der dargelegten – oder auch andere – Ansätze zu entscheiden.
Zu entwickeln bleiben zum Einen der Übersetzer bzw. Interpreter der Sprache, zum Andern
eine intuitive BenutzerInnenschnittstelle. Für beide Komponenten wurden diverse Anforderungen beschrieben, welche bislang identifiziert werden konnten. Es wird jedoch kein Anspruch auf
Vollständigkeit erhoben, die Anforderungen an die Applikation können sich im Zuge der Weiterentwicklung ändern; natürlich können auch neue gewünschte Funktionalitäten hinzukommen.
Als weitere Hilfestellung könnte eine Autocomplete-Funktion für die Eingabe von Funktionsnamen (für Funktionen wie beispielsweise MULTINVERS, ROOTOFUNITY oder EULERPHI) gebaut werden.
Dabei muss man die Eingabe direkt nach jedem Tastenanschlag parsen. Da die Sprache einfach
gehalten ist, kann man leicht entscheiden, welcher Datentyp oder welche Funktion aktuell gerade eingegeben wird. Passende Treffern können dann vorgeschlagen werden und NutzerInnen
sollten in der Lage sein, mittels einer Tastenkombination (beispielsweise Strg + Leertaste) die
Funktion oder den Datentyp zu vervollständigen. Weiters könnte man auf diese Weise auch die
verschiedenen Kontrollstrukturen (IF, FOR, WHILE) vervollständigen. AnwenderInnen müssen
dann nur mehr die Variablen in den Kontrollstrukturen richtig setzen. Dies verringert sowohl den
Schreibaufwand als auch die Fehlerwahrscheinlichkeit durch Syntaxfehler. Sofern sie bereits deklariert wurden (und somit in der Hash-Tabelle vorhanden sind), könnten auch Variablennamen
vervollständigt werden.
5.5 Der Jigloo GUI Editor in Eclipse
Die Oberfläche des vorgestellten Stackrechners, welcher der Demonstration der implementierten
Bibliothek diente, wurde in Java-Swing geschrieben. Ebenso die Testumgebung für die ersten
Tests im Zuge der Entwicklung. Um die Programmierung der Swingklassen zu vereinfachen,
wurde ein GUI Editor verwendet. Sollten die Quelldateien weiter verwendet werden, macht
es Sinn, denselben Editor zu verwenden. In diesem Fall handelt es sich um den Jigloo Editor
von CloudGarden in der Version 4.6.2 [Clo10]. Der Stackrechner wurde mittels Eclipse [Ecl10]
in der Version 3.4.2 implementiert. Durch den Updatemanager von Eclipse kann der Editor
einfach installiert und aktualisiert werden. Im Editor ist es möglich, mittels Drag and Drop die
gewünschten Elemente zu platzieren. Es wurde das GroupLayout verwendet, welches in Java
seit der Version 1.6 [SUN10b] zur Verfügung steht. Im Editor sind aber alle anderen Layouts
ebenso verfügbar und können einfach verändert werden.
6 Anwendungsbeispiele
In diesem Kapitel sollen die besprochenen Konzepte angewandt werden. Sowohl am implementierten Stackrechner als auch in der in Kapitel 4 vorgestellten Sprache werden verschieden Verfahren und Protokolle in Form von konkreten Beispielen durchgerechnet. Dieses Kapitel soll
somit einerseits einen Einblick in die Benutzbarkeit des Stackrechners geben und andererseits
einen Ausblick verschaffen, wie die Berechnungen im fertigen Rechner (der finalen Applikation)
durchgeführt werden könnten. Die hier berechneten Verfahren und Protokolle sind aus Kapitel 1
bekannt.
6.1 RSA-Verfahren
Aus Abbildung 5.14 ist ein Konzept der graphischen BenutzerInnenschnittstelle des zu implementierenden Rechners bekannt. In dieser Abbildung ist auch ersichtlich, wie eine Berechnung
der Schlüssel auf Seite von Alice realisiert werden könnte. Nun soll das ganze Verfahren im
Stackrechner durchgerechnet werden.
Da im Stackrechner kein Zufallszahlengenerator (oder Pseudozufallszahlengenerator) integriert
wurde, müssen die Zufallszahlen (bzw. zufällige Primzahlen) per Hand eingegeben werden. Die
Darstellung der Berechnungsschritte im Stackrechner ist in Abbildung 6.1 ersichtlich; im Detail
sehen diese wie folgt aus (die mittels # gekennzeichnete Kommentare wurden manuell eingetragen):
#Alice berechnet n
1) 991 ist prim!
#p = 991
2) 863 ist prim!
#q = 863
3) 863 * 991 = 855233
#n = p * q = 855233
#Berechnen von phi(n)
4) 862 * 990 = 853380
#phi(n) = (p - 1) * (q - 1) = 853380
#Annahme von e = 709 und berechnen des ggT
5) 709 ist prim!
6) ggT(709, 853380) = 1
#ggT(e, phi(n)) = 1 -> e = 709 ist
geeignet
#Berechnen von d
7) 709^-1 = 810049 (mod 853380)
#d = e^-1 MOD phi(n) = 810049
#Bob verschlüsselt die Nachricht
#m = 718862
8) 718862^709 MOD 855233 = 455983
#c = m^e MOD n = 455983
#Alice entschlüsselt den verschlüsselten Text c
9) 455983^810049 MOD 855233 = 718862
#m = c^d MOD n = 718862
72
6 Anwendungsbeispiele
Abb. 6.1: Berechnungsschritte beim RSA-Verfahren im Stackrechner
Die Primzahlen wurden in der Testumgebung (bekannt aus Abbildung 5.4) ermittelt und in den
Stackrechner manuell eingegeben. Alle anderen Berechnungsschritte erfolgten ausschließlich im
implementierten Stackrechner.
Durchführbarkeit in der spezifizierten Sprache
Die Durchführung in der spezifizierten Sprache wird im Folgenden dargestellt. Anfangs werden
zwei Primzahlen und ein Pseudozufallszahlengenerator deklariert:
p:PRIME(10);
q:PRIME(10);
generator:PSEUDORANDOM(2^10, 2^15, 50);
Dieser Pseudozufallszahlengenerator liefert Zahlen von 210 bis zum Wert 215 und wird mit dem
Initialwert 50 erzeugt. Bei einer Zuweisung mit dem generator wird eine Zufallszahl erzeugt
und zurückgeliefert. Nachdem die Primzahlen und der Generator deklariert wurden, werden die
Primzahlen durch den Pseudozufallszahlengenerator initialisiert:
p := generator;
q := generator;
Bei dieser Zuweisung wird eine Zufallszahl erzeugt. Da die Variablen p und q vom Typ PRIME
sind, muss diese Zufallszahl auch eine Primzahl sein, so muss die Laufzeitumgebung bei der
Zuweisung testen ob der Generator ein Primzahl geliefert hat. Sollte dies nicht der Fall sein,
wird eine neue Zufallszahl erzeugt. Die öffentlich bekannte Zahl n wird deklariert und aus den
zuvor bestimmten Primzahlen p und q errechnet:
n:INTEGER;
n := p * q;
ϕ(n) wird als INTEGER deklariert und berechnet:
phi:INTEGER;
phi := (p - 1) * (q - 1);
Der öffentliche Schlüssel e wird deklariert und mittels des Pseudozufallszahlengenerators initialisiert. Außerdem wird getestet, ob der größte gemeinsame Teiler von e und ϕ(n) gleich 1 ist,
6.2 Diffie-Hellman-Protokoll
73
dies wird in einer Schleife realisiert. Sollte der ggT nicht 1 sein, wird erneut eine Zufallszahl
erzeugt und zugewiesen:
e:INTEGER;
e := generator;
WHILE(GGT(e, phi) != 1){
e := generator;
}
Nachdem der öffentliche Schlüssel e gefunden wurde, kann der geheime Schlüssel d deklariert
und berechnet (d ≡ e−1 (mod ϕ(n))) werden.
d:INTEGER;
d := MULTINVERS(e, phi);
Die nachricht kann mithilfe folgender Befehlsfolgen ver- bzw. entschlüsselt werden.
Verschlüsselung:
nachricht:INTEGER(n);
nachricht := 718862;
chiffre:INTEGER;
chiffre := EXP(nachricht, e, n);
Entschlüsselung:
nachricht2:INTEGER(n);
nachricht2 := EXP(chiffre, d, n);
6.2 Diffie-Hellman-Protokoll
Zur Initialisierung des Protokolls berechnet Alice die Primitivwurzel α. Hierfür benötigt sie ein
paar Berechnungen (aus Abschnitt 3.5 bekannt), welche im Stackrechner wie folgt durchgeführt
werden können:
#Berechnung von alpha
#Alice wählt p = 991, q = 11 und h = 709 und berechnet
1) 11^-1 = 901 (mod 991)
#q^-1 MOD p = 901
2) 990 * 901 MOD 991 = 90
#(p - 1) / q = (p - 1) * q^-1 = 90
3) 709^90 MOD 991 = 773
#alpha = h^((p - 1) / q) MOD p = 773
Schlüsselaustausch
Um den Schlüssel zwischen Alice und Bob auszutauschen, werden im Stackrechner folgende
Operationen ausgeführt:
74
6 Anwendungsbeispiele
#Alice wählt den geheimen Wert x = 9
4) 773^9 MOD 991 = 518
#A = alpha^x MOD p = 518
#Alice schickt p, q, alpha und A zu Bob
#Bob wählt den geheimen Wert y = 7 und berechnet
5) 773^7 MOD 991 = 754
#B = alpha^y MOD p = 754
#Bob schickt B an Alice und berechnet den geheimen Schlüssel
6) 518^7 MOD 991 = 134
#Ka = A^y MOD p = 134
#Alice berechnet den geheimen Schlüssel
7) 754^9 MOD 991 = 134
#Kb = B^x MOD p = 134
Nach diesen Schritten wurde der Schlüssel Ka für Alice und der Schlüssel Kb für Bob berechnet.
Diese entsprechen dem ausgetauschten Sitzungsschlüssel.
Beispiel in der spezifizierten Sprache
Nun soll wieder die spezifizierte Sprache verwendet werden, um dieses Protokoll zu realisieren.
Dazu wählen Alice und Bob anfangs die zwei zufälligen Primzahlen, und die öffentlich bekannte
Primitivwurzel wird berechnet. Der hier verwendete Wert h wird nur zur Bestimmung der q-ten
primitiven Einheitswurzel benötigt.
Zur Initialisierung des Systems wird ein Pseudozufallszahlengenerator erzeugt und die Primzahlen p und q werden bestimmt:
p:PRIME(10);
q:PRIME(10);
generator:PSEUDORANDOM(2^10, 2^15, 50);
p := generator;
q := generator;
WHILE(GGT(q,(p - 1)) == 1){
q := generator;
}
Um eine Einheitswurzel zu bestimmen, wird der Algorithmus aus Abschnitt 3.5 verwendet.
Durch die WHILE-Schleife wird gewährleistet, dass q|(p − 1) gilt. Nun wird zur Bestimmung der
q-ten primitiven Einheitswurzel α ein h zufällig aus [1 : p − 1] generiert und daraus α berechnet:
alpha:INTEGER(p);
alpha := ROOTOFUNITY(q, p);
Der Ablauf könnte wie folgt durchgeführt werden:
Alice und Bob wählen zwei Zufallszahlen x und y aus der Untergruppe G:
x:INTEGER(q);
y:INTEGER(q);
x := generator;
y := generator;
6.3 ElGamal-Verfahren
75
Alice berechnet a = αx MOD p und schickt a zu Bob:
a:INTEGER(p);
a := EXP(alpha, x);
Bob berechnet b = αy MOD p und schickt b zu Alice:
b:INTEGER(p);
b := EXP(alpha, y);
Alice berechnet den Schlüssel KAB = Ka = bx MOD p:
Ka:INTEGER(p);
Ka := EXP(b, x);
Bob berechnet den Schlüssel KAB = Kb = ay MOD p:
Kb:INTEGER(p);
Kb := EXP(a, x);
Es muss natürlich Ka = Kb gelten. Im Rechner darf aber eine Variable nur einmal deklariert
werden. So wurden zwei Variablen angelegt, um auf Alices und Bobs Seite die Berechnung
durchführen zu können.
6.3 ElGamal-Verfahren
Im ElGamal-Verfahren (siehe Abschnitt 1.4) wird eine Primitivwurzel benötigt. Der Stackrechner stellt das Auffinden von Primitivwurzeln nicht zur Verfügung, deshalb muss diese ’manuell’
(wie aus Abschnitt 3.5 bekannt) ermittelt werden. Um beispielsweise eine 5-te primitive Einheitswurzel in Z991 zu berechnen, geht Alice wie folgt vor:
#Alice berechnet:
1) 5^-1 = 793 (mod 991)
2) 793 * 990 MOD 991 = 198
#Berechnen von alpha -> 5-te
3) 709^198 MOD 991 = 799
#Berechnet q^-1 MOD p = 793
#Berechnet (p - 1) * q^-1 MOD p = 198
primitive Einheitswurzel in Z991 (h = 709)
#alpha = h^((p - 1) / q) MOD p = 799
Nachdem die Einheitswurzel berechnet wurde, wählt Alice ihren geheimen Exponenten und
berechnet den öffentlichen Schlüssel:
#Alice wählt den geheimen Schlüssel
#xA = 739
#und berechnet den öffentlichen Schlüssel
4) 799^739 MOD 991 = 160
#yA = alpha^xA MOD p = 160
76
6 Anwendungsbeispiele
Ver- und Entschlüsselung
Um eine Nachricht (beispielsweise m = 123) zu verschlüsseln und das Chiffrat (c1 , c2 ) zu bestimmen, geht Bob folgendermaßen vor:
#Bob wählt k = 863 und berechnet K:
5) 160^863 MOD 991 = 197
6) 799^863 MOD 991 = 825
#Bob wählt m = 123
7) 197 * 123 MOD 991 = 447
#k = 863
#K = yA^k MOD p = 197
#c1 = alpha^k MOD p = 825
#c2 = K * m = 447
Zur Entschlüsselung wird das Paar (c1 , c2 ) an Alice geschickt, welche nun die Nachricht m folgend
ermitteln kann:
#Alice berechnet:
8) 825^739 MOD 991 = 197
9) 197^-1 = 825 (mod 991)
10) 825 * 447 MOD 991 = 123
#D1(c1) = c1^xA MOD p = 197
#D1(c1)^-1 MOD p = 825
#m = D1(c1)^-1 * c2 MOD p= 123
Signatur
Um eine Nachricht zu signieren wird wie folgt vorgegangen:
# S I G N I E R E N
#Alice wählt ein k und berechnet r und s #k = 857
12) 799^857 MOD 991 = 197
#r = alpha^k MOD p = 197
13) 857^-1 = 923 (mod 990)
#k^-1 MOD (p - 1) = 923
14) 197 * 739 MOD 990 = 53
#r * xA MOD (p - 1) = 53
15) 123 - 53 = 70
#(m - xA * r) MOD (p - 1) = 70
16) 923 * 70 MOD 990 = 260
#s = k^-1 * (m - xA * r) MOD (p - 1) = 260
#Bob verifiziert:
17) 799^123 MOD 991 = 825
#alpha^m MOD p = 825
#yA^r * r^s (mod p) wird folgend berechnet:
18) 197^260 MOD 991 = 1
#r^s MOD p = 1
19) 160^197 MOD 991 = 825
#yA^r MOD p = 825
20) 1 * 825 MOD 991 = 825
#yA^r * r^s MOD p = 825
r · r s MOD p und verifiziert somit die digitale Signatur.
Bob vergleicht αm MOD p mit yA
Beispiel in der spezifizierten Sprache
Es soll wieder gezeigt werden, wie dieses Verfahren in der definierten Sprache realisiert werden
könnte. Die Initialisierung des Systems durch Alice könnte wie folgt aussehen:
6.3 ElGamal-Verfahren
77
p:PRIME(10);
q:PRIME(10);
generator:PSEUDORANDOM(2^10, 2^15, 50);
p := generator;
q := generator;
alpha:INTEGER;
alpha := ROOTOFUNITY(q, p);
xA:INTEGER(p - 2);
yA:INTEGER(p);
xA := generator;
yA := EXP(alpha, xA);
Hier wurden p und q als Primzahlen deklariert und zufällig durch den Pseudozufallszahlengenerator initialisiert. Der Zufallswert h liegt in [1 : p − 1], dafür wird die WHILE-Schleife
verwendet. Wenn kein Pseudozufallszahlengenerator verwendet wird, könnte h auch mittels
h := RANDOM(1:p - 1); (bzw. mittels h := RANDOM;, da der Wertebereich von h endlich ist)
initialisiert werden. Der Nachteil, wenn RANDOM verwendet wird ist, dass die Berechnung nicht
mehr wiederholt werden kann, da RANDOM immer unterschiedliche Zahlen liefert und keinen Initialwert besitzt. Nachdem die q-te primitive Einheitswurzel α bestimmt wurde, kann xA zufällig
bestimmt und yA = αxA MOD p berechnet werden.
Die Verschlüsselung wird von Bob wie folgt durchgeführt:
k:INTEGER(p - 1);
K:INTEGER(p);
k := RANDOM;
K := EXP(yA, k);
c1:INTEGER(p);
c1 := EXP(alpha, k);
m:INTEGER(p);
c2:INTEGER(p);
m := 123;
c2 := K * m;
k MOD p. Anschließend kann das
Bob wählt ein zufälliges k ∈ [0 : p − 2] und berechnet K = yA
Paar(c1 , c2 ) – nach der Bestimmung der Nachricht m – durch die Berechnungen c1 = αk MOD p
und c2 = K · m MOD p bestimmt werden.
Alice kann das Chiffrat (c1 , c2 ) folgendermassen entschlüsseln:
d1:INTEGER(p);
d1 := EXP(c1, xA);
d1Invers:INTEGER(p);
d1Invers := MULTINVERS(d1, p);
m:INTEGER(p);
m := d1Invers * c2;
78
6 Anwendungsbeispiele
Um die Nachricht m auf der Seite von Alice zu entschlüsseln, wird zuerst d1 = cx1 A MOD p berechnet und das multiplikative Inverse d−1
1 MOD p ermittelt. Die Nachricht m kann anschließend
−1
mittels m = d1 · c2 MOD p rekonstruiert werden.
6.4 Shamir’s Secret Sharing
Wie aus Kapitel 1 bekannt, spricht man von Geheimnisteilung (Secret Sharing), wenn Daten
(beispielsweise ein Schlüssel für das RSA-Verfahren) verteilt und auf mehrere SpielerInnen gespeichert werden. Um diese Daten wieder herzustellen, wird nur eine Teilmenge der SpielerInnen
benötigt. Die Mindestgröße dieser Teilmenge muss vorab festgelegt werden.
Für die Berechnung der Shares im Stackrechner wurde q = 991, t = 3, r1 = 701, r2 = 699, r3 = 21
und das Geheimnis s = 123 angenommen und das Polynom g(x) = 123 + 701 · x + 699 · x2 + 21 · x3
erzeugt. Anschließend wurden n = 5 Shares im Stackrechner ermittelt:
#Berechnen von g(1) = 123 + 701 * 1 + 699 * 1^2
1) 21 + 699 = 720
2) 720 + 701 = 1421
3) 1421 + 123 = 1544
4) 1544 MOD 991 = 553
#g(1) = 553 = s1
#Berechnen von g(2) = 123 + 701 * 2 + 699 * 2^2
5) 2^3 MOD 991 = 8
6) 21 * 8 MOD 991 = 168
7) 2^2 MOD 991 = 4
8) 699 * 4 MOD 991 = 814
9) 814 + 168 = 982
10) 982 MOD 991 = 982
11) 701 * 2 MOD 991 = 411
12) 411 + 982 = 1393
13) 1393 MOD 991 = 402
14) 123 + 402 = 525
#g(2) = 525 = s2
#Berechnen von g(3) = 123 + 701 * 3 + 699 * 3^2
15) 3^3 MOD 991 = 27
16) 21 * 27 MOD 991 = 567
17) 3^2 MOD 991 = 9
18) 699 * 9 MOD 991 = 345
19) 345 + 567 = 912
20) 912 MOD 991 = 912
21) 701 * 3 MOD 991 = 121
22) 121 + 912 = 1033
23) 1033 MOD 991 = 42
24) 123 + 42 = 165
#g(3) = 165 = s3
#Berechnen von g(4) = 123 + 701 * 4 + 699 * 4^2
25) 4^3 MOD 991 = 64
26) 21 * 64 MOD 991 = 353
27) 4^2 MOD 991 = 16
+ 21 * 1^3 MOD 991
+ 21 * 2^3 MOD 991
+ 21 * 3^3 MOD 991
+ 21 * 4^3 MOD 991
6.4 Shamir’s Secret Sharing
79
28) 699 * 16 MOD 991 = 283
29) 283 + 353 = 636
30) 701 * 4 MOD 991 = 822
31) 822 + 636 = 1458
32) 1458 MOD 991 = 467
33) 123 + 467 = 590
34) 590 MOD 991 = 590
#g(4) = 590 = s4
#Berechnen von g(5) = 123 + 701 * 5 + 699 * 5^2 + 21 * 5^3 MOD 991
35) 5^3 MOD 991 = 125
36) 21 * 125 MOD 991 = 643
37) 5^2 MOD 991 = 25
38) 699 * 25 MOD 991 = 628
39) 628 + 643 = 1271
40) 1271 MOD 991 = 280
41) 701 * 5 MOD 991 = 532
42) 532 + 280 = 812
43) 123 + 812 = 935
44) 935 MOD 991 = 935
#g(5) = 935 = s5
Diese fünf Shares (g(1), g(2), g(3), g(4) und g(5)) können nun – vertraulich – an verschiedene Personen verteilt werden. Um das Geheimnis s wieder zu reproduzieren, benötigt man mindestens
m ≥ t + 1 Shares. In unserem Beispiel werden m = 4 Shares verwendet.
Wie aus Kapitel 1 bekannt, lautet die Rekonstruktionsformel:
g(x) ≡
m
X
si
i=1
m
Y
(x − j) · (i − j)−1 (mod q)
j=1,j6=i
Das Geheimnis s lässt sich an der Stelle g(0) berechnen:
g(0) ≡
m
X
i=1
si
m
Y
j · (j − i)−1 (mod q)
j=1,j6=i
Zur Vereinfachung der Formel wird die Gewichtung wi von si bestimmt:
wi ≡
m
Y
j · (j − i)−1 (mod q)
j=1,j6=i
Das Geheimnis soll mit vier Shares rekonstruiert werden. Dafür sollen die Shares s1 , s3 , s4 und
s5 benutzt werden. Die Formeln zur Berechnung der Gewichtung der einzelnen Shares sehen wie
folgt aus:
w1 ≡ 3 · (3 − 1)−1 · 4 · (4 − 1)−1 · 5 · (5 − 1)−1 (mod 991)
w3 ≡ 1 · (1 − 3)−1 · 4 · (4 − 3)−1 · 5 · (5 − 3)−1 (mod 991)
w4 ≡ 1 · (1 − 4)−1 · 3 · (3 − 4)−1 · 5 · (5 − 4)−1 (mod 991)
w5 ≡ 1 · (1 − 5)−1 · 3 · (3 − 5)−1 · 4 · (4 − 5)−1 (mod 991)
Die Berechnungen werden im Stackrechner durchgeführt:
80
6 Anwendungsbeispiele
#Gewichtung w1 = 3 * (3 - 1)^-1 * 4 * (4
45) 5 - 1 = 4
46) 4^-1 = 248 (mod 991)
47) 5 * 248 MOD 991 = 249
48) 4 - 1 = 3
49) 3^-1 = 661 (mod 991)
50) 4 * 661 MOD 991 = 662
51) 662 * 249 MOD 991 = 332
52) 3 - 1 = 2
53) 2^-1 = 496 (mod 991)
54) 3 * 496 MOD 991 = 497
55) 497 * 332 MOD 991 = 498 #w1 = 498
#Berechnen von w3 = 1 * (1 - 3)^-1 * 4 *
56) 2^-1 = 496 (mod 991)
57) 5 * 496 MOD 991 = 498
58) 4 * 498 MOD 991 = 10
59) 1 - 3 = (-2)
60) (-2)^-1 = 495 (mod 991)
61) 495 * 10 MOD 991 = 986 #w3 = 986
#Berechnen von w4 = 1 * (1 - 4)^-1 * 3 *
62) 3 - 4 = (-1)
63) (-1)^-1 = 990 (mod 991)
64) 3 * 990 MOD 991 = 988
65) 988 * 5 MOD 991 = 976
66) 1 - 4 = (-3)
67) (-3)^-1 = 330 (mod 991)
68) 976 * 330 MOD 991 = 5
#w4 = 5
#Berechnen von w5 = 1 * (1 - 5)^-1 * 3 *
69) 4 - 5 = (-1)
70) (-1)^991 MOD 991 = 990
71) 4 * 990 MOD 991 = 987
72) 3 - 5 = (-2)
73) (-2)^-1 = 495 (mod 991)
74) 3 * 495 MOD 991 = 494
75) 987 * 494 MOD 991 = 6
76) 1 - 5 = (-4)
77) (-4)^-1 = 743 (mod 991)
78) 6 * 743 MOD 991 = 494
#w5 = 494
- 1)^-1 * 5 * (5 - 1)^-1 MOD 991
(4 - 3)^-1 * 5 * (5 - 3)^-1 MOD 991
(3 - 4)^-1 * 5 * (5 - 4)^-1 MOD 991
(3 - 5)^-1 * 4 * (4 - 5)^-1 MOD 991
Aus der Summenformel
s≡
m
X
i=1
si · wi (mod q)
6.4 Shamir’s Secret Sharing
81
ergibt sich zur Rekonstruktion des Geheimnisses folgende Gleichung:
s ≡ 553 · 498 + 165 · 986 + 590 · 5 + 935 · 494
Dies wird im Stackrechner berechnet:
#Geheimnisses s = 553
79) 498 * 553 MOD 991
80) 986 * 165 MOD 991
81) 166 + 887 = 1053
82) 1053 MOD 991 = 62
83) 5 * 590 MOD 991 =
84) 968 + 62 = 1030
85) 1030 MOD 991 = 39
86) 494 * 935 MOD 991
87) 84 + 39 = 123
* 498 + 165 * 986 + 590 * 5 + 935 * 494
= 887
= 166
968
= 84
#Geheimnis wurde korrekt rekonstruiert
Beispiel in der spezifizierten Sprache
In der Sprache müssen nicht alle Variablen von Hand berechnet werden, da Schleifen zur
Verfügung stehen. Die Initialisierung des Systems dieht wie folgt aus: t = 3 und das Geheimnis
s = 123 wird angenommen, alle anderen Variablen werden zufällig bestimmt.
q:PRIME(10);
generator:PSEUDORANDOM(2^10, 2^15, 50);
q := generator;
s:INTEGER(q);
t:INTEGER;
t := 3;
s := 123;
r:ARRAY[t] OF INTEGER;
FOR counter := 0 TO t-1{
r[counter] := generator MOD q;
}
Das Polynom hat die Form g(x) = 123 + r1 · x + r2 · x2 + r3 · x3 und kann in einer Schleife
bestimmt werden. Es werden fünf Shares erzeugt, welche in einem Array abgelegt werden:
g:ARRAY[5] OF INTEGER;
FOR counter := 1 TO 5{
tmp:INTEGER(q);
tmp := 1;
FOR counter2 := 1 TO t{
tmp := tmp + ((r[counter2 - 1] * EXP(counter, counter2, q)) MOD q);
}
g[counter - 1] := (s + tmp) MOD q;
}
82
6 Anwendungsbeispiele
Somit wurden die Shares erzeugt, nun kann die Rekonstruktion berechnet werden. Dafür wird
angenommen, dass – wie im Beispiel mittels Stackrechner berechnet – vier Shares (mit den
Indizes 1, 3, 4 und 5) verwendet werden. Auch hier kann man Schleifen verwenden, um die Rekonstruktionsformel anzuwenden:
sneu:INTEGER(q);
FOR i := 1 TO anzahl{
IF((i == 1) OR (i == 3) OR (i == 4) OR (i == 5)){
prod:INTEGER(q);
prod := 1;
FOR j := 1 TO anzahl{
IF(i != j){
prod := prod * j * MULTINVERS((j - i), q);
}
}
sneu := g[i - 1] + prod;
}
}
Hier wird nicht die Gewichtung berechnet, sondern direkt das Polynom im Ursprung ausgewertet,
also g(0) berechnet. Gerade bei diesem Beispiel ist gut ersichtlich, wie effizient die Verwendung
der spezifizierten Sprache ist: Möchte man im fertigen Rechner dieses Verfahren realisieren, muss
es nur einmal eingegeben werden. Durch Austauschen der Variablenwerte können anschließend
verschiedene Variationen durchgerechnet werden.
7 Zusammenfassung und Ausblick
Kryptographische Verfahren und Protokolle finden gegenwärtig in verschiedenen Bereichen des
täglichen Lebens Anwendung. Wann immer zwei Instanzen auf (abhör-)sicherem Wege miteinander kommunizieren wollen, muss die auszutauschende Information verschlüsselt werden.
Kommunikation auf gesichertem Wege erfolgt beispielsweise mithilfe des HTTPS-Protokolls (für
Online-Banking, Buchungsvorgänge, etc.). Die Systemsicherheit – als Teilbereich der Informatik – besitzt somit einen großen Stellenwert, der sich auch in den Lehrplänen der InformatikStudiengänge niederschlägt. Ziel der vorliegenden Arbeit war es, die Voraussetzungen für eine
Applikation zu schaffen, die in der Lehre eingesetzt werden kann. Diese Applikation soll es Studierenden erleichtern, ein Verständnis für kryptographische Verfahren und Protokolle zu entwickeln.
Diese Arbeit bietet einen Überblick über aktuelle kryptographische Berechnungen. Zu Beginn
der Arbeit wurden einige ausgewählte gegenwärtig für kryptographische Zwecke verwendete Verfahren und Protokolle wie RSA-Verfahren, ElGamal-Verfahren, Diffie-Hellman Protokoll und
Shamir’s Secret Sharing vorgestellt. Im nächsten Schritt wurden die Algorithmen, welche für die
Realisierung dieser Verfahren und Protokolle benötigt werden, im Detail besprochen. Beispielsweise seien hier Primzahltest, effizientes Potenzieren oder das Auffinden der q-ten primitiven
Einheitswurzel genannt. Im Rahmen der Implementierung eines Stackrechners für Demonstrationszwecke wurden relevante Algorithmen anschließend implementiert und in einer Bibliothek
gesammelt für weitere Entwicklungen zur Verfügung gestellt. Alle behandelten Berechnungen
können in endlichen Körpern (aber auch in Z) durchgeführt werden. Zu Demonstrationszwecken
wurde weiters ein Stackrechner realisiert, der auf die bereitgestellten Methoden der Bibliothek
zugreift. Einzelne Berechnungsbeispiele belegen die erfolgreiche und korrekte Verwendung der
implementierten Methoden.
Nachdem die Voraussetzungen für die Implementierung der gewünschten Applikation geschaffen wurden, wurde ein Konzept erarbeitet, welches die Anforderungen an diese Applikation
beschreibt. Es umfasst benötigte Funktionalitäten wie Speicherarten (welche Daten notwendigerweise in einem gespeicherten Worksheet abgelegt werden sollen), die Erstellung von verschiendenen Versionen (Studierenden- und Lehrenden-Version) mit Hilfe eines Preprocessors und ein
mögliches Erscheinungsbild sowie die Interaktion mit der graphischen BenutzerInnenoberfläche.
Zum Zeitpunkt der Fertigstellung der vorliegenden Arbeit war die Weiterführung dieses Projektes geplant. Diese Weiterführung soll den letzten Schritt dieses Projekts bewerkstelligen und
das bereitgestellte Konzept in die tatsächliche Applikation überführen. Hierfür steht neben der
implementierten Bibliothek auch eine Sprachspezifikation zur Verfügung, die für die Implementierung genutzt werden kann.
A Syntax Überblick
Hier wird ein Überblick über die gesamte Syntax der Sprache gegeben. Dieser beinhaltet auch
Konzepte, die in Kapitel 4 nicht vollständig definiert wurden.
buchstabe
→
’A’ | ’B’ | ’C’ | ’D’ | ’E’ | ’F’ | ’G’ | ’H’ | ’I’ | ’J’ | ’K’
| ’L’ | ’M’ | ’N’ | ’O’ | ’P’ | ’Q’ | ’R’ | ’S’ | ’T’ | ’U’ | ’V’
| ’W’ | ’X’ | ’Y’ | ’Z’ | ’a’ | ’b’ | ’c’ | ’d’ | ’e’ | ’f’ | ’g’
| ’h’ | ’i’ | ’j’ | ’k’ | ’l’ | ’m’ | ’n’ | ’o’ | ’p’ | ’q’ | ’r’
| ’s’ | ’t’ | ’u’ | ’v’ | ’w’ | ’x’ | ’y’ | ’z’.
ziffer
→
’0’ | ’1’ | ’2’ | ’3’ | ’4’ | ’5’ | ’6’ | ’7’ | ’8’ | ’9’.
nummer
→
ziffer { ziffer } .
id
→
buchstabe { buchstabe | ziffer } .
addOP
→
’+’ | ’-’.
mulOP
→
’*’ | ’/’ | ’MOD’ | ’^’.
relOP
→
’<’ | ’>’ | ’<=’ | ’>=’.
equOP
→
’==’ | ’!=’.
literal
→
’TRUE’ | ’FALSE’ | nummer | random | polynom .
selektor
→
(’[’ expr ’]’ | ’.’ id ) [ selektor ] .
primaryExpr
→
literal | id [ selektor ] [ ’.GET’ | ’.POP’ ]
| ’(’ expr ’)’ | funktion .
unaryExpr
→
[ addOP ] primaryExpr .
mulExpr
→
unaryExpr { mulOP unaryExpr } .
addExpr
→
mulExpr { addOP mulExpr } .
relExpr
→
[ ’NOT’ ] addExpr [ relOP addExpr ] .
equalExpr
→
relExpr [ equOP relExpr ] .
condAndExpr
→
equalExpr {’AND’ equalExpr } .
expr
→
condAndExpr {’OR’ condAndExpr } .
zuweisung
→
id [ selektor ] ’:=’ expr .
bedingung
→
’(’ expr ’)’.
86
A
deklaration
→
funktion
→
Syntax Überblick
id ’:’ typ .
( ’GCD’ | ’EXTGCD’ | ’PRIMITIVEELEMENT’ | ’ROOTOFUNITY’
| ’MULTINVERS’ | ’PRIMETEST’) ’(’ expr ’,’ expr ’)’
| ’EULERPHI’ ’(’ expr ’)’
| ’EXP’ ’(’ expr ’,’ expr [ ’,’ expr ] ’)’.
block
→
’{’ statementList ’}’.
if
→
’IF’ bedingung block [ ’ELSE’ ( if | block ) ] .
while
→
’WHILE’ bedingung block .
for
→
’FOR’ id ’:=’ expr (’TO’ | ’DOWNTO’) expr block .
statementList
→
{ statement }+ .
deklList
→
{ deklaration ’;’}+ .
statement
→
( zuweisung | addStackFifo ) ’;’ | if | while | for .
addStackFifo
→
id ’.’ (’PUSH’ | ’ADD’) ’(’ expr ’)’.
primTyp
→
’INTEGER’ [ ’(’ expr ’)’ ] | ’BOOLEAN’
| ’PRIME’ ’(’ expr ’)’.
kompTyp
→
’POLYNOM’ | gf | record | array | ’STACK’ | ’FIFO’
| randGenerator .
typ
→
primTyp | kompTyp .
polynom
→
term
→
random
→
’RANDOM’ [ ’(’ expr [ ’:’ expr ] ’)’ ] .
gf
→
’GF’ ’(’ expr ’,’ polynom ’)’.
record
→
’RECORD’ deklList ’END’.
array
→
’ARRAY’ selektor ’OF’ ( primTyp | kompTyp ) .
randGenerator
→
’PSEUDORANDOM’ ’(’ expr ’,’ expr ’,’ expr ’)’.
’[’ term { addOP term } ’]’.
[ nummer [ ’*’ ] ] ’x’ [ ’^’ expr ]
| nummer
B GUI-Editor und Piktogramme
In diesem Abschnitt werden die Verwendung des GUI-Editors Jigloo unter Eclipse und die damit
verbundenen Schwierigkeiten besprochen. Anschließend wird das Tango Desktop Project kurz
vorgestellt, dessen bereitgestellte Ressourcen in der Applikation eingesetzt werden.
Jigloo unter Eclipse
In Eclipse können Dateien für Jigloo auf direktem Weg neu erstellt werden. Diese beinhalten
eine main-Methode um diese Datei in Java zu starten und die erzeugte Oberfläche anzusehen.
Um Jigloo Dateien zu erstellen, geht man auf “File i Neu i Other...” oder alternativ kann
eine Tastaturkombination (Strg + N) benutzt werden. Nun kann man unter “GUI Forms i
Swing” verschiedene Swing Klassen auswählen und diese – nachdem man der Klasse einen Namen
gegeben hat – erstellen lassen. Im Hauptfenster steht jetzt ein Reiter “GUI Editor”; wenn man
diesen auswählt, wird der Jigloo Editor geöffnet und es können verschiedene Komponenten mit
Drag and Drop hinzugefügt und verschoben werden.
Möchte man bereits implementierte Klassen verändern, muss man diese im Jigloo Editor öffnen.
Dafür klickt man mit der rechten Maustaste auf die Datei und wählt “Open with i Form Editor”
aus. Nun erscheint auch im Hauptfenster der Menüreiter “GUI Editor” und die Datei kann mit
Jigloo editiert werden.
Das gesamte Manual kann auf [Clo10] nachgeschlagen werden.
Probleme mit Jigloo
Im Stackrechner werden der Stack und die Berechnungsschritte – welche sich in einem
JScrollPane befinden – sowohl vertikal als auch horizontal gestreckt, damit bei Größenänderung
des Fensters diese zwei Bereiche mitverändert werden. Dies kann im Jigloo Editor eingestellt
werden (Expands Horizontally und Expands Vertically). Wird jedoch anschließend mittels Drag
and Drop etwas an der Oberfläche verändert, dann kann es vorkommen, dass diese Expandierungen gelöscht werden. So muss man bei jeder Änderung sowohl Expands Horizontally als auch
Expands Vertically wieder aktivieren. Darüber hinaus sind keine weiteren Probleme aufgefallen.
88
B
GUI-Editor und Piktogramme
Tango Desktop Project
Bilder können dazu beitragen, die Benutzung einer Applikation zu vereinfachen. Beim Einsatz
von Bildern ist jedoch darauf zu achten, dass deren Inhalt Assoziationen zu bekannten (und
bestimmten) Gegenständen oder Aktionen auslöst. Hierfür sind Piktogramme besonders gut
geeignet, da sie genau dieses Bekannte in reduzierter und stilisierter Form abbilden. [This03]
Das Tango Desktop Project strebt eine Vereinheitlichung von Desktopoberflächen an und stellt
dafür einen Pool an Piktogrammen zur Verfügung. Das Projekt wurde lizensiert unter der GPL.
Sowohl für den implementierten Stackrechner als auch für das Konzept und die Abbildungen
der graphischen BenutzerInnenschnittstelle des Rechners für endliche Körper wurden Piktogramme des Tango Desktop Projects verwendet. Mehr über das Tango Desktop Project kann
unter [Tan10] nachgelesen werden, unter derselben Adresse können auch die bereitgestellten
Icons heruntergelanden werden.
C Symbole und Notation
Z
Menge der ganzen Zahlen, d.h. Z = {. . . , −2, −1, 0, 1, 2, . . .}.
N
Menge der natürlichen Zahlen, inklusive der 0, d.h. N = {0, 1, 2, . . .}.
P
Menge der Primzahlen. Primzahlen sind solche, deren einzige Teiler die
1 und sie selber sind.
R
Menge der reellen Zahlen, entsprechen alle Punkte der Zahlengerade.
Q
Menge der rationalen Zahlen, es gilt P ⊂ N ⊂ Z ⊂ Q ⊂ R.
Zn
Zn = {x|0 ≤ x < n} Ring der Restklassen modulo n.
x ≡ y (mod n)
x kongruent y modulo n (x MOD n = y MOD n).
x ∈R N
x ist eine Zufallszahl aus N.
ggT(x, y)
Größter gemeinsamer Teiler von x und y.
y = (yn−1 · · · y1 y0 )b
Binärdarstellung von y der Zahlenbasis b.
[a, b)
Rechtsoffenes Intervall: {x ∈ R|a ≤ x < b}.
(a, b)
Offenes Intervall: {x ∈ R|a < x < b}.
[a : b]
Intervall in den ganzen Zahlen: {x ∈ Z|a ≤ x ≤ b}.
[a : b)
Intervall in den ganzen Zahlen: {x ∈ Z|a ≤ x < b}.
Literaturverzeichnis
[ACDF+ 06] R. M. Avanzi, H. Cohen, C. Doche, G. Frey, T. Lange, K. Nguyen, F. Vercauteren: Handbook of Elliptic and Hyperelliptic Curve Cryptography. Chapman &
Hall/CRC (2006).
[AcKS06]
O. Aciiçmez, Ç. K. Koç, J.-P. Seifert: On the Power of Simple Branch Prediction
Analysis. Technical report, School of EECS, Oregon State University, Corvallis, OR
97331, USA (2006).
[AgKS04]
M. Agrawal, N. Kayal, N. Saxena: PRIMES is in P. In: Annals of Mathematics,
160, 2 (2004), 781–793.
[Apa10]
Apache Software Foundation: The Apache Ant Project. http://ant.apache.org/
(2010).
[Bart03]
A. P. Barth: Algorithmik für Einsteiger: Für Studierende, Lehrer und Schüler in
den Fächern Mathematik und Informatik. Vieweg+Teubner Verlag (2003).
[BaSh96]
E. Bach, J. Shallit: Algorithmic Number Theory; Volume I: Efficient Algorithms.
The MIT Press (1996).
[BeBF02]
A. Bertsch, F. Bourseau, D. Fox: Perspektive kryptografischer Verfahren auf elliptischen Kurven. In: Datenschutz und Datensicherheit, 26, 2 (2002).
[Bern97]
D. J. Bernstein: Multidigit Multiplication For Mathematicians (1997), preprint,
available from http://cr.yp.to/papers.html.
[BGTZ08]
R. Brent, P. Gaudry, E. Thomé, P. Zimmermann: Faster Multiplication in GF(2)[x].
http://hal.archives-ouvertes.fr/docs/00/33/74/43/PDF/gf2x.pdf (2008).
[BlSS04]
I. F. Blake, G. Seroussi, N. P. Smart (Hrsg.): Advances in elliptic curve cryptography, Bd. 317. Cambridge University Press (2004).
[Buch04]
J. Buchmann: Einführung in die Kryptographie. Springer-Verlag (2004).
[Clo10]
CloudGarden: Jigloo. http://www.cloudgarden.com/jigloo/, Stand: 14.07.2010
(2010).
[Cohe03]
J. S. Cohen: Computer Algebra and Symbolic Computation: Mathematical Methods. A K Peters, Ltd. (2003).
[Dahm06]
M. Dahm: Grundlagen der Mensch-Compuer-Interaktion. Pearson Studium (2006).
[Dank06]
W. Dankmeier: Grundkurs Codierung. Friedr. Vieweg & Sohn Verlag (2006).
[DaST93]
J. H. Davenport, Y. Siret, E. Tournier: Computer Algebra, Systems and Algorithms
for Algebraic Computation. Academic Press, pub-AP:adr, second Aufl. (1993),
92
Literaturverzeichnis
translated from the French by A. Davenport and J. H. Davenport. Also available in French and Russian.
[DerS10]
DerStandart.at: Der Präsident war ziemlich sauer auf mich. http://derstandard.at/
1282979136148/Der-Praesident-war-ziemlich-sauer-auf-mich, Stand: 21.09.2010,
Printausgabe: 8.9.2010 (2010).
[Ecl10]
Eclipse Foundation: Eclipse.org home. http://www.eclipse.org (2010).
[Egge09]
M. A. Egger: Endliche Körper. Diplomarbeit, Alpen-Adria-Universität Klagenfurt
(2009).
[ElGa85]
T. ElGamal: A Public-Key Cryptosystem and a Signature Scheme Based on Discrete Logarithms. In: IEEE Transactions on Information Theory, 31, 4 (1985),
469–472.
[GaGe03]
J. von zur Gathen, J. Gerhard: Modern Computer Algebra (Second Edition). The
Press Syndicate of the University of Cambridge (2003).
[GaKZ07]
P. Gaudry, A. Kruppa, P. Zimmermann: A GMP-based Implementation of
Schönhage-Strassen’s Large Integer Multiplication Algorithm. In: ISSAC ’07: Proceedings of the 2007 international symposium on Symbolic and algebraic computation, ACM, New York, NY, USA (2007), 167–174.
[Good06]
F. M. Goodman: Algebra: Abstract and Concrete, ed. 2.5. Semisimple Press Iowa
City, IA (2006).
[Gord84]
J. Gordon: Strong Primes are Easy to Find. In: EUROCRYPT (1984), 216–223.
[Hall07]
S. Hallgren: Polynomial-Time Quantum Algorithms for Pell’s Equation and the
Principal Ideal Problem. In: J. ACM, 54, 1 (2007), 1–19.
[HeLP97]
M. Helander, T. Landauer, P. Prabhu: Handbook of Human-Computer Interaction.
Elsevier (1997).
[Heun03]
V. Heun: Grundlegende Algorithmen — Einführung in den Entwurf und die Analyse effizienter Algorithmen. Vieweg Verlag, Braunschweig-Wiesbaden (2003), 2.
Auflage.
[Hors99]
P. Horster: Von der Schwierigkeit, sichere Kryptosysteme zu entwerfen. In: Angewandte Mathematik insbesondere Informatik (1999), 102–137.
[Hunt10]
J. Hunter: JDOM. http://www.jdom.org, Stand: 14.07.2010 (2010).
[ISO96]
ISO: ISO/IEC 14977:1996: Information technology — Syntactic metalanguage
— Extended BNF. International Organization for Standardization, pub-ISO:adr
(1996).
[John00]
J. Johnson: GUI Bloopers. Morgan Kaufmann (2000).
[Jung93]
D. Jungnickel: Finite Fields: Structure and Arithmetics. Bibliographisches Institut
& F.A. Brockhaus AG (1993).
[KaKi10]
C. Karpfinger, H. Kiechle: Kryptologie : algebraische Methoden und Algorithmen.
Wiesbaden : Vieweg + Teubner (2010).
92
Literaturverzeichnis
93
[KaOf63]
A. Karatsuba, Y. Ofman: Multiplication of multidigit numbers on automata. In:
Soviet Physics Doklady, 7 (1963), 595–596.
[Kapl05]
M. Kaplan: Computer Algebra. Springer-Verlag Berlin Heidelberg (2005).
[Knut01]
D. E. Knuth: Arithmetik. Springer (2001), Übersetzt von R. Loos, Originaltitel:
The Art of Computer Programming, Volume 3, Chapter 4, Addison-Wesley 1998.
[Koep06]
W. Koepf: Computeralgebra. Springer-Verlag Berlin Heidelberg (2006).
[KoMe04]
N. Koblitz, A. J. Menezes: Obstacles to the torsion-subgroup attack on the decision
Diffie–Hellman Problem. In: Mathematics of Computation, 73, 248 (2004), 2027–
2041.
[Krop10]
J. Kropf: JavaPP. http://www.slashdev.ca/javapp/, Stand: 21.08.2010 (2010).
[Kurz77]
H. Kurzweil: Endliche Gruppen. Springer-Verlag Berlin Heidelberg (1977).
[Kurz07]
H. Kurzweil: Endliche Körper. Springer-Verlag Berlin Heidelberg (2007).
[KuSt98]
H. Kurzweil, B. Stellmacher: Theorie der endlichen Gruppen. Springer-Verlag Berlin
Heidelberg (1998).
[Lafo10]
E. Lafortune: http://proguard.sourceforge.net/, Stand: 12.07.2010 (2010).
[Lent10]
K. Lentzsch: JGOODIES: Java User Interface Design. http://www.jgoodies.com,
Stand: 14.07.2010 (2010).
[LiHL03]
C.-B. Liu, C.-H. Huang, C.-L. Lei: Design and implementation of long-digit Karatsuba’s multiplier using tensor product formulation. In: In The Ninth Workshop on
Compiler Techniques for HighPerformance Computing (2003), 23–30.
[LiNi00]
R. Lidl, H. Niederreiter: Finite fields. Cambridge University Press (2000).
[LiPi98]
R. Lidl, G. Pilz: Applied abstract algebra. Springer-Verlag (1998).
[Lory07]
P. Lory: Reducing the Complexity in the Distributed Multiplication Protocol of Two
Polynomially Shared Values. In: Proceedings of the 21st International Conference
on Advanced Networking and Applications (AINA 2007), Symposium on Security
in Networks and Distributed Systems (SSNDS-07), IEEE Computer Society (2007),
404–408.
[Lory08]
P. Lory: Accelerating the Distributed Multiplication Protocol with Applications to
the Distributed Miller-Rabin Primality Test. In: From Nano to Space - Applied
Mathematics Inspired by Roland Bulirsch, Springer Verlag (2008), 245–257.
[Mene93]
A. J. Menezes (Hrsg.): Elliptic Curve Public Key Cryptosystems. Kluwer Academic
Publishers (1993).
[MeVO96]
A. J. Menezes, S. A. Vanstone, P. C. V. Oorschot: Handbook of Applied Cryptography. CRC Press, Inc., Boca Raton, FL, USA (1996).
[More94]
A. K. More: Endliche Körper und Polynome über endliche Körper. Diplomarbeit,
Universität Klagenfurt (1994).
[MuMu07]
G. L. Mullen, C. Mummert: Finite Fields and Applications. American Mathematical
Society (2007).
93
94
Literaturverzeichnis
[Pepp07]
P. Pepper: Programmieren lernen: Eine grundlegende Einführung mit Java. Springer (2007).
[Ries87]
H. Riesel: Prime Numbers and Computer Methods for Factorization (Revised and
corrected second printing). Birkhäuser Boston (1987).
[RiSA78]
R. L. Rivest, A. Shamir, L. M. Adleman: A Method for Obtaining Digital Signatures
and Public-Key Cryptosystems. In: Communications of the ACM, 21, 2 (1978), 120–
126.
[RSA10]
RSA: http://www.rsa.com, Stand: 12.07.2010 (2010).
[SaST04]
H. Sato, D. Schepers, T. Takagi: Exact Analysis of Montgomery Multiplication. In:
INDOCRYPT: International Conference in Cryptology in India, LNCS, SpringerVerlag (2004).
[Scha07]
M. Schaffer: Collision-Free Number Generation: Efficient Constructions, Privacy Issues, and Cryptographic Aspects. Dissertation, Alpen-Adria-Universität Klagenfurt
(2007).
[ScSt71]
A. Schönhage, V. Strassen: Schnelle Multiplikation grosser Zahlen. In: Computing,
7 (1971), 281–292.
[Shor94]
P. Shor: Algorithms for Quantum Computation: Discrete Logarithms and Factoring.
In: Symposium on Foundations of Computer Science, 0 (1994), 124–134.
[Shor03]
P. Shor: Why Haven’t More Quantum Algorithms Been Found? In: J. ACM, 50, 1
(2003), 87–90.
[Srin03]
R. Srinivas: Using AES with Java Technology (2003), http://java.sun.com/
developer/technicalArticles/Security/AES/AES v1.html, Stand: 21.08.2010.
[SUN10a]
SUN: BigInteger. http://download.oracle.com/javase/7/docs/api/java/math/
BigInteger.html, Stand: 14.07.2010 (2010).
[SUN10b]
SUN: Developer Resources for Java Technology. http://java.sun.com, Stand:
22.06.2010 (2010).
[SUN10c]
SUN: JOptionPane. http://download.oracle.com/javase/6/docs/api/javax/swing/
JOptionPane.html, Stand: 21.08.2010 (2010).
[SUN10d]
SUN: SecureRandom. http://download.oracle.com/docs/cd/E17409 01/javase/7/
docs/api/java/security/SecureRandom.html, Stand: 14.07.2010 (2010).
[Tan10]
Tango Desktop Project: http://tango.freedesktop.org, Stand: 16.05.2010 (2010).
[This03]
F. Thissen: Kompendium Screen-Design: effektiv informieren und kommunizieren
mit Multimedia. X. media. press Series, Springer (2003).
[TOP10]
TOP500: http://www.top500.org/lists/2010/11, Stand: 23.11.2010 (2010).
[Wern08]
M. Werner: Information und Codierung. Wiesbaden : Vieweg + Teubner (2008).
[Will08]
W. Willems: Codierungstheorie und Kryptographie. Mathematik kompakt,
Birkhäuser (2008).
94
Herunterladen