AXMD - Di 18.02.03 08:42
Hi,
dieser Text ist von gestern, dem 17.2.2003. Ich weiß nicht, ob du inzwischen meine Ansätze verstanden hast - ansonsten glaube ich, hier sehr detailreich erklären zu können, wie die Vinègre-Chiffre (nicht nur vom Prinzip, sondern auch von der programmtechnischen Umsetzung her) funktioniert.
Ich habe mir viel Zeit für diesen Text gestern Abend genommen und hoffe, dass die Vinègre-Chiffre nun verständlich ist. Solltest du dennoch Fragen haben, kannst du mir entweder eine private Nachricht oder eine Email schreiben. Scheu dich nicht: jeder fängt mal an!
Zu allererst möchte ich eine kleine Korrektur einbringen: im Code-Abschnitt, in dem die Funktion "EncodeLine" besprochen wird, musst du statt dem "xor" ein "+" nehmen. Aber mehr dazu später.
Nun möchte ich noch einmal ganz von vorne beginnen: die Vinègre-Chiffre ist im Prinzip eine Erweiterung bzw. Verbesserung der Caesar-Chiffre. Die Caesar-Chiffre beruht auf einem sehr einfachen Prinzip: der zu kodierende Text wird zeichenweise um einen bestimmten Wert verschoben. Um das Ganze etwas zu veranschaulichen: der Text "Hallo" würde bei einer Verschiebung von 3 so aussehen: "Kdoor" - jedes Zeichen wird im Alphabet um jeweils drei Zeichen "erhöht".
Die Vinègre-Chiffre verfeinert diese Methode etwas, da die Caesar-Chiffre aufgrund der Häufigkeit der Buchstaben sehr leicht zu entschlüsseln ist (e ist im Deutschen der häufigste Buchstabe). Sehen wir uns also die Vinègre-Chiffre an: anstatt jedes Zeichen um den gleichen Wert zu erhöhen, werden die Zeichen in regelmäßigen Abständen um verschiedene Werte erhöht. Wieder ein Beispiel zur Veranschaulichung: "Hallo" würde bei einer Verschiebung von 3-2-1 zu "Kcmoq" werden. Um dem Ganzen folgen zu können: das "H" wird um 3 erhöht, also K. Das "a" wird um 2 erhöht, also "c". Das "l" wird um 1 erhöht, also m. Nun sind wir am Ende unseres Verschiebungsmusters angelangt. Was tun wir jetzt? Ganz einfach: wir beginnen einfach wieder von vorne: das zweite "l" wird also wieder um 3 erhöht und man erhält so ein "o". Das Setzen wir bis zum Ende des verschlüsselten Textes fort.
Das war soweit die Theorie, doch wie sieht's in der Praxis aus? Was wir auf jeden Fall überlegen müssen: wie soll der Text verschlüsselt werden (bzw. wieviele verschiedene aufeinanderfolgende Verschiebungen). Man könnte beispielsweise ein Array mit 8 Feldern nehmen. Das könnte so aussehen:
Quelltext
1: 2:
| const verschiebungen: Array [1..8] of Byte = (2, 2, 0, 5, 1, 9, 8, 6); |
Warum ich Byte gewählt habe, dürfte eigentlich klar sein: wenn wir Zahlen unter zehn verwenden, ist die Verwendung von Variablentypen, die größere Werte fassen können, absolut unnötig. Außerdem müssen wir beachten, dass bei einer Vinègre-Verschlüsselung keine allzu großen Werte verwendet werden dürfen. Der Grund ist eigtnlich offensichtlich: ein "normaler" String kann laut ASCII-Zeichensatz "nur" 255 verschiedene Zustände pro Zeichen annehmen. Wenn wir also zum 220. Zeichen 50 hinzuzählen, kämen wir auf 270. Und das lässt sich nicht mehr darstellen. Lassen wir die Zahlen hingegen klein, ist die Wahrscheinlichkeit, dass wir über diesen Wert 255 hinauskommen, relativ gering.
Um nun zum eigentlichen Thema zurückzukommen: unser zweites Problem ist die Zeilenanzahl des Memos. Wir müssen also jede Zeile einzeln kodieren. Wenn wir den ganzen Text kodieren würden, hätten wir ein Problem: der Wagenrücklauf #13 würde ebenfalls verschlüsselt werden - dann hätten wir nur noch eine Zeile verschlüsselten Text (ausgenommen natürlich, es ergibt sich durch Zufall ein Wagenrücklauf in der Verschlüsselung).
Nun wollen wir aus unseren beiden Ansatzpunkten ein Konzept aufbauen. Ich möchte nun das Konzept kurz wiedergeben und danach auf die einzelnen Punkte genauer eingehen, um die ganze Sache etwas verständlicher zu machen:
Der Text wird in einer Prozedur in einzelne Zeilen zerlegt. Diese Zeilen werden gesondert kodiert und am Ende wieder zusammengesetzt. Die Kodierprozedur wird wie folgt ablaufen: in einer for-Schleife wird der String (die Zeile) zeichenweise kodiert; sprich: das Muster (in unserem Fall: 2, 2, 0, 5, 1, 9, 8, 6) wird zum String addiert. Diese Vorgehensweise wurde bereits weiter oben besprochen, noch nicht erwähnt wurde allerdings, wie die programmtechnische Umsetzung dazu aussieht.
Das ist zwar nicht schwierig, aber ich möchte trotzdem einen kleinen Exkurs wagen: in unserem Beispiel wollen wir den String Zeichen für Zeichen kodieren - das heißt, dass jedes Zeichen einen Index (eine Position) im String hat. Das Zeichen "o" im Wort "Hallo" hat beispielsweise den Index 5. Wenn man nun ein einfaches Muster wie 3-2-1 verwendet, so wird jeder (nach einer mehr oder weniger kurzen Überlegungszeit) erkennen, dass das fünfte Zeichen im String "Hallo" mit 2 addiert werden muss, die zweite Position in unserem Muster. Im Kopf sieht das ganze überschlagsmäßig so aus: wenn du die Zeichenposition (5) durch die Musterlänge 3 dividierst, erhältst du (wie in der Volksschule) 1 mal, 2 Rest. Dieses "2 Rest" entspricht (zufälligerweise) genau unserem Ergebnis, nämlich 2.
In Delphi (auch in anderen Programmiersprachen) gibt es einen Operanden, der nur den Rest einer Division zurückgibt. Dieser Operand heißt mod. Ein Beispiel: 15 dividiert durch 4 ergibt 3 mal und 3 Rest. 15 mod 4 ist also 3.
Diesen kleinen Exkurs habe ich rein verständnishalber gemacht, denn wenn dieser kleine Teil nicht klar ist, macht die ganze Verschlüsselung Probleme.
Nun aber weiter mit unserem Konzept: der erste Teil besteht aus der Zerlegung des gesamten Memo-Textes. Das ist über eine for-Schleife am einfachsten möglich:
Quelltext
1: 2: 3: 4: 5: 6: 7:
| procedure Zeilen_einzeln_kodieren; var i: Integer; begin for i := 0 to Memo1.Lines.Count - 1 do begin Memo1.Lines[i] := Vinegre_Chiffre(Memo1.Lines[i]); end; |
Das wäre die kompakteste Schreibweise. Die Funktion "Vinegre_Chiffre" werden wir gleich im Detail besprechen; für diesen Ansatz reicht der Name der Funktion. Auch ein alter Bekannter kreuzt wieder unseren Weg: der List-Index 0. Die Zählung der Zeilen beginnt mit 0 und endet mit Zeilenzahl - 1. Aber das würde jetzt wirklich zu weit ausschweifen.
In dieser Prozedur wird natürlich vorausgesetzt, dass Memo1 existiert. Sollte Memo1 nicht existieren, sondern ein beliebiges Memofeld übergeben werden können, müsste man im Prinzip nur einen Parameter einbauen:
Quelltext
1: 2: 3: 4: 5: 6: 7:
| procedure Zeilen_einzeln_kodieren(Memo: TMemo); var i: Integer; begin for i := 0 to Memo.Lines.Count - 1 do begin Memo.Lines[i] := Vinegre_Chiffre(Memo.Lines[i]); end; |
Nun zur eigentlichen Chiffrierung: zu allererst wollen wir ein Array deklarieren, das unsere altbekannten Nummern enthält:
Quelltext
1: 2:
| const verschiebungen: Array [1..8] of Byte = (2, 2, 0, 5, 1, 9, 8, 6); |
Natürlich kannst du auch andere Ziffern verwenden (oder eine größere Zahlenvielfalt), aber ich denke, dass ein Beispiel zu Ende der Erklärung nicht schlecht wäre. Und dafür sind allzu große Arrays nicht geeigent (man verliert leicht die Übersicht).
Nun haben wir also bereits unser Array; was noch fehlt ist die eigentliche Verschlüsselung. Da wir - wie bereits erwähnt - zeichenweise kodieren, muss der String in Einzelteile zerlegt werden. Am Besten kann das über eine for-Schleife gemacht werden.
im nun folgenden Beispiel möchte ich die Caesar-Chiffre zeigen. Das weicht zwar ein klein wenig vom Thema ab, sollte aber für das Verständnis der Vinègre-Chiffre nicht von Nachteil sein. Der Code sei fürs Erste einmal in den Raum gestellt:
Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10:
| function Caesar(s: String): String; var i: Integer; dummy: String; begin dummy := s; for i := 1 to Length(s) do dummy[i] := Chr(Ord(dummy[i]) + 3); Result := dummy; end; |
Das "3" soll hier nicht verwirrend wirken: wir nehmen einfach einmal an, dass wir in dieser Caesar-Verschlüsselung eine Verschiebung von 3 wählen.
Um nun genauer auf den Ablauf der Prozedur einzugehen: die Variable dummy dient als Unterstützung, um nicht auf die Original-Variable zugreifen zu müssen (s). Das ist vor allem bei DLLs wichtig - aber das soll hier nicht erwähnt werden. In der for-Schleife wird nun der i-ten Position der Variable dummy der Wert Chr(Ord(dummy[i]) + 3) zugewiesen. Da dieser Ausdruck etwas kompliziert ist, wollen wir ihn von innen nach außen auflösen:
dummy[i] ist das Zeichen an der aktuellen Position (i). Vorüberlegung: wie können wir zu diesem Zeichen 3 hinzuzählen? Ord und Chr sind die Funktionen, die uns dabei helfen können. Ord liefert die Position eines Zeichens in der ASCII-Tabelle. Das können wir hier anwenden: Ord(dummy[i]) liefert die Position des Zeichens an der aktuellen Position in der ASCII-Tabelle. Nun zählen wir 3 hinzu (+ 3) und verwandeln das Ganze wieder zurück in ein Zeichen, das dem Zeichen an der Position Ord(dummy[i]) + 3 entspricht.
Der Befehl Chr(Ord(dummy[i]) + 3) liefert also unser verschlüsseltes Zeichen. Das müssen wir nur noch zuweisen. Und wohin? Klar: dorthin, wo wir es hergenommen haben (dummy[i]): das i-te Zeichen unseres Strings.
Am Ende wird nur noch als Ergebnis unserer Prozedur dummy zurückgegeben (Result := dummy).
Nun haben wir die Caesar-Chiffre (hoffentlich) verstanden und können gleich direkt zur Vinègre-Chiffre übergehen: im Prinzip müssen wir nur einen Teil austauschen: da wir mit variabler Verscheibung arbeiten, müssen wir unser " + 3" durch "verschiebung[i mod 8]" ersetzen. Der Grunf wurde vorhin bereits besprochen: Position mod Musterlänge ergibt die Position des Musters, die addiert werden soll. Nun lautet unser Code:
Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10:
| function Vinegre_Chiffre(s: String): String; var i: Integer; dummy: String; begin dummy := s; for i := 1 to Length(s) do dummy[i] := Chr(Ord(dummy[i]) + verschiebung[i mod 8]); Result := dummy; end; |
Damit hätten wir die Vinègre-Chiffre komplett aufgelöst und können uns noch einigen Feinheiten widmen: im Prinzip könnten wir auch in die Funktion selbst das Muster einbauen. Ein zusätzlicher Parameter wie zum Beispiel "Muster: Array of Byte" könnte uns die Deklaration des Konstanten-Arrays ersparen.
Um nun einen kompletten Überblick über die Verschlüsselung gewinnen zu können, werde ich die "ganze" Unit einblenden:
Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31:
| Unit Vinegre;
interface
uses StdCtrls, SysUtils;
function Vinegre_Chiffre(s: String): String; procedure Zeilen_einzeln_kodieren(Memo: TMemo);
implementation
function Vinegre_Chiffre(s: String): String; var i: Integer; dummy: String; begin dummy := s; for i := 1 to Length(s) do dummy[i] := Chr(Ord(dummy[i]) + verschiebung[i mod 8]); Result := dummy; end;
procedure Zeilen_einzeln_kodieren(Memo: TMemo); var i: Integer; begin for i := 0 to Memo.Lines.Count - 1 do begin Memo.Lines[i] := Vinegre_Chiffre(Memo.Lines[i]); end;
end. |
Achtung: dieser Code setzt voraus, dass diese Unit separat vom anderen Programm erstellt und dann eingebunden wird. Das Hauptprogramm könnte dann beispielsweise so lauten:
Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27:
| Unit Unit1;
interface
uses Windows, SysUtils, Graphics, Forms, Dialogs, Controls, Messages, StdCtrls {...}, Vinegre {nicht vergessen!!!};
type TForm1 = class (TForm) Button1: TButton; Memo1: TMemo; public procedure Button1Click(Sender: TObject); end;
var Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject); begin Zeilen_einzeln_kodieren(Memo1); end;
end. |
Damit wäre die Verschlüsselung vollständig. Der letzte Aufruf ("Zeilen_einzeln_kodieren(Memo1)") dürfte klar sein: der Inhalt des Memos wird kodiert. Übergabeparameter ist hierbei das Memo1 von Form1.
Was nun noch "fehlt" ist eine Entschlüsselungsprozedur. Die könnte man sich zwar eigentlich schenken, aber sie soll nicht unerwähnt bleiben: man muss nur das "+" in der Funktion "Vinegre_Chiffre" durch ein "-" ersetzen.
Man könnte allerdings auch in der Funktion selbst durch einen Parameter diese Funktionalität hervorrufen. Damit könnte man sich dann etwas Zeitaufwand sparen. Hier der Vollständigkeit halber die Implementation:
Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14:
| function Vinegre_Chiffre(s: String; Encode: Boolean): String; var i: Integer; dummy: String; begin dummy := s; for i := 1 to Length(s) do begin if Encode then dummy[i] := Chr(Ord(dummy[i]) + verschiebung[i mod 8]) else dummy[i] := Chr(Ord(dummy[i]) - verschiebung[i mod 8]) end; Result := dummy; end; |
Wenn man nun kodieren will, ruft man die Prozedur mit dem zweiten Parameter als true auf; will man dekodieren: false.
Abschließende Bemerkungen:
1.) Natürlich könnte man den Quelltext so umprogrammieren, dass man einen zweiten Parameter übergeben kann (ein Memo, in das die kodierte Fassung des Textes gespeichert wird).
2.) Die letzten 13000 Zeichen wurden ohne Zuhilfenahme von Delphi oder einem Delphi-Buch geschrieben. Dieser Text wurde rein aus dem Stehgreif geschrieben und besitzt keinen Anspruch auf Korrektheit. Möglicherweise besitzen sogar die Prozeduren und Funktionen an sich logische Fehler und können (rein kopiert) nicht in Delphi kompiliert werden.
In diesem Sinne möchte ich noch kurz etwas anhängen: sollte irgendjemand Fehler in diesem Text finden, ist er herzlich eingeladen, mir diese mitzuteilen. Aber ich denke, dass diese (guten) anderthalb Stunden Arbeit nicht allzu viele Fehler (mal abgesehen von Rechtschreib- und Grammatikfehlern) beherbergen dürften.
Ich hoffe, alle um ein wichtiges Kapitel des Verschlüsselung bereichert zu haben,
So long,
AXMD