Dieser Artikel versucht zu erklären, wie beliebige Daten im Rauschen von Bildern versteckt werden können.
Es gibt nur einen sicheren Ort für private Daten: Den Ort, an dem niemand danach sucht.
Eine mit Algorithmen wie PGP verschlüsselte Datei ist nicht lesbar, aber jeder sieht
sofort, dass etwas versteckt wird. Wäre es nicht gut, wenn jeder Deine verschlüsselten
Dateien öffnen könnte, und anstelle der Geheimnisse unscharfe Fotos vom letzten
Urlaub sehen würde? Kaum jemand würde nach Teilen verschlüsselter Nachrichten
in den Pixel-Werten suchen.
Um zu sehen, wie die Anwendung arbeitet, solltest Du den Quellcode anschauen.
Allgemein dargestellt werden Nachrichten so versteckt:
Und so wieder extrahiert:
Zuerst schreibt das Programm die Länge der Nachricht ins erste Pixel. Wir brauchen diesen Wert später, um die Nachricht wieder zu extrahieren.
messageLength = (Int32)messageStream.Length; //hier werden eventuelle Konflikte abgefangen //... //Länge der Nachricht ins erste Pixel schreiben int colorValue = messageLength; int red = colorValue >> 2; colorValue -= red << 2; int green = colorValue >> 1; int blue = colorValue - (green << 1); pixelColor = Color.FromArgb(red, green, blue); bitmap.SetPixel(0,0, pixelColor);
Anschließend liest es ein Byte aus dem Schlüssel-Stream, um die Position des nächsten zu verwendenden Pixels zu berechnen:
//mit dem zweiten Pixel anfangen Point pixelPosition = new Point(1,0); //Für jedes Byte der Nachricht for(int messageIndex=0; messageIndex<messageLength; messageIndex++){ //repeat the key, if it is shorter than the message if(keyStream.Position == keyStream.Length){ keyStream.Seek(0, SeekOrigin.Begin); } //Nächste Pixel-Anzahl aus dem Schlüssel lesen, "1" für 0 verwenden currentStepWidth = keyStream.ReadByte() + 1; //Zeile wechseln, falls aktuelle Schrittweite den Rand überschreitet while(currentStepWidth > bitmapWidth){ currentStepWidth -= bitmapWidth; pixelPosition.Y++; } //Position horizontal versetzen if((bitmapWidth - pixelPosition.X) < currentStepWidth){ pixelPosition.X = currentStepWidth - (bitmapWidth - pixelPosition.X); pixelPosition.Y++; }else{ pixelPosition.X += currentStepWidth; }
Jetzt lesen wir das Pixel und ersetzen eine Farb-Komponente mit dem Nachrichten-Byte (oder alle Komponenten, wenn schwarz/weisses Rauschen entstehen soll):
//Farbe des "sauberen" Pixels lesen pixelColor = bitmap.GetPixel(pixelPosition.X, pixelPosition.Y); //Um etwas Verwirrung hinzuzufügen, wird das Byte mit der aktuellen Schrittweite kombiniert int currentByte = messageStream.ReadByte() ^ currentKeyByte; if(useGrayscale){ pixelColor = Color.FromArgb(currentByte, currentByte, currentByte); }else{ //Eine Farb-Komponente ersetzen SetColorComponent(ref pixelColor, currentColorComponent, currentByte); //Für das nächste Byte eine andere Komponente verwenden currentColorComponent = (currentColorComponent==2) ? 0 : (currentColorComponent+1); } } //Ende "for" - weiter zum nächsten Byte
Wenn das Programm eine versteckte Nachricht ausliest, liest es die Länge der Nachricht und die Farb-Komponenten, anstatt sie in die Pixel zu schreiben. So wird die Länge aus dem ersten Pixel gelesen:
pixelColor = bitmap.GetPixel(0,0); messageLength = (pixelColor.R << 2) + (pixelColor.G << 1) + pixelColor.B; messageStream = new MemoryStream(messageLength);
Die Pixel-Koordinaten werden genauso bestimmt wie bereits beschrieben. Anschließend wird das versteckte Byte aus dem Farbwert gelesen:
//Farbe des geänderten Pixels lesen pixelColor = bitmap.GetPixel(pixelPosition.X, pixelPosition.Y); //Das ersteckte Nachrichten-Byte aus der Farbe extrahieren byte foundByte = (byte)(currentKeyByte ^ GetColorComponent(pixelColor, currentColorComponent)); messageStream.WriteByte(foundByte); //Für das nächste Byte eine andere Komponente verwenden currentColorComponent = (currentColorComponent==2) ? 0 : (currentColorComponent+1); } //Ende "for" - weiter zum nächsten Byte