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