Station - Prototyping
Prototypen
Bei der Entwicklung neuer Produkte wird zunächst oft ein Prototyp erstellt. Dabei handelt es sich um ein funktionsfähiges Modell, das dem geplanten Produkt in vielen Bereichen entspricht, meist aber auch eine Reihe von Vereinfachungen aufweist.
Prototypen werden - insbesondere bei komplexen technischen Systemen - zunächst einmal erstellt, um Erfahrungen mit dem neuen System und seiner Entwicklung zu sammeln.
Prototypen kommen entsprechend auch bei der Entwicklung komplexer Software zum Einsatz. Wir skizzieren im Folgenden, wie ein Prototyp zu einem RSA-Kryptosystem mit Hilfe der funktionalen Programmierung erstellt werden kann.
Weitere Informationen zum RSA-Verfahren findest du im Abschnitt Das RSA-Verfahren.
Funktionen zum RSA-Verfahren
Beim RSA-Verfahren werden natürliche Zahlen mit Hilfe eines (öffentlichen bzw. privaten) Schlüssels in neue Zahlen umwandelt.
Dabei wird folgende Berechnungsvorschrift benutzt.
FUNKTION verschluesselnRSA: verschluesselnRSA(zahl, schluessel) -> (zahl ** schluessel[0]) % schluessel[1]
Anstatt Potenzbildung und Modulo-Operation nacheinander auszuführen, ist es günstiger, eine geeignete Hilfsfunktion zur effektiven Berechnung der modularen Potenz zu benutzen:
Diese Hilfsfunktion lässt sich folgendermaßen festlegen:
FUNKTION modpot: {Fall 1:} y == 0: modpot(x, y, m) -> 1 {Fall 2:} y != 0: {Fall 2.1:} y%2 == 1: modpot(x, y, n) -> (x*modpot((x*x)%m, y//2, m))%m {Fall 2.2:} y%2 != 1: modpot(x, y, n) -> (modpot((x*x)%m, y//2, m))%m
Die Korrektheit dieser Regeln uberprüft man am besten anhand konkreter Zahlenwerte:
FUNKTION modpot: {Fall 1:} modpot(2, 0, 5) -> 1 {Fall 2:} {Fall 2.1:} modpot(2, 7, 5) -> (2*modpot(4%5, 3, 5))%5 {Fall 2.2:} modpot(2, 6, 5) -> (modpot(4%5, 3, 5))%5
Mit der Hilfsfunktion lässt sich die RSA-Verschlüsselung aus so festlegen:
FUNKTION verschluesselnRSA: verschluesselnRSA(zahl, schluessel) -> modpot(zahl, schluessel[0], schluessel[1])
Wenn mehrere Zahlen zur Verschlüsselung vorliegen, benutzen wir die folgende Funktion:
Diese Funktion kann mit dem Map-Operator (siehe ...) oder direkt mit Regeln festgelegt werden:
FUNKTION verschluesselnListeRSA: {Fall 1:} zahlenListe == []: verschluesselnListeRSA(zahlenListe, schluessel) -> [] {Fall 2:} zahlenListe != []: verschluesselnListeRSA(zahlenListe, schluessel) -> [verschluesselnRSA(zahlenListe[0], schluessel)] + verschluesselnListeRSA(zahlenListe[1:], schluessel)
Funktionen zur Codierung und Decodierung
Das RSA-Verfahren wandelt natürliche Zahlen in natürliche Zahlen um. Wenn man eine Nachricht mit dem RSA-Verfahren verschlüsseln möchte, muss man diese zunächst in geeigneter Weise mit natürlichen zahlen codieren. Wir gehen dabei wie folgt vor.
'Köln' [75, 195, 182, 108, 110] K ö l n [19395, 46700, 110] 75*(256**1)+195*(256**0) 182*(256**1)+108*(256**0) 110*(256**0)
In einem ersten Schritt werden die Zeichen der Zeichenfolge in Bytes umgewandelt. Wir benutzen dabei die Standardcodierung "UTF-8" (siehe ...). Beachte, dass bei dieser Codierung Umlaute und Sonderzeichen ggf. mit mehreren Bytes dargestellt werden.
In einem zweiten Schritt werden aus Bytes Zahlen erzeugt. Die Bytezahlen werden hierbei als Ziffern
im 256-er-System angesehen.
Hierbei muss allerdings beachtet werden, dass die erzeugten Zahlen kleiner als die Zahl n
eines Schlüsselpaares (e, n)
oder (d, n)
sind.
Es dürfen daher nur eine begrenzte Anzahl von Bytes zusammengefasst werden.
Die Anzahl der Bytes, die in Abhängigkeit des verwendeten Schlüssels zusammengefasst werden, nennen
wir Blocklänge.
Beim Decodieren gehen wir den umgekehrten Weg:
[19395, 46700, 110] 75*(256**1)+195*(256**0) 182*(256**1)+108*(256**0) 110*(256**0) [75, 195, 182, 108, 110] K ö l n 'Köln'
Zur Durchführung dieser Umwandlungen werden eine Funktion zur Codierung
sowie eine Funktion zur Decodierung konzipiert.
Für die einzelnen Umwandlungsschritte werden ebenfalls Funktionen eingeführt.
Die Funktion stringToByteList
wandelt gemäß UTF-8 eine Zeichenkette in eine Liste
aus Bytes um.
FUNKTION stringToByteList: zeichenkette -> list(bytes(s, 'utf8'))
Die Funktion byteListToNat
wandelt eine Liste aus Bytes in eine natürliche Zahl um.
FUNKTION byteListToNat: {Fall 1:} len(byteListe) == 1: byteListToNat(byteListe) -> byteListe[0] {Fall 2:} len(byteListe) != 1: byteListToNat(byteListe) -> byteListe[-1] + 256*byteListToNat(byteListe[:-1])
Mit byteListe[-1]
wird hier das letzte Element von byteListe
beschrieben,
mit byteListe[:-1]
die Restliste von byteListe
, wenn man das letzte Element
weglässt.
Die Funktion byteListToNatList
wandelt eine Liste aus Bytes gemäß der Blocklänge
in eine Liste aus natürlichen Zahlen um.
FUNKTION byteListToNatList:
{Fall 1:}
len(byteListe) <= blocklaenge: byteListToNatList(byteListe, blocklaenge) ->
[byteListToNat(byteListe)]
{Fall 2:}
len(byteListe) > blocklaenge: byteListToNatList(byteListe, blocklaenge) ->
[byteListToNat(byteListe[0:blocklaenge])] +
byteListToNatList(byteListe[blocklaenge:], blocklaenge)
Mit diesen Umwandlungsfunktionen kann jetzt die Funktion zur Codierung definiert werden.
FUNKTION codierenZeichenketteZahlenliste: codierenZeichenketteZahlenliste(zeichenkette, blocklaenge) -> byteListToNatList(stringToByteList(zeichenkette), blocklaenge))
Für die Umwandlungsschritte beim Decodieren werden ebenfalls weitere Funktionen eingeführt.
Die Funktion natListToByteList
wandelt eine Liste aus natürlichen Zahlen in eine Liste
aus Bytes um.
FUNKTION natListToByteList: {Fall 1:} natListe == []: natListToByteList(natListe) -> [] {Fall 2:} natListe != []: natListToByteList(natListe) -> natToByteList(natListe[0]) + natListToByteList(natListe[1:])
Die Funktion byteListToString
wandelt eine Liste aus Bytes in eine Zeichenkette um.
FUNKTION byteListToString: byteListe -> str(bytes(byteListe), 'utf8')
Mit diesen Hilfsfunktionen kann jetzt die Funktion zur Decodierung definiert werden.
FUNKTION decodierenZahlenlisteZeichenkette: decodierenZahlenlisteZeichenkette(zahlenListe) -> byteListToString(natListToByteList(zahlenListe))
Zur Bestimmung der Blocklänge wird eine weitere Funktion benötigt.
FUNKTION blocklaenge:
{Fall 1:}
n < 256: blocklaenge(n) -> 0
{Fall 2:}
n == 256: blocklaenge(n) -> 1
{Fall 2:}
n > 256: blocklaenge(n) -> blocklaenge(n//256)+1
Funktionen zur Kombination aus RSA-Verfahren und (De-)Codierung
Jetzt können wir die bereits entwickelten Funktionen zum RSA-Verfahren und zur (De-)Codierung kombinieren.
Die Funktion verschluesseln
codiert eine Zeichenkette durch eine Zahlenliste und
verarbeitet diese mit dem RSA-Verfahren. Die Funktion wird so konzipiert, dass sie eine beliebige
Codierungsfunktion benutzen kann.
Dabei wird folgende Berechnungsvorschrift benutzt.
FUNKTION verschluesseln:
verschluesseln(zeichenkette, schluessel, codierFunktion) ->
verschluesselnListeRSA(codierFunktion(zeichenkette, schluessel), schluessel)
Entsprechend wird die Funktion entschluesseln
konzipiert.
Sie verarbeitet eine Zahlenliste mit dem RSA-Verfahren und decodiert sie anschließend, indem sie
aus der Zahlenliste eine Zeichenkette erzeugt. Die Funktion wird ebenfalls so konzipiert, dass sie eine beliebige
Decodierungsfunktion benutzen kann.
Dabei wird folgende Berechnungsvorschrift benutzt.
FUNKTION entschluesseln:
entschluesseln(zeichenkette, schluessel, decodierFunktion) ->
decodierFunktion(verschluesselnListeRSA(natListe, schluessel))
Implementierung der Funktionen
Die Datei kryptosystem_rsa.txt enthält eine Python-Implementierung der entwickelten Funktionen.
Mit diesen Funktionen können jetzt Experimente mit dem RSA-Verfahren im Sinne eines Prototypen gemacht werden:
from kryptosystem_rsa import *
# Testprogramm
n = 116063
e = 4097
d = 51137
oeffentlicherSchluessel = (e, n)
privaterSchluessel = (d, n)
s0 = 'Köln'
print(s0)
s1 = verschluesseln(s0, oeffentlicherSchluessel, codierenZeichenketteZahlenliste)
print(s1)
s2 = entschluesseln(s1, privaterSchluessel, decodierenZahlenlisteZeichenkette)
print(s2)
Einzelne Funktionen können jetzt variiert werden, oder weitere Funktionen können ergänzt werden.
Quellen
Foto: Veritas_Comet_S - Urheber: Norbert Schnitzler - Lizenz: CreativeCommons by-sa-3.0