Aufgabe 6: Public-Key-Verfahren

Werbung
Mathematisches Praktikum - SoSe 2015
Prof. Dr. Wolfgang Dahmen — Felix Gruber, Igor Voulis
Aufgabe 6
Bearbeitungszeit: zwei Wochen (bis Freitag, den 17. Juli 2015)
Mathematischer Hintergrund: Public-Key-Kryptosysteme, RSA-Algorithmus, erweiterter Euklidischer
Algorithmus, Primzahltests
Elemente von C++: Klassen, Ein- und Ausgabeoperatoren, Standard Template Library (STL)
Public-Key-Verfahren
Moderne kryptographische Methoden finden im heutigen Alltag viele Anwendungen. Sie werden beispielsweise
bei der Zugangskontrolle zu Computernetzen, zur Geheimhaltung von Daten oder zum Signieren elektronischer Dokumente benutzt.
Die Standardanwendung der Kryptographie sind die Verschlüsselungsverfahren zur Geheimhaltung von Daten. Man unterscheidet zwischen symmetrischen und asymmetrischen Verfahren. Grundsätzlich schickt immer Alice (A) eine Nachricht an Bob (B), die sie mit einem Schlüssel 𝑒 (encryption) unter einem gegebenen
Verschlüsselungsverfahren verschlüsselt. Bob wiederum entschlüsselt diese Nachricht mit dem dafür vorgesehenen Schlüssel 𝑑 (decryption). Sind diese beiden Schlüssel gleich, oder zumindest der eine leicht aus dem
anderen zu berechnen, so handelt es sich um ein symmetrisches Verfahren, anderenfalls um ein asymmetrisches Verfahren.
Bei einem symmetrischen Verfahren müssen Alice und Bob zur Kommunikation über einen sicheren Kanal
den Schlüssel austauschen, was eines der zentralen Probleme dieser Art von Verfahren darstellt. Denkt man
an mehrere Benutzer, die allesamt Daten sicher miteinander austauschen wollen,
(︀𝑛)οΈ€so muss jeder mit jedem
einen sicheren Schlüssel vereinbaren. Bei 𝑛 Teilnehmern sind das insgesamt schon 2 Schlüssel, die zusätzlich
auch noch sicher abgelegt sein müssen.
Einen Ausweg stellen die so genannten Public-Key-Verfahren dar, bei denen ein Schlüssel 𝑒 allgemein bekannt
ist (public key) und nur der geheime Schlüssel 𝑑 (private key) sicher verwahrt werden muss. Jeder, insbesondere Alice, kann dann mit Hilfe von Bobs öffentlichem Schlüssel 𝑒𝐡 eine Nachricht an ihn verschlüsseln, die
aber nur Bob wieder entschlüsseln kann, da nur er seinen privaten Schlüssel 𝑑𝐡 kennt. Der geheime Austausch
entfällt ebenso wie die Verwaltung einer sehr großen Menge von Schlüsseln. Bei dieser Art Verfahren muss
allerdings sichergestellt sein, dass der öffentliche Schlüssel von Bob auch von Bob ist und nicht von einem
unbekannten Dritten ausgetauscht wurde. Hierzu dient eine Public-Key-Infrastruktur, zu der beispielsweise
eine Certification-Authority (CA) gehört, die garantiert, dass die verwahrten öffentlichen Schlüssel von den
zugehörigen Personen stammen. Solche CAs gibt es mittlerweile sowohl bei öffentlichen Trägern (RWTH
Aachen1 , Bund) als auch bei privaten Firmen. Eine dezentrale Aternative zu CAs bietet ein Web of Trust,
wie es zum Beispiel von OpenPGP verwendet wird.
Leider sind die Public-Key-Verfahren in der Regel nicht so effizient wie viele der symmetrischen Verfahren, so
dass man bei großen Datenmengen hybride Verfahren anwendet. Dazu erzeugt Alice einen Sitzungsschlüssel
und verschlüsselt damit ihre Daten mit Hilfe eines symmetrischen Verfahrens. Dann verschlüsselt sie diesen
Sitzungsschlüssel mit einem Public-Key-Verfahren mit dem öffentlichen Schlüssel von Bob, und hängt ihn
an die verschlüsselten Daten an. Den Sitzungsschlüssel verwirft sie nun bzw. erzeugt zu Beginn der nächsten Kommunikation wieder einen Neuen. Das Public-Key-Verfahren wird hier also nur für wenige Daten
verwendet, nämlich für den Schlüssel des symmetrischen Verfahrens.
1
Zertifizierungsstelle der RWTH Aachen: https://doc.itc.rwth-aachen.de/display/CERT/Home
1
Signaturprüfung
Im Unterschied zur Datenverschlüsselung geht es bei der Signaturprüfung nicht darum, Daten geheim zu
halten, sondern man möchte sicherstellen, dass ein empfangenes Dokument auch vom angegebenen Autor
stammt, z.B. von Alice. Ist bei dem asymmetrischen Verfahren unerheblich, ob man mit dem öffentlichen
Schlüssel 𝑒 ver- und mit dem privaten Schlüssel 𝑑 entschlüsselt oder umgekehrt (das gilt nicht immer), so
geht Alice wie folgt vor: Mit Hilfe einer sogenannten kryptographischen Hashfunktion berechnet sie von ihrem
Dokument einen charakteristischen Fingerabdruck, auch Signatur genannt, normalerweise eine große Zahl.
Bei jeder Veränderung des Textes ändert sich dieser Fingerabdruck. Sie verschlüsselt nun die Signatur mit
ihrem privaten Schlüssel und hängt sie an das Dokument an. Ist bekannt, welche Hashfunktion sie benutzt
hat2 , so kann Bob die Echtheit des empfangenen Dokumentes folgendermaßen feststellen: er entschlüsselt die
angehängte Signatur mit dem öffentlichen Schlüssel von Alice (über eine CA erhältlich) und vergleicht sie
mit dem Fingerabdruck des empfangenen Dokumentes.
Ihre Aufgabe ist es nun, ein Public-Key-Verfahren für eine Signaturprüfung zu programmieren. Es handelt
sich dabei um das erste und wahrscheinlich auch bekannteste Verfahren, das RSA-Verfahren [5]. Es ist benannt nach R. Rivest, A. Shamir und L. Adleman, die es 1977 entwickelten. Mittels einer Hashfunktion und
einer Liste von Public-Keys sollen Sie entscheiden, welcher der Beispieltexte authentisch ist und vom Autor
stammt und welcher Text gefälscht wurde. Zusätzlich sollen Sie ein Public-Private-Key Paar erzeugen (Primzahlerzeugung) und damit eine selbst geschriebene Gedichtzeile signieren (also den Wert der Hashfunktion
verschlüsseln).
Wir beschreiben zunächst das RSA-Verfahren und im Anschluss daran die zu benutzende Hashfunktion. Die
auftretenden Teilaufgaben werden am Ende des Abschnitts diskutiert.
RSA-Verfahren
Als erstes erzeugt man den privaten und den öffentlichen Schlüssel. Dazu wählt man zwei zufällige Primzahlen
𝑝 und π‘ž mit 𝑝 > 2, π‘ž > 2 und 𝑝 ΜΈ= π‘ž und berechnet das RSA-Modul
𝑛 = π‘π‘ž.
Die Eulersche πœ™-Funktion3 hat damit den Wert
πœ™(𝑛) = (𝑝 − 1)(π‘ž − 1).
Weiter wählt man als Verschlüsselungsexponenten 𝑒 eine Zahl, die
1 < 𝑒 < πœ™(𝑛) und ggT(𝑒, πœ™(𝑛)) = 1
erfüllt4 . Dazu berechnet man den Entschlüsselungsexponenten 𝑑 unter der Bedingung
1 < 𝑑 < πœ™(𝑛) und 𝑑𝑒 ≡ 1 mod πœ™(𝑛).
Diese Zahl 𝑑 kann mit dem erweiterten Euklidischen Algorithmus ermittelt werden. Der öffentliche Schlüssel
ist dann das Paar (𝑛, 𝑒) und der private Schlüssel die Zahl 𝑑.
Verschlüsselt wird eine Zahl π‘š mit 0 ≤ π‘š < 𝑛 wie folgt
𝑐 ≡ π‘šπ‘’ mod 𝑛.
Zur Berechnung wird das schnelle Potenzieren benutzt. Entschlüsselt wird eine Nachricht 𝑐 durch
π‘š ≡ 𝑐𝑑 mod 𝑛.
2
Das ist kein Sicherheitsrisiko und nicht geheim!
Die Eulersche πœ™-Funktion πœ™ : N → N, π‘š ↦→ #{1 ≤ π‘Ž ≤ π‘š : ggT(π‘Ž, π‘š) = 1} gibt die Anzahl der zu π‘š teilerfremden natürlichen
Zahlen kleiner oder gleich π‘š an.
4
ggT bedeutet größter gemeinsamer Teiler.
3
2
Das Funktionsprinzip beruht auf der Tatsache, dass für die oben konstruierten Schlüssel des RSA-Verfahrens
gilt:
(π‘šπ‘’ )𝑑 ≡ π‘š mod 𝑛.
Insbesondere ist das Verfahren symmetrisch bezüglich der Verwendung der Schlüssel, d.h. die Rollen von
𝑒 und 𝑑 können vertauscht werden. Die Signaturprüfung läßt sich also mit dem oben beschriebenen RSAVerfahren durchführen.
Hashfunktion, MD5
Ist Σ ein Alphabet, so ist eine Hashfunktion β„Ž eine Abbildung
β„Ž : Σ* → Σ𝑛 ,
𝑛 ∈ Z.
Sie bildet Strings beliebiger Länge auf Strings mit fester Länge 𝑛 ab. Es soll in dieser Übung keine sinnvolle
Hashfunktion geschrieben, sondern nur eine existierende, nämlich die MD5-Hashfunktion, benutzt werden.
Es handelt sich hierbei um einen Algorithmus, der aus einem Text einen Fingerabdruck der Länge 128
Bit erzeugt. Dazu werden in verschiedenen Durchläufen bitweise Verknüpfungen auf Blöcken innerhalb des
Textes erstellt. Eine genau Beschreibung findet sich in [2]. Wir verwenden MD5 da es realtiv einfach ist; jedoch
sollte es nicht mehr für kryptographische Anwendungen benutzt werden, da es einzwischen Verfahren gibt,
mit denen sich leicht Kollisionen von MD5-Hashes generieren lassen. Als Alternative bietet sich beispielsweise
SHA-3 an.
Arbeiten wir zur Ver- und Entschlüsselung mit dem Datentyp uint64_t, der 64-bittig ist, so belegt ein
Fingerabdruck ein uint64_t-Feld der Länge 2. Diese Funktionalität ist in der Klasse Signatur gekapselt,
und Objekte dieses Typs werden als Argument der Testroutinen benutzt. Eine Beschreibung der Klasse
finden Sie am Ende dieses Dokuments.
Ihre Aufgabe ist es nun, bei einem empfangenen Text, der auf Authentizität untersucht werden soll, einen
solchen Fingerabdruck zu entschlüsseln und mit dem Fingerabdruck des Textes zu vergleichen, oder bei einem
zu sendenden Text einen mit Ihrem Schlüssel verschlüsselten Fingerabdruck mitzuschicken.
Euklidischer Algorithmus und Erweiterter Euklidischer Algorithmus
Der Euklidische Algorithmus berechnet den größten gemeinsamen Teiler ggT(π‘Ž, 𝑏) zweier Zahlen π‘Ž und 𝑏.
Der Algorithmus beruht auf folgender Eigenschaft:
Ist 𝑏 = 0, so ist ggT(π‘Ž, 𝑏) = |π‘Ž|. Anderenfalls gilt ggT(π‘Ž, 𝑏) =ggT(|𝑏|, π‘Ž mod 𝑏).
Schreiben Sie eine Funktion ggt mit folgendem Interface
uint64_t ggt(uint64_t a, uint64_t b);
welche den ggT(π‘Ž, 𝑏) für Zahlen π‘Ž und 𝑏 berechnet und zurückgibt.
Der erweiterte Euklidische Algorithmus berechnet darüber hinaus zwei Zahlen π‘₯, 𝑦 ∈ Z, so dass gilt5 :
ggT(π‘Ž, 𝑏) = π‘₯π‘Ž + 𝑦𝑏.
Hierfür werden Folgen (π‘₯π‘˜ ),(π‘¦π‘˜ ),(π‘Ÿπ‘˜ ) und (π‘žπ‘˜ ) konstruiert, bis für ein 𝑛 ≥ 2 der Rest π‘Ÿπ‘›+1 = 0 wird. Der
Term π‘Ÿπ‘› entspricht dann dem ggT(π‘Ž, 𝑏), und die gesuchten Werte für π‘₯ und 𝑦 lauten π‘₯ = (−1)𝑛 π‘₯𝑛 und
5
Man kann zeigen, dass solche ganzen Zahlen π‘₯ und 𝑦 immer existieren.
3
𝑦 = (−1)𝑛+1 𝑦𝑛 . Die Rekursion wird beschrieben durch
π‘Ÿπ‘˜ = (−1)π‘˜ π‘₯π‘˜ π‘Ž + (−1)π‘˜+1 π‘¦π‘˜ 𝑏
⌋οΈ‚
⌊οΈ‚
π‘Ÿπ‘˜−1
π‘žπ‘˜ =
π‘Ÿπ‘˜
⎧
βŽͺ
⎨(π‘žπ‘˜ π‘₯π‘˜ + π‘₯π‘˜−1 , π‘žπ‘˜ π‘¦π‘˜ + π‘¦π‘˜−1 )
(π‘₯π‘˜+1 , π‘¦π‘˜+1 ) = (0, 1)
βŽͺ
⎩
(1, 0)
0≤π‘˜ ≤𝑛+1
1≤π‘˜≤𝑛
1≤π‘˜≤𝑛
π‘˜=0
π‘˜ = −1
Schreiben Sie eine Funktion xggt mit folgendem Interface
uint64_t xggt(uint64_t a, uint64_t b, int64_t& x, int64_t& y);
welche den Wert ggT(π‘Ž, 𝑏) für Zahlen π‘Ž und 𝑏 zurückgibt sowie die Zahlen π‘₯ und 𝑦 nach obigem Schema
berechnet. Auch wenn mathematisch gesehen eine Folge von Zahlen konstruiert wird, sollten Sie bei der
Implementierung der zweistufigen Rekursion das Anlegen von Feldern vermeiden, deren Länge 3 übersteigt.
Überlegen sie sich, wie Sie mit Hilfe des obigen erweiterten Euklidischen Algorithmus den Entschlüsselungsexponenten 𝑑 aus dem RSA-Verfahren berechnen können.
Beispiel zum Erweiterten Euklidischen Algorithmus mit π‘Ž = 120 und 𝑏 = 25:
π‘˜
π‘Ÿπ‘˜ π‘žπ‘˜ π‘₯π‘˜ π‘¦π‘˜
0 120 − 1 0
1 25 4 0 1
2 20 1 1 4
3
5 4 1 5
4
0 − 5 24
Daher ist ggT(120, 25) = 5, 𝑛 = 3, π‘₯ = −1 und 𝑦 = 5.
Schnelle Potenzberechnung
Angenommen es ist auszurechnen:
π‘Žπ‘₯ mod 𝑛.
Weiter sei
π‘₯=
π‘˜
∑︁
π‘₯𝑖 2𝑖
𝑖=0
die Binärentwicklung von π‘₯. Dabei ist π‘₯𝑖 ∈ {0, 1} für alle 0 ≤ 𝑖 ≤ π‘˜. Wegen
π‘₯
∑οΈ€π‘˜
π‘Ž =π‘Ž
𝑖=0
π‘₯𝑖 2𝑖
=
π‘˜
∏︁
𝑖
(π‘Ž2 )π‘₯𝑖 =
𝑖=0
∏︁
𝑖
π‘Ž2
0≤𝑖≤π‘˜,π‘₯𝑖 =1
𝑖
kann man das Produkt nun aus der Multiplikation der Quadrate π‘Ž2 berechnen, für die π‘₯𝑖 = 1 gilt. Bei der
Umsetzung ist auszunutzen, dass
𝑖+1
𝑖
π‘Ž2 = (π‘Ž2 )2
𝑖
gilt. Die Modulo-Rechnung kann auf jeden Faktor π‘Ž2 einzeln angewandt werden. Erst dies ermöglicht bei
realistischen Größenordnungen von π‘Ž, π‘₯ und 𝑛 die Berechnung des Ausdrucks π‘Žπ‘₯ mod 𝑛.
4
Primzahlerzeugung
Primzahlen einer gegebenen Größenordnung (in Bits) kann man dadurch erzeugen, dass man das erste und
das letzte Bit in einer Darstellung auf 1 setzt, also die Größenordnung sowie die Zahl als ungerade festlegt.
Dann füllt man die inneren Bits zufällig mit 0 oder 1 und führt schließlich mit der so entstandenen Zahl
einen Primzahltest durch6 . Realistische Größenordnungen sind 500-2000 Bit, d.h. 150-600 Dezimalstellen.
Im Rahmen dieser Aufgabe wollen wir uns aber auf kleine Primzahlen (16 Bit) beschränken. Ihre Aufgabe
ist es, zwei (kleine!) Primzahlen 𝑝 und π‘ž in der Größenordnung bis 216 − 1 = 65535 zu finden. Primzahltests
findet man in der Literatur z.B. unter den Stichworten:
βˆ™ Probedivision,
βˆ™ Sieb des Eratosthenes,
βˆ™ Fermat-Test und
βˆ™ Miller-Rabin-Test.
Im Folgenden skizzieren wir die Probedivision. Die beiden letzt genannten Methoden beruhen auf dem kleinen
Satz von Fermat bzw. einer Erweiterung und erfordern etwas mehr Realisierungsaufwand. Sie sollen daher
nicht mehr Gegenstand dieser Übung sein.
Probedivision
Um festzustellen, dass eine gegebene Zahl 𝑛 nicht prim ist, reicht es offenbar aus, mindestens einen Faktor
√
1 < π‘ž < 𝑛 von 𝑛 zu finden. Umgekehrt ist eine Zahl dann prim, wenn keine der Primzahlen 𝑝 ≤ 𝑛 die Zahl
𝑛 (ohne Rest) teilt.
Erzeugen Sie mit dieser Methode sukzessive alle Primzahlen, die kleiner sind als eine gegebene Oberschranke pMax (hier fest als 65536 codiert). Arbeiten Sie dafür auf der bereitgestellten Klasse Prim. Die in der
Methode Prim::init() nach und nach zu ermittelnden Primzahlen werden in der Template-Datenstruktur
set<T> pMenge gesammelt, wobei für T der in unit.h definierte Datentyp uint64_t verwendet werden soll.
Die interne Sortierung der Einträge dieser Menge erfolgt nach dem “<”-Kriterium der Klasse uint64_t, d.h.
aufsteigend. Bei der Probedivision für eine Zahl 𝑛 müssen nun alle ”kleinen“ Elemente 𝑝 der pMenge, d.h.
√
alle 𝑝 ≤ 𝑛, als Divisor getestet werden. Nähere Erläuterungen zum Zugriff auf einzelne Elemente enthält
der folgende Abschnitt.
Sets und Maps
Variablen vom Typ set (Menge) und map (Abbildung) sind assoziative Container, d.h. Objekte, die zum
Verwalten anderer Objekte dienen. Beide Datentypen sind als vorgefertigte Template-Klassen in der Standard
Template Library (STL) von C++ enthalten. Der Zugriff auf die Daten eines assoziativen Containers erfolgt
anhand eines Schlüssels7 . Im Fall eines sets sind Schlüssel und Daten identisch, bei einer map werden diese
aber i.a. nicht übereinstimmen. Der Typ des Schlüssels und der Daten ist frei wählbar. Organisiert man
beispielsweise ein Adressbuch als map, dient der Name einer Person als Schlüssel (nach diesem wird der
Container intern sortiert), während die Angaben zu Wohnort, Straße und Hausnummer als Daten unter dem
jeweiligen Schlüssel abgelegt werden.
Für die Verwendung solcher Container im Programm definiert man sich am einfachsten mittels typedef
einen neuen Datentyp. Mit
6
Bertrand’s Postulat: “Zu jedem 𝑛 ∈ N existiert eine Primzahl 𝑝 mit 𝑛 < 𝑝 ≤ 2𝑛” sichert das Auffinden zumindest einer
Primzahl im untersuchten Intervall.
7
dieser hat nichts mit der Verschlüsselung einer Nachricht zu tun
5
#include <set>
#include <map>
typedef set<int>
mySet;
typedef map<string, int> myMap;
wird mySet als Menge von int-Zahlen und myMap als Abbildung mit string-Schlüsseln und int-Daten
deklariert. Damit kann z.B. ein Lottoschein oder ein Telefonregister realisiert werden:
mySet Lottozahlen;
myMap TelReg;
Lottozahlen.insert(12);
Lottozahlen.insert(47);
Lottozahlen.insert(18);
Lottozahlen.insert(24);
Lottozahlen.insert(2);
Lottozahlen.insert(33);
TelReg["Ron"]
= 79825;
TelReg["Hermine"] = 22235;
TelReg["Harry"]
= 40108;
cout << TelReg["Hermine"] << endl;
Um Container zu durchlaufen, werden Iteratoren benutzt. Iteratoren sind eine Verallgemeinerung von Zeigern.
Sie erlauben es, mit verschiedenen Containern auf gleiche Weise zu arbeiten. Die Ausgabe aller Elemente des
Lottoscheins bzw. des Telefonregisters erfolgt z.B. über
for (mySet::iterator it=Lottozahlen.begin(); it!=Lottozahlen.end(); ++it)
cout << *it << " ";
for (myMap::iterator it=TelReg.begin(); it!=TelReg.end(); ++it)
cout << "Name: " << it->first << ", TelNr: " << it->second << endl;
Hier bedeutet it->first (gleichbedeutend mit (*it).first) den Zugriff auf den Schlüssel (in diesem Falle
der Name) und it->second den Zugriff auf die Daten (hier die Telefonnummer). Beachten Sie, dass der
Zugriff auf ein Map-Element über den operator[] dieses Element erzeugt und in die Map aufnimmt, falls
es noch nicht existiert. Möchten sie nur testen, ob ein Schlüssel existiert, benutzen sie die Methode find.
Zusammenfassung der Aufgabe
Orientieren Sie sich bei der Bearbeitung der Aufgabe am besten an der vorgegebenen Struktur in der Datei
rsa.cpp.
Hauptpunkte:
A)
1) Generieren Sie ein Public-Private-Schlüsselpaar in der Funktion NeueSchluessel einer Klasse RSA
(s.u.). Testen sie dieses durch Aufruf von TestSchluessel. Insbesondere werden dort auch die
Funktionen Verschluesseln und Entschluesseln aufgerufen und überprüft.
2) Verschicken Sie mit der Routine SchickeNachricht eine beliebige Nachricht, und signieren Sie
diese mit Ihrem privaten Schlüssel.
6
B)
1) Lesen Sie die Datei keys.txt mit den öffentlichen Schlüsseln Ihrer Betreuer ein und legen Sie diese
in einer Map ab. Verwenden Sie für die Dateioperationen ifstream, für die Schlüssel die Klasse
RSA (s.u.) und für die Menge der Namen und Schlüssel die Klasse map mit den Typen string und
RSA.
2) Durchlaufen Sie die Map und holen Sie für jeden Betreuer mittels HoleNachricht eine Nachricht
und einen Fingerabdruck. Entschlüsseln Sie diesen Fingerabdruck mit Hilfe des öffentlichen Schlüssels aus der zuvor initialisierten Map und vergleichen ihn mit dem Fingerabdruck der Nachricht.
Geben Sie Ihr Ergebnis durch Aufruf von PruefeNachricht weiter.
Unterpunkte:
Schreiben Sie eine Klasse RSA mit den public-Variablen 𝑛, 𝑒, 𝑑 und den private-Variablen 𝑝, π‘ž, πœ™(𝑛). Diese
Klasse soll folgende Funktionen besitzen:
uint64_t Verschluesseln(const uint64_t& m, bool bPublic=true);
uint64_t Entschluesseln(const uint64_t& c, bool bPublic=false);
void NeueSchluessel();
Der boolesche Parameter der ersten beiden Funktionen zeigt an, ob der private oder der öffentliche Schlüssel
verwendet werden soll. Zur Ver- und Entschlüsselung benutzen Sie das RSA-Verfahren mit den Variablen
der Klasse. Sie benötigen folgende Routinen:
i) Schnelle Potenzierung.
ii) Euklidischer und Erweiterter Euklidischer Algorithmus.
iii) Ver- und Entschlüsselung.
Weiter soll die Klasse RSA einen Eingabe- und einen Ausgabeoperator besitzen. Eine erste Version mit den
Prototypen der Funktionen finden Sie in unit.h.
Existierende Hilfskonstrukte
Es werden die 64 Bit langen Integertypen uint64_t und int64_t (signed) aus dem Header cstdint verwendet, die mit dem C++11-Standard in C++ eingeführt wurden. Denken Sie also daran das Programm mit
der Compileroption -std=c++11 zu übersetzen.
Die Klasse Signatur kapselt im Wesentlichen die externen MD5-Routinen. Mit der Klassenfunktion
void set(const string& s);
können Sie den Fingerabdruck des Textes s in das interne Feld der Länge length() vom Typ uint64_t
setzen. Lesen und Setzen des Feldes erfolgt wie üblich über operator[].
Zufallszahlen erhält man mit Hilfe des eingebauten Pseudozufallszahlen-Generators. Dieser ist zu Beginn
Ihres Programmes zu initialisieren (z.B. mit srand(time(NULL))). Der Aufruf von rand() liefert dann eine
Zufallszahl im Bereich von 0 bis RAND_MAX= 2147483647 (Typ uint64_t).
Zum Testen der Funktionen ggt, xggt und fastpow gibt es die Hilfsfunktionen Pruefe_ggt,
Pruefe_xggt und Pruefe_fastpow, die Sie jeweils mit ihrem Funktionsnamen als Argument ausfrufen. Die
Prüfroutinen testen dann Ihre Funktionen mit verschiedenen Parametern, siehe auch rsa.cpp.
7
Literatur
[1] C/C++-Referenz. http://www.cppreference.com/index.html.
[2] MD5. http://www.ietf.org/rfc/rfc1321.txt.
[3] Buchmann, J.: Einführung in die Kryptographie. Springer Verlag, Heidelberg, 1999.
[4] Menezes, A. J., P. C. van Oorschot und S. A. Vanstone: Handbook of Applied Cryptography. CRC
Press, Boca Raton, 1996. http://www.cacr.math.uwaterloo.ca/hac/.
[5] Rivest, R. L., A. Shamir und L. Adleman: A Method for Obtaining Digital Signatures and Public-Key
Cryptosystems. Communications of the ACM, 21(2):120–126, 1978. Preprint: http://people.csail.
mit.edu/rivest/Rsapaper.pdf.
[6] Schneier, B.: Applied Cryptography. John Wiley & Sons, New York, 1996.
8
Herunterladen