![]() |
||||||||||||
|
|
|||||||||||
Worum geht es?Dieser Artikel erweitert die Anwendung aus Teil 1 um drei Funktionen:
Mehrere SchlüsselSymmetrische Schlüssel bringen ein zentrales Problem mit sich: Wie übermittelt man sie?
Jeder, der den Schlüssel in die Finger bekommt, kann jede einer Nachrichten entschlüsseln.
Also müssen wir es schwierig machen, den Schlüssel überhaupt zu entwenden.
Eine Möglichkeit wäre, die Schlüssel relativ kurz zu halten und gar nicht zu speichern.
Aber ein kurzer Schlüssel hinterlässt einen Fußabdruck in der verschlüsselten Datei,
in unserem Fall wäre dieser als regelmäßiges Rauschmuster zu sehen. Ein bestimmtes Kennwort gehört immer zu seiner Schlüssel-Datei:
public struct FilePasswordPair{
public String fileName;
public String password;
public FilePasswordPair(String fileName, String password){
this.fileName = fileName;
this.password = password;
}
}
Bevor wir die Schlüssel verwenden können, müssen die Schlüssel/Kennwort-Paare kombiniert werden.
Hier ist das gleiche in C#
public static MemoryStream CreateKeyStream(FilePasswordPair key){
FileStream fileStream = new FileStream(key.fileName, FileMode.Open);
MemoryStream resultStream = new MemoryStream();
int passwordIndex = 0;
int currentByte = 0;
while( (currentByte = fileStream.ReadByte()) >= 0 ){
//Schlüssel-Byte mit entsprechendem Kennwort-Byte kombinieren
currentByte = currentByte ^ key.password[passwordIndex];
//Ergebnis zum Schlüssel-Stream hinzufügen
resultStream.WriteByte((byte)currentByte);
//Weiter zum nächsten (ggf. zurück zum ersten) Zeichen
passwordIndex++;
if(passwordIndex == key.password.Length){
passwordIndex = 0;
}
}
fileStream.Close();
resultStream.Seek(0, SeekOrigin.Begin);
return resultStream;
}
Der entstandene Stream ist genauso lang wie die Schlüssel-Datei, wird aber niemals irgendwo gespeichert.
Natürlich ist diese Verschlüsselung nicht wirklich sicher, darum werden wir mehr Schlüssel und
mehr Kennwörter verwenden. Bevor der Empfänger die Nachricht extrahieren kann, muss er
alle Schlüssel besitzen und alle Kennwörter wissen. Wenn es jemandem gelingt, einen Schlüssel zu
kopieren oder zwei Kennwörter zu erraten, besteht noch kein Grund zu Panik, solange die anderen Schlüssel noch
sicher sind.
private static MemoryStream GetKeyStream(FilePasswordPair[] keys){
//Schlüssel und ihre Kennwörter XOR-kombinieren
MemoryStream[] keyStreams = new MemoryStream[keys.Length];
for(int n=0; n<keys.Length; n++){
keyStreams[n] = CreateKeyStream(keys[n]);
}
//Leeren Stream initialisieren
MemoryStream resultKeyStream = new MemoryStream();
//Länge des längsten Stream finden
long maxLength = 0;
foreach(MemoryStream stream in keyStreams){
if( stream.Length > maxLength ){ maxLength = stream.Length; }
}
int readByte = 0;
for(long n=0; n<=maxLength; n++){
for(int streamIndex=0; streamIndex<keyStreams.Length; streamIndex++){
if(keyStreams[streamIndex] != null){
readByte = keyStreams[streamIndex].ReadByte();
if(readByte < 0){
//Stream zuende - Datei schließen
//Der letzte Lauf (n==maxLength) schließt den letzten Stream
keyStreams[streamIndex].Close();
keyStreams[streamIndex] = null;
}else{
//Ein Byte in den Ergebnis-Stream kopieren
resultKeyStream.WriteByte( (byte)readByte );
}
}
}
}
return resultKeyStream;
}
Wie man sieht, müssen wir den Algorithmus nicht weiter ändern. Wir rufen einfach Die Information über mehrere Bilder verteilenWenn Du richtig paranoid bist, wirst Du Deiner Mailbox nicht vertrauen. Natürlich wirst Du auch
nicht dem Briefträger vertrauen. Das bedeutet, dass Du es nicht wagst, die Träger-Bitmap in
einem Stück zu verschicken.
Du kannst jedes Bild in einer separaten eMail verschicken, in verschiedenen Postfächern hinterlegen,
oder auf verschiedenen Festplatten speichern.
Die Oberfläche erlaubt es, mehrere Träger-Bitmaps genauso wie Schlüssel-Dateien auszuwählen.
Die Auswahl wird in einem Array vom Typ
public struct CarrierImage{
//Name des "sauberen" Bildes
public String sourceFileName;
//Dateiname für das neue Bild
public String resultFileName;
//Breite * Höhe
public long countPixels;
//Farbiges (false) oder monochromes (true) Rauschen in diesem Bild erzeugen
public bool useGrayscale;
//Anzahl der Bytes, die in diesem Bild versteckt werden - wird von HideOrExtract() gesetzt
public long messageBytesToHide;
public CarrierImage(String sourceFileName, String resultFileName, long countPixels, bool useGrayscale){
this.sourceFileName = sourceFileName;
this.resultFileName = resultFileName;
this.countPixels = countPixels;
this.useGrayscale = useGrayscale;
this.messageBytesToHide = 0;
}
}
Größere Bilder können mehr Bytes (in mehr Pixeln) fassen als kleinere Bilder. Diese Beispiel-Anwendung verwendet die einfachste mögliche Verteilung:
//Anzahl der Bytes berechnen, für die dieses Bild verwendet wird
for(int n=0; n<imageFiles.Length; n++){
//pixels = Anzahl der Pixel im Bild / Anzahl verfügbarer Pixel insgesamt
float pixels = (float)imageFiles[n].countPixels / (float)countPixels;
//Das Bild wird (Länge der Nachricht * pixels) Bytes verstecken
imageFiles[n].messageBytesToHide = (long)Math.Ceiling( (float)messageLength * pixels );
}
Jetzt beginnen wir mit der ersten Träger-Bitmap, laufen durch die Nachricht, verstecken eine bestimmte Anzahl an Bytes, wechseln zur zweiten Träger-Bitmap, und immer so weiter.
//Aktuelle Position im Träger-Bitmap
//Start bei 1, weil (0,0) die Länge der Nachricht enthält
Point pixelPosition = new Point(1,0);
//Anzahl der Bytes, die in diesem Bild bereits versteckt wurden
int countBytesInCurrentImage = 0;
//Index des aktuell verwendeten Bildes
int indexBitmaps = 0;
//Nachricht durchlaufen und jedes Byte verstecken
for(int messageIndex=0; messageIndex<messageLength; messageIndex++){
//Position des nächsten Pixels berechnen
//...
//Weiter zur nächsten Bitmap
if(countBytesInCurrentImage == imageFiles[indexBitmaps].messageBytesToHide){
indexBitmaps++;
pixelPosition.Y = 0;
countBytesInCurrentImage = 0;
bitmapWidth = bitmaps[indexBitmaps].Width-1;
bitmapHeight = bitmaps[indexBitmaps].Height-1;
if(pixelPosition.X > bitmapWidth){ pixelPosition.X = 0; }
}
//Ein Byte verstecken oder extrahieren
//...
countBytesInCurrentImage++;
}
Zum Schluss müssen wir die neuen Bilder speichern. Jedes Bild kann in einem anderen Format gespeichert werden (bmp, tif oder png). Das neue Format hat nichts mit dem Format der Original-Datei zu tun. Das heisst, Du kannst eine BMP-, zwei PNG und eine TIFF-Datei als Träger-Bilder auswählen, und die Ergebnisse in drei TIFF- und einer PNG-Datei speichern.
//...
for(indexBitmaps=0; indexBitmaps<bitmaps.Length; indexBitmaps++){
if( ! extract ){ //Extrations-Modus ändert keine Bilder
//Entstandenes Bild speichern und Original schließen
SaveBitmap( bitmaps[indexBitmaps], imageFiles[indexBitmaps].resultFileName );
}
}
//...
private static void SaveBitmap(Bitmap bitmap, String fileName){
String fileNameLower = fileName.ToLower();
//Format anhand der Erweiterung bestimmen
System.Drawing.Imaging.ImageFormat format = System.Drawing.Imaging.ImageFormat.Bmp;
if((fileNameLower.EndsWith("tif"))||(fileNameLower.EndsWith("tiff"))){
format = System.Drawing.Imaging.ImageFormat.Tiff;
}else if(fileNameLower.EndsWith("png")){
format = System.Drawing.Imaging.ImageFormat.Png;
}
//Bitmap kopieren
Image img = new Bitmap(bitmap);
//Datei schließen
bitmap.Dispose();
//Neue Bitmap speichern
img.Save(fileName, format);
img.Dispose();
}
|