Стеганографија у ГИФ-у

Увод

Поздрав.
Не тако давно, док сам студирао на универзитету, постојао је курс из дисциплине „Софтверске методе информационе безбедности“. Задатак је захтевао да направимо програм који уграђује поруку у ГИФ датотеке. Одлучио сам да то урадим у Јави.

У овом чланку ћу описати неке теоријске тачке, као и како је настао овај мали програм.

Теоријски део

ГИФ формат

ГИФ (Грапхицс Интерцханге Формат – формат за размену слика) је формат за складиштење графичких слика, способан да складишти компресоване податке без губитка квалитета у формату до 256 боја. Овај формат је 1987. године (ГИФ87а) развио ЦомпуСерве за пренос растерских слика преко мрежа. 1989. године формат је измењен (ГИФ89а), додата је подршка за транспарентност и анимацију.

ГИФ датотеке имају блок структуру. Ови блокови увек имају фиксну дужину (или зависи од неких заставица), тако да је скоро немогуће погрешити где се који блок налази. Структура најједноставније неанимиране ГИФ слике у ГИФ89а формату:

Стеганографија у ГИФ-у

Од свих блокова структуре, у овом случају ће нас занимати глобални блок палете и параметри одговорни за палету:

  • CT — присуство глобалне палете. Ако је ова заставица постављена, глобална палета мора почети одмах након логичке ручке екрана.
  • Size — величина палете и број боја на слици. Вредности за овај параметар:

veličina
Број боја
Величина палете, бајтови

7
256
768

6
128
384

5
64
192

4
32
96

3
16
48

2
8
24

1
4
12

0
2
6

Методе шифровања

Следећи методи ће се користити за шифровање порука у датотекама слика:

  • Метода ЛСБ (најмање значајног бита).
  • Метода додавања палете

ЛСБ метода - уобичајена метода стеганографије. Састоји се од замене последњих значајних битова у контејнеру (у нашем случају, бајтова глобалне палете) са битовима скривене поруке.

Програм ће користити последња два бита у бајтовима глобалне палете као део ове методе. То значи да ће се за 24-битну слику, где је палета боја три бајта за црвену, плаву и зелену, након уграђивања поруке у њу свака компонента боје променити за највише 3/255 градација. Таква промена, прво, биће невидљива или тешко приметна људском оку, а друго, неће бити видљива на уређајима за излаз информација ниског квалитета.

Количина информација ће директно зависити од величине палете слика. Пошто је максимална величина палете 256 боја, и ако су два бита поруке уписана у компоненту сваке боје, онда је максимална дужина поруке (са максималном палетом на слици) 192 бајта. Када се порука угради у слику, величина датотеке се не мења.

Метода проширења палете, који функционише само за ГИФ структуру. Најефикасније ће бити на сликама са малом палетом. Његова суштина је да повећава величину палете, чиме се даје додатни простор за писање потребних бајтова уместо бајтова боје. Ако узмемо у обзир да је минимална величина палете 2 боје (6 бајтова), онда максимална величина уграђене поруке може бити 256 × 3–6 = 762 бајта. Недостатак је ниска криптографска сигурност; уграђена порука се може прочитати помоћу било ког уређивача текста ако порука није подвргнута додатном шифровању.

Практични део

Дизајн програма

Сви неопходни алати за имплементацију алгоритама за шифровање и дешифровање биће укључени у пакет com.tsarik.steganography. Овај пакет укључује интерфејс Encryptor са методама encrypt и decrypt, Класа Binary, који пружа могућност рада са низовима битова, као и класама изузетака UnableToEncryptException и UnableToDecryptException, који би требало да се користи у методама интерфејса Encryptor у случају грешака кодирања односно декодирања.

Главни програмски пакет com.tsarik.programs.gifed укључиће изводљиву програмску класу са статичком методом main, што вам омогућава да покренете програм; класа која чува параметре програма; и пакети са другим класама.

Имплементација самих алгоритама биће представљена у пакету com.tsarik.programs.gifed.gif класе GIFEncryptorByLSBMethod и GIFEncryptorByPaletteExtensionMethod. Обе ове класе ће имплементирати интерфејс Encryptor.

На основу структуре ГИФ формата, можете креирати општи алгоритам за увођење поруке у палету слика:

Стеганографија у ГИФ-у

Да би се утврдило присуство поруке на слици, потребно је на почетак поруке додати одређени низ битова које декодер прво чита и проверава да ли је тачност. Ако се не поклапа, онда се сматра да на слици нема скривене поруке. Затим морате одредити дужину поруке. Затим текст саме поруке.

Дијаграм класа целе апликације:

Стеганографија у ГИФ-у

Спровођење програма

Имплементација целог програма може се поделити на две компоненте: имплементацију метода шифровања интерфејса и метода дешифровања Encryptor, у часовима GIFEncryptorByLSBMethod и GIFEncryptorByPaletteExtensionMethod, и имплементацију корисничког интерфејса.

Размотрите класу GIFEncryptorByLSBMethod.

Стеганографија у ГИФ-у

Поља firstLSBit и secondLSBit садрже бројеве битова сваког бајта слике у који треба унети поруку и одакле поруку треба прочитати. Поље checkSequence складишти секвенцу битова за проверу да би се осигурало препознавање уграђене поруке. Статичка метода getEncryptingFileParameters враћа параметре наведене датотеке и карактеристике потенцијалне поруке.

Алгоритам методе encrypt класа GIFEncryptorByLSBMethod:

Стеганографија у ГИФ-у

И његов код:

@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();
}

Алгоритам и изворни код методе decrypt класа GIFEncryptorByLSBMethod:

Стеганографија у ГИФ-у

@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);
}

Имплементација часа GIFEncryptorByPaletteExtensionMethod биће сличан, само је начин чувања/читања информација другачији.

У класи MainFrame описане су методе омотача: encryptImage(Encryptor encryptor) и decryptImage(Encryptor encryptor), обрада резултата метода интерфејса Encryptor и интеракцију са корисником, тј. отварање дијалога за избор датотеке, приказивање порука о грешци, итд.; као и друге методе: openImage(), омогућавајући кориснику да изабере слику, exit(), који напушта апликацију. Ове методе се позивају из Action'с одговарајуће ставке менија. Ова класа додатно имплементира помоћне методе: createComponents() - креирање компоненти форме, loadImageFile(File f) — учитавање слике у посебну компоненту из датотеке. Имплементација часа GIFEncryptorByPaletteExtensionMethod слично имплементацији класе GIFEncryptorByLSBMethod, главна разлика је у начину на који се бајтови поруке пишу и читају са палете.

Рад програма

ЛБС метода

Рецимо да постоји оваква слика:

Стеганографија у ГИФ-у

На овој слици, палета се састоји од 256 боја (како Паинт чува). Прве четири боје су: бела, црна, црвена, зелена. Остале боје су црне. Глобална секвенца битова палете ће бити следећа:

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

Стеганографија у ГИФ-у

Када се порука угради, подвучени битови ће бити замењени битовима из поруке. Добијена слика се готово не разликује од оригинала.

Оригинал
Слика са уграђеном поруком

Стеганографија у ГИФ-у
Стеганографија у ГИФ-у

Метода проширења палете

Када отворите слику која садржи поруку користећи овај метод, видећете следећу слику:

Стеганографија у ГИФ-у

Јасно је да овај метод неће радити за пуноправне шпијунске активности и може захтевати додатно шифровање поруке.

Шифровање/дешифровање у анимираним сликама функционише исто као и у обичним статичним сликама, али анимација није прекинута.

Коришћени извори:

Преузми:

Извор: ввв.хабр.цом

Додај коментар