Einführung
Willkommen.
Vor nicht allzu langer Zeit, während meines Studiums an der Universität, hatte ich ein Projekt in der Disziplin „Programmierte Methoden der Informationssicherheit“. Die Aufgabe bestand darin, ein Programm zu erstellen, das Nachrichten in GIF-Dateien einbettet. Ich entschied mich, dies in Java zu machen.
In diesem Artikel werde ich einige theoretische Aspekte beschreiben und erläutern, wie dieses kleine Programm entwickelt wurde.
Theoretischer Teil
GIF-Format
GIF (engl. Graphics Interchange Format) ist ein Format zur Speicherung von Grafikbildern, das komprimierte Daten ohne Qualitätsverlust im Format von bis zu 256 Farben speichern kann. Dieses Format wurde 1987 (GIF87a) von CompuServe entwickelt, um Rasterbilder über Netzwerke zu übertragen. 1989 wurde das Format modifiziert (GIF89a) und erhielt Unterstützung für Transparenz und Animation.
GIF-Dateien haben eine blockartige Struktur. Diese Datenblöcke haben stets eine feste Länge (oder sie hängt von bestimmten Flags ab), sodass es nahezu unmöglich ist, sich zu irren, wo welcher Block sich befindet. Die Struktur eines einfachsten, nicht animierten GIF-Bildes im GIF89a-Format ist:

Von allen Blöcken der Struktur interessieren uns in diesem Fall der globale Palettenblock und die Parameter, die für die Palette verantwortlich sind:
CT— Existenz einer globalen Palette. Wenn dieses Flag gesetzt ist, sollte unmittelbar nach dem Deskriptor des logischen Bildschirms die globale Palette beginnen.Size— Größe der Palette und die Anzahl der Farben des Bildes. Werte dieses Parameters:
Size
Anzahl der Farben
Größe der Palette in Byte
7
256
768
6
128
384
5
64
192
4
32
96
3
16
48
2
8
24
1
4
12
0
2
6
Verschlüsselungsmethoden
Als Methoden zur Verschlüsselung von Nachrichten in Bilddateien werden verwendet:
- LSB-Methode (Least Significant Bit, niederwertigster Bit)
- Palette Ergänzungs-Methode
LSB-Methode — Ein verbreiteter Steganographiemethode. Dabei werden die letzten signifikanten Bits im Container (in unserem Fall die Bytes der globalen Palette) durch die Bits der versteckten Nachricht ersetzt.
Bei diesem Verfahren werden die letzten beiden Bits der Bytes der globalen Palette verwendet. Das bedeutet, dass sich bei einem 24-Bit-Bild, in dem die Farbpalette aus drei Bytes für Rot, Blau und Grün besteht, nach der Einbettung der Nachricht jede Farbkomponente um maximal 3/255 Gradationen ändern wird. Eine solche Veränderung wird erstens für das menschliche Auge unsichtbar oder schwer erkennbar sein und zweitens nicht auf minderwertigen Ausgabegeräten wahrnehmbar sein.
Die Menge an Informationen hängt direkt von der Größe der Farbpalette des Bildes ab. Da die maximale Größe der Palette 256 Farben beträgt und bei der Eintragung von zwei Bits der Nachricht in jede Farbkomponente die maximale Länge der Nachricht (bei maximaler Palette im Bild) 192 Bytes beträgt. Nach der Einbettung der Nachricht in das Bild verändert sich die Dateigröße nicht.
Palette-Expansion-Methode, der nur für GIF-Strukturen funktioniert. Er wird am effektivsten bei Bildern mit kleinen Farbpaletten sein. Sein Wesen besteht darin, die Palette zu vergrößern, wodurch zusätzlicher Platz für die Speicherung der erforderlichen Bytes an der Stelle der Farbbytes geschaffen wird. Wenn man bedenkt, dass die minimale Palettegröße 2 Farben (6 Bytes) beträgt, kann die maximal einbettbare Nachricht 256×3–6=762 Bytes betragen. Nachteil — niedrige Kryptosicherheit; die eingebettete Nachricht kann mit jedem Texteditor gelesen werden, wenn die Nachricht keiner zusätzlichen Verschlüsselung unterzogen wurde.
Praktischer Teil
Programmentwicklung
Alle notwendigen Werkzeuge zur Implementierung der Verschlüsselungs- und Entschlüsselungsalgorithmen befinden sich im Paket com.tsarik.steganography. Dieses Paket enthält die Schnittstelle Encryptor mit den Methoden encrypt und decrypt, die Klasse Binary, die die Arbeit mit Bitarrays ermöglicht, sowie Ausnahmeklassen UnableToEncryptException und UnableToDecryptException, die in den Methoden der Schnittstelle verwendet werden müssen Encryptor im Falle von Fehlern beim Kodieren und Dekodieren.
Hauptpaket des Programms com.tsarik.programs.gifed wird eine ausführbare Programmklasse mit einer statischen Methode umfassen main, die das Programm ausführt; eine Klasse, die die Programmparameter speichert; und Pakete mit anderen Klassen.
Die Implementierung der Algorithmen wird im Paket präsentiert com.tsarik.programs.gifed.gif Klassen GIFEncryptorByLSBMethod und GIFEncryptorByPaletteExtensionMethod. Beide Klassen werden das Interface implementieren Encryptor.
Basierend auf der Struktur des GIF-Formats kann ein allgemeiner Algorithmus zur Einbettung von Nachrichten in die Bildpalette erstellt werden:

Um das Vorhandensein einer Nachricht im Bild zu bestimmen, sollte zu Beginn der Nachricht eine bestimmte Bitsequenz hinzugefügt werden, die der Decoder zunächst liest und auf Korrektheit überprüft. Stimmt sie nicht überein, gilt die Nachricht als im Bild nicht verborgen. Anschließend muss die Länge der Nachricht angegeben werden. Danach folgt der eigentliche Nachrichtentext.
Klassendiagramm der gesamten Anwendung:

Implementierung des Programms
Die Implementierung des gesamten Programms kann in zwei Komponenten unterteilt werden: die Implementierung der Verschlüsselungs- und Entschlüsselungsmethoden des Interfaces Encryptor, in den Klassen GIFEncryptorByLSBMethod und GIFEncryptorByPaletteExtensionMethod, und die Implementierung der Benutzeroberfläche.
Betrachten wir die Klasse GIFEncryptorByLSBMethod.

Felder firstLSBit und secondLSBit enthält die Bitnummern jedes Bytes des Bildes, in die die Nachricht geschrieben und aus der gelesen werden soll. Feld checkSequence speichert die Prüfziffernfolge für die Erkennung der eingebetteten Nachricht. Statische Methode getEncryptingFileParameters gibt die Parameter der angegebenen Datei und Merkmale der potenziellen Nachricht zurück.
Algorithmus der Methode encrypt der Klasse GIFEncryptorByLSBMethod:

Und sein Code:
@Override
public void encrypt(File in, File out, String text) throws UnableToEncodeException, NullPointerException, IOException {
if (in == null) {
throw new NullPointerException("Die Eingabedatei ist null");
}
if (out == null) {
throw new NullPointerException("Die Ausgabedatei ist null");
}
if (text == null) {
throw new NullPointerException("Der Text ist null");
}
// Bytes aus der Eingabedatei lesen
byte[] bytes = new byte[(int)in.length()];
InputStream is = new FileInputStream(in);
is.read(bytes);
is.close();
// Format überprüfen
if (!(new String(bytes, 0, 6)).equals("GIF89a")) {
throw new UnableToEncodeException("Eingabedatei hat das falsche GIF-Format");
}
// Palette-Größe-Eigenschaft aus den ersten drei Bits im zehnten Byte der Datei lesen
byte[] b10 = Binary.toBitArray(bytes[10]);
byte bsize = Binary.toByte(new byte[] {b10[0], b10[1], b10[2]});
// Farbanzahl und mögliche Nachrichtenlänge berechnen
int bOrigColorCount = (int)Math.pow(2, bsize+1);
int possibleMessageLength = bOrigColorCount * 3 / 4;
int possibleTextLength = possibleMessageLength - 2; // ein Byte für die Prüfziffer und ein Byte für die Nachrichtenlänge
if (possibleTextLength < text.length()) {
throw new UnableToEncodeException("Der Text ist zu groß");
}
int n = 13;
// Prüfsequenz schreiben
for (int i = 0; i < checkSequence.length / 2; i++) {
byte[] ba = Binary.toBitArray(bytes[n]);
ba[firstLSBit] = checkSequence[2*i];
ba[secondLSBit] = checkSequence[2*i+1];
bytes[n] = Binary.toByte(ba);
n++;
}
// Textlänge schreiben
byte[] cl = Binary.toBitArray((byte)text.length());
for (int i = 0; i < cl.length / 2; i++) {
byte[] ba = Binary.toBitArray(bytes[n]);
ba[firstLSBit] = cl[2*i];
ba[secondLSBit] = cl[2*i+1];
bytes[n] = Binary.toByte(ba);
n++;
}
// Nachricht schreiben
byte[] textBytes = text.getBytes();
for (int i = 0; i < textBytes.length; i++) {
byte[] c = Binary.toBitArray(textBytes[i]);
for (int ci = 0; ci < c.length / 2; ci++) {
byte[] ba = Binary.toBitArray(bytes[n]);
ba[firstLSBit] = c[2*ci];
ba[secondLSBit] = c[2*ci+1];
bytes[n] = Binary.toByte(ba);
n++;
}
}
// Ausgabedatei schreiben
OutputStream os = new FileOutputStream(out);
os.write(bytes);
os.close();
}
Algorithmus und Quellcode der Methode decrypt der Klasse GIFEncryptorByLSBMethod:

@Override
public String decrypt(File in) throws UnableToDecodeException, NullPointerException, IOException {
if (in == null) {
throw new NullPointerException("Die Eingabedatei ist null");
}
// Bytes aus der Eingabedatei lesen
byte[] bytes = new byte[(int)in.length()];
InputStream is = new FileInputStream(in);
is.read(bytes);
is.close();
// Format überprüfen
if (!(new String(bytes, 0, 6)).equals("GIF89a")) {
throw new UnableToDecodeException("Die Eingabedatei hat das falsche GIF-Format");
}
// Palettengröße-Property aus den ersten drei Bits im 10. Byte der Datei lesen
byte[] b10 = Binary.toBitArray(bytes[10]);
byte bsize = Binary.toByte(new byte[] {b10[0], b10[1], b10[2]});
// Farbanzahl und mögliche Nachrichtenlänge berechnen
int bOrigColorCount = (int)Math.pow(2, bsize+1);
int possibleMessageLength = bOrigColorCount*3/4;
int possibleTextLength = possibleMessageLength-2; // ein Byte für die Kontrolle und ein Byte für die Nachrichtenlänge
int n = 13;
// Prüfziffernfolge lesen
byte[] csBits = new byte[checkSequence.length];
for (int i = 0; i < 4; i++) {
byte[] ba = Binary.toBitArray(bytes[n]);
csBits[2*i] = ba[firstLSBit];
csBits[2*i+1] = ba[secondLSBit];
n++;
}
byte cs = Binary.toByte(csBits);
if (cs != Binary.toByte(checkSequence)) {
throw new UnableToDecodeException("Es gibt keine verschlüsselte Nachricht im Bild (Prüfziffernfolge ist inkorrekt)");
}
// Textlänge lesen
byte[] cl = new byte[8];
for (int i = 0; i < 4; i++) {
byte[] ba = Binary.toBitArray(bytes[n]);
cl[2*i] = ba[firstLSBit];
cl[2*i+1] = ba[secondLSBit];
n++;
}
byte textLength = Binary.toByte(cl);
if (textLength < 0) {
throw new UnableToDecodeException("Die entschlüsselte Textlänge ist kleiner als 0");
}
if (possibleTextLength < textLength) {
throw new UnableToDecodeException("Es gibt keine Nachrichten (Die entschlüsselte Nachrichtenlänge (" + textLength + ") ist kleiner als die mögliche Nachrichtenlänge (" + possibleTextLength + "))");
}
// Textbits lesen und Textbytes erstellen
byte[] bt = new byte[textLength];
for (int i = 0; i < bt.length; i++) {
byte[] bc = new byte[8];
for (int bci = 0; bci < bc.length/2; bci++) {
byte[] ba = Binary.toBitArray(bytes[n]);
bc[2*bci] = ba[firstLSBit];
bc[2*bci+1] = ba[secondLSBit];
n++;
}
bt[i] = Binary.toByte(bc);
}
return new String(bt);
}
Die Implementierung der Klasse GIFEncryptorByPaletteExtensionMethod ist ähnlich, unterscheidet sich jedoch in der Methode zum Speichern/Lesen von Informationen.
In der Klasse MainFrame sind die Methoden-„Wrapper“ beschrieben: encryptImage(Encryptor encryptor) und decryptImage(Encryptor encryptor), die die Ergebnisse der Methoden des Interfaces verarbeiten Encryptor und mit dem Benutzer interagieren, d. h. sie öffnen einen Dialog zum Auswählen von Dateien, zeigen Fehlermeldungen an usw.; sowie weitere Methoden: openImage(), die dem Benutzer die Auswahl eines Bildes ermöglicht, exit(), die den Ausgang aus der Anwendung ermöglicht. Diese Methoden werden aus Aktionendes entsprechenden Menüpunkts aufgerufen. In dieser Klasse sind zusätzlich Hilfsmethoden implementiert: createComponents() — Erstellung der Formular-Components, loadImageFile(File f) — Laden des Bildes aus einer Datei in eine spezielle Komponente. Die Implementierung der Klasse GIFEncryptorByPaletteExtensionMethod entspricht der Implementierung der Klasse GIFEncryptorByLSBMethod, der Hauptunterschied besteht darin, wie Bytes von Nachrichten aus der Palette geschrieben und gelesen werden.
Programmablauf
Methode LBS
Angenommen, es gibt ein solches Bild:

In diesem Bild besteht die Palette aus 256 Farben (so speichert Paint). Die ersten vier Farben sind: weiß, schwarz, rot, grün. Die restlichen Farben sind schwarz. Die Bitfolge der globalen Palette wird wie folgt aussehen:
11111111 11111111 11111111 00000000 00000000 00000000 11111111 00000000 00000000 00000000 11111111 00000000…

Nach der Implementierung werden die hervorgehobenen Bits durch die Bits aus der Nachricht ersetzt. Das resultierende Bild weicht kaum vom Original ab.
Original
Bild mit integrierter Nachricht

![]()
Palette-Expansion-Methode
Beim Öffnen des Bildes, in das die Nachricht mit dieser Methode eingefügt wurde, könnte man folgendes Bild entdecken:

Offensichtlich eignet sich diese Methode nicht für umfassende Spionageaktivitäten und erfordert möglicherweise eine zusätzliche Verschlüsselung der Nachricht.
Verschlüsselung/Dekodierung in animierten Bildern funktioniert genauso wie in gewöhnlichen statischen Bildern, wodurch die Animation nicht gestört wird.
Verwendete Quellen:
Herunterladen:
Quelle: habr.com
