Steganografia w formacie GIF

Wprowadzenie

Witamy.
Nie tak dawno temu, gdy studiowałem na uniwersytecie, odbywały się zajęcia z dyscypliny „Programowe metody bezpieczeństwa informacji”. Zadanie wymagało od nas stworzenia programu osadzającego wiadomość w plikach GIF. Postanowiłem zrobić to w Javie.

W tym artykule opiszę kilka punktów teoretycznych, a także sposób, w jaki powstał ten mały program.

Część teoretyczna

formacie GIF

GIF (Graphics Interchange Format - format wymiany obrazów) to format przechowywania obrazów graficznych, umożliwiający przechowywanie skompresowanych danych bez utraty jakości w formacie do 256 kolorów. Format ten został opracowany w 1987 r. (GIF87a) przez firmę CompuServe do przesyłania obrazów rastrowych w sieciach. W 1989 roku zmodyfikowano format (GIF89a), dodano obsługę przezroczystości i animacji.

Pliki GIF mają strukturę blokową. Bloki te zawsze mają stałą długość (lub zależy to od niektórych flag), więc prawie niemożliwe jest pomyłka co do lokalizacji każdego bloku. Struktura najprostszego nieanimowanego obrazu GIF w formacie GIF89a:

Steganografia w formacie GIF

Ze wszystkich bloków konstrukcji w tym przypadku nas będzie interesował globalny blok palety i parametry odpowiedzialne za paletę:

  • CT — obecność globalnej palety. Jeśli ta flaga jest ustawiona, paleta globalna musi zaczynać się bezpośrednio po logicznym uchwycie ekranu.
  • Size — wielkość palety i liczba kolorów na obrazku. Wartości tego parametru:

Rozmiar
Liczba kolorów
Rozmiar palety, bajty

7
256
768

6
128
384

5
64
192

4
32
96

3
16
48

2
8
24

1
4
12

0
2
6

Metody szyfrowania

Do szyfrowania wiadomości w plikach graficznych zostaną użyte następujące metody:

  • Metoda LSB (najmniej znaczącego bitu).
  • Metoda dodawania palet

Metoda LSB - powszechna metoda steganografii. Polega na zastąpieniu ostatnich znaczących bitów w kontenerze (w naszym przypadku bajtów globalnej palety) bitami ukrytej wiadomości.

W ramach tej metody program użyje dwóch ostatnich bitów bajtów globalnej palety. Oznacza to, że w przypadku obrazu 24-bitowego, gdzie paleta kolorów składa się z trzech bajtów dla koloru czerwonego, niebieskiego i zielonego, po osadzeniu w nim komunikatu każdy składnik koloru zmieni się maksymalnie o 3/255 gradacji. Taka zmiana, po pierwsze, będzie niewidoczna lub trudna do zauważenia dla ludzkiego oka, a po drugie, nie będzie widoczna na urządzeniach wyjściowych informacji niskiej jakości.

Ilość informacji będzie bezpośrednio zależeć od rozmiaru palety obrazów. Ponieważ maksymalny rozmiar palety wynosi 256 kolorów i jeśli do składowej każdego koloru zostaną zapisane dwa bity komunikatu, wówczas maksymalna długość komunikatu (przy maksymalnej palecie na obrazie) wynosi 192 bajty. Po osadzeniu wiadomości w obrazie rozmiar pliku nie ulega zmianie.

Metoda rozszerzania palety, co działa tylko w przypadku struktury GIF. Będzie najskuteczniejszy na obrazach o małej palecie. Jego istotą jest to, że zwiększa rozmiar palety, zapewniając w ten sposób dodatkową przestrzeń na zapisanie niezbędnych bajtów w miejsce bajtów kolorów. Jeśli weźmiemy pod uwagę, że minimalny rozmiar palety to 2 kolory (6 bajtów), to maksymalny rozmiar osadzonego komunikatu może wynosić 256 × 3–6 = 762 bajty. Wadą jest niskie bezpieczeństwo kryptograficzne, osadzoną wiadomość można odczytać w dowolnym edytorze tekstu, jeśli wiadomość nie została poddana dodatkowemu szyfrowaniu.

Część praktyczna

Projekt programu

Wszystkie niezbędne narzędzia do implementacji algorytmów szyfrowania i deszyfrowania zostaną zawarte w pakiecie com.tsarik.steganography. Pakiet ten zawiera interfejs Encryptor z metodami encrypt и decrypt, Klasa Binary, który zapewnia możliwość pracy z tablicami bitowymi, a także klasami wyjątków UnableToEncryptException и UnableToDecryptException, które powinny być używane w metodach interfejsu Encryptor odpowiednio w przypadku błędów kodowania i dekodowania.

Główny pakiet programu com.tsarik.programs.gifed będzie zawierać uruchamialną klasę programu z metodą statyczną main, pozwalający na uruchomienie programu; klasa przechowująca parametry programu; i pakiety z innymi klasami.

Implementacja samych algorytmów zostanie przedstawiona w pakiecie com.tsarik.programs.gifed.gif zajęcia GIFEncryptorByLSBMethod и GIFEncryptorByPaletteExtensionMethod. Obie te klasy będą implementować interfejs Encryptor.

Na podstawie struktury formatu GIF można stworzyć ogólny algorytm wprowadzania komunikatu do palety obrazów:

Steganografia w formacie GIF

Aby stwierdzić obecność komunikatu w obrazie, należy na początku komunikatu dodać określoną sekwencję bitów, którą dekoder najpierw odczytuje i sprawdza poprawność. Jeśli nie pasuje, uważa się, że na obrazie nie ma ukrytej wiadomości. Następnie musisz określić długość wiadomości. Następnie treść samej wiadomości.

Diagram klas całej aplikacji:

Steganografia w formacie GIF

Realizacja programu

Implementację całego programu można podzielić na dwa elementy: implementację metod szyfrowania interfejsu i deszyfrowania Encryptor, na zajęciach GIFEncryptorByLSBMethod и GIFEncryptorByPaletteExtensionMethodoraz implementację interfejsu użytkownika.

Weź pod uwagę klasę GIFEncryptorByLSBMethod.

Steganografia w formacie GIF

Pola firstLSBit и secondLSBit zawierają numery bitów każdego bajtu obrazu, w które należy wpisać komunikat i skąd należy odczytać komunikat. Pole checkSequence przechowuje sekwencję bitów kontrolnych, aby zapewnić rozpoznanie osadzonego komunikatu. Metoda statyczna getEncryptingFileParameters zwraca parametry określonego pliku i charakterystykę potencjalnej wiadomości.

Algorytm metody encrypt klasa GIFEncryptorByLSBMethod:

Steganografia w formacie GIF

I jego kod:

@Override
public void encrypt(File in, File out, String text) throws UnableToEncodeException, NullPointerException, IOException {
	if (in == null) {
		throw new NullPointerException("Input file is null");
	}
	if (out == null) {
		throw new NullPointerException("Output file is null");
	}
	if (text == null) {
		throw new NullPointerException("Text is null");
	}
	
	// read bytes from input file
	byte[] bytes = new byte[(int)in.length()];
	InputStream is = new FileInputStream(in);
	is.read(bytes);
	is.close();
	
	// check format
	if (!(new String(bytes, 0, 6)).equals("GIF89a")) {
		throw new UnableToEncodeException("Input file has wrong GIF format");
	}
	
	// read palette size property from first three bits in the 10-th byte from the file
	byte[] b10 = Binary.toBitArray(bytes[10]);
	byte bsize = Binary.toByte(new byte[] {b10[0], b10[1], b10[2]});
	
	// calculate color count and possible message length
	int bOrigColorCount = (int)Math.pow(2, bsize+1);
	int possibleMessageLength = bOrigColorCount*3/4;
	int possibleTextLength = possibleMessageLength-2;// one byte for check and one byte for message length
	
	if (possibleTextLength < text.length()) {
		throw new UnableToEncodeException("Text is too big");
	}
	
	int n = 13;
	
	// write check sequence
	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++;
	}
	
	// write text length
	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++;
	}
	
	// write message
	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++;
		}
	}
	
	// write output file
	OutputStream os = new FileOutputStream(out);
	os.write(bytes);
	os.close();
}

Algorytm i kod źródłowy metody decrypt klasa GIFEncryptorByLSBMethod:

Steganografia w formacie GIF

@Override
public String decrypt(File in) throws UnableToDecodeException, NullPointerException, IOException {
	if (in == null) {
		throw new NullPointerException("Input file is null");
	}
	
	// read bytes from input file
	byte[] bytes = new byte[(int)in.length()];
	InputStream is = new FileInputStream(in);
	is.read(bytes);
	is.close();
	
	// check format
	if (!(new String(bytes, 0, 6)).equals("GIF89a")) {
		throw new UnableToDecodeException("Input file has wrong GIF format");
	}
	
	// read palette size property from first three bits in the 10-th byte from the file
	byte[] b10 = Binary.toBitArray(bytes[10]);
	byte bsize = Binary.toByte(new byte[] {b10[0], b10[1], b10[2]});
	
	// calculate color count and possible message length
	int bOrigColorCount = (int)Math.pow(2, bsize+1);
	int possibleMessageLength = bOrigColorCount*3/4;
	int possibleTextLength = possibleMessageLength-2;	// one byte for check and one byte for message length
	
	int n = 13;
	
	// read check sequence
	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("There is no encrypted message in the image (Check sequence is incorrect)");
	}
	
	// read text length
	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("Decoded text length is less than 0");
	}
	if (possibleTextLength < textLength) {
		throw new UnableToDecodeException("There is no messages (Decoded message length (" + textLength + ") is less than Possible message length (" + possibleTextLength + "))");
	}
	
	// read text bits and make text bytes
	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);
}

Implementacja klasy GIFEncryptorByPaletteExtensionMethod będzie podobny, różni się jedynie sposób zapisywania/odczytu informacji.

W klasie MainFrame opisano metody pakowania: encryptImage(Encryptor encryptor) и decryptImage(Encryptor encryptor), przetwarzanie wyników metod interfejsu Encryptor oraz interakcję z użytkownikiem, tj. otwarcie okna dialogowego wyboru pliku, pokazywanie komunikatów o błędach itp.; a także inne metody: openImage(), umożliwiając użytkownikowi wybór obrazu, exit(), co powoduje zamknięcie aplikacji. Metody te są wywoływane z Actionodpowiednie pozycje menu. Klasa ta dodatkowo implementuje metody pomocnicze: createComponents() - tworzenie komponentów formularzy, loadImageFile(File f) — ładowanie obrazu do specjalnego komponentu z pliku. Implementacja klasy GIFEncryptorByPaletteExtensionMethod podobny do implementacji klasy GIFEncryptorByLSBMethod, główna różnica polega na sposobie zapisywania i odczytywania bajtów wiadomości z palety.

Działanie programu

Metoda LBS

Powiedzmy, że istnieje taki obraz:

Steganografia w formacie GIF

Na tym obrazku paleta składa się z 256 kolorów (jak zapisuje Paint). Pierwsze cztery kolory to: biały, czarny, czerwony, zielony. Pozostałe kolory to czarny. Globalna sekwencja bitów palety będzie następująca:

11111111 11111111 11111111 00000000 00000000 00000000 11111111 00000000 00000000 00000000 11111111 00000000...

Steganografia w formacie GIF

Po osadzeniu wiadomości podkreślone bity zostaną zastąpione bitami z wiadomości. Powstały obraz prawie nie różni się od oryginału.

Oryginalny
Obraz z osadzoną wiadomością

Steganografia w formacie GIF
Steganografia w formacie GIF

Metoda rozszerzania palety

Gdy otworzysz obraz zawierający wiadomość przy użyciu tej metody, zobaczysz następujący obrazek:

Steganografia w formacie GIF

Oczywiste jest, że ta metoda nie sprawdzi się w przypadku pełnoprawnych działań szpiegowskich i może wymagać dodatkowego szyfrowania wiadomości.

Szyfrowanie/deszyfrowanie animowanych obrazów działa tak samo jak w przypadku zwykłych obrazów statycznych, ale animacja nie jest zakłócana.

Wykorzystane źródła:

Pobierz:

Źródło: www.habr.com

Dodaj komentarz