Стеганографія в GIF

Запровадження

Вітаю.
Нещодавно, коли навчався в університеті, була курсова з дисципліни «Програмні методи захисту інформації». За завданням потрібно було створити програму, яка впроваджує повідомлення у файли формату GIF. Вирішив робити на Java.

У цій статті я опишу деякі теоретичні моменти, а також як створювалася ця невелика програма.

Теоретична частина

Формат GIF

GIF (англ. Graphics Interchange Format – формат для обміну зображеннями) – формат зберігання графічних зображень, здатний зберігати стислі дані без втрати якості у форматі до 256 кольорів. Цей формат був розроблений у 1987 році (GIF87a) фірмою CompuServe для передачі растрових зображень мережами. У 1989-му формат був модифікований (GIF89a), було додано підтримку прозорості та анімації.

Файли формату GIF мають блокову структуру. Дані блоки завжди мають фіксовану довжину (або залежить від деяких прапорів), так що помилитися в тому, де який блок знаходиться, практично неможливо. Структура найпростішого неанімованого GIF-зображення формату GIF89a:

Стеганографія в GIF

З усіх блоків структури в даному випадку нас цікавитимуть блок глобальної палітри та параметри, які відповідають за палітру:

  • CT - Наявність глобальної палітри. Якщо цей прапор встановлений, відразу після дескриптора логічного екрану повинна починатися глобальна палітра.
  • Size - Розмір палітри і кількість кольорів картинки. Значення цього параметра:

Розмір
Кількість кольорів
Розмір палітри, байт

7
256
768

6
128
384

5
64
192

4
32
96

3
16
48

2
8
24

1
4
12

0
2
6

Методи шифрування

Як методи зашифрування повідомлень у файлах зображень будуть використовуватися:

  • Метод LSB (Least Significant Bit, найменший значний біт)
  • Метод доповнення палітри

Метод LSB - Поширений метод стеганографії. Він полягає в заміні останніх значущих біт в контейнері (у нашому випадку байти глобальної палітри) на біти повідомлення, що приховується.

У програмі будуть використовуватися в рамках цього методу два останні біти в байтах глобальної палітри. Це означає, що для 24-бітного зображення, де колір палітри є три байти для червоного, синього, і зеленого кольорів, після введення повідомлення в нього, кожна складова кольору зміниться максимум на 3/255 градації. Така зміна, по-перше, буде непомітною або важко помітною для людського ока, а по-друге, не буде помітною на низькоякісних пристроях виведення інформації.

Кількість інформації безпосередньо залежатиме від розміру палітри зображення. Оскільки максимальний розмір палітри 256 кольорів і якщо записувати по два біти повідомлення в складову кожного кольору, то максимальна довжина повідомлення (при максимальній палітрі в зображенні) становить 192 байти. Після введення повідомлення у зображення розмір файлу не змінюється.

Метод розширення палітрипрацює тільки для структури GIF. Він буде найбільш ефективним у зображеннях з палітрою невеликих розмірів. Суть його у тому, що він збільшує розмір палітри, цим давши додатковий простір для запису необхідних байт дома байт квітів. Якщо врахувати що мінімальний розмір палітри становить 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.

На основі структури формату GIF можна скласти загальний алгоритм впровадження повідомлення на палітру зображення:

Стеганографія в GIF

Для визначення присутності повідомлення у зображенні необхідно до початку повідомлення додавати певну послідовність біт, яку дешифратор зчитує насамперед і перевіряє на коректність. Якщо вона не збігається, то вважається, що у зображенні немає прихованого повідомлення. Далі треба зазначати довжину повідомлення. Потім сам текст повідомлення.

Діаграма класів всієї програми:

Стеганографія в GIF

Реалізація програми

Реалізацію всієї програми можна розбити на дві складові: реалізація методів шифрування та дешифрування інтерфейсу Encryptor, у класах GIFEncryptorByLSBMethod и GIFEncryptorByPaletteExtensionMethod, та реалізація інтерфейсу користувача.

Розглянемо клас GIFEncryptorByLSBMethod.

Стеганографія в GIF

Поля firstLSBit и secondLSBit містять номери бітів кожного байта зображення, в які має заноситись та звідки зчитуватися повідомлення. Поле checkSequence зберігає контрольну послідовність біт забезпечення розпізнавання вбудованого повідомлення. Статичний метод getEncryptingFileParameters повертає параметри вказаного файлу та характеристики потенційного повідомлення.

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

Стеганографія в GIF

І його код:

@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:

Стеганографія в 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);
}

Реалізація класу GIFEncryptorByPaletteExtensionMethod буде аналогічною, тільки відрізняється метод збереження/зчитування інформації.

У класі MainFrame описані методи-«обгортки»: encryptImage(Encryptor encryptor) и decryptImage(Encryptor encryptor), що обробляють результати методів інтерфейсу Encryptor та здійснюють взаємодію з користувачем, тобто відкривають діалог вибору файлів, показують повідомлення про помилки тощо; а також інші методи: openImage(), що дає можливість користувачеві вибору зображення, exit(), що здійснює вихід із програми. Ці методи викликаються з Action'ів відповідних пунктів меню. У цьому класі додатково реалізовано допоміжні методи: createComponents() - Створення компонентів форми, loadImageFile(File f) — завантаження зображення до спеціального компонента з файлу. Реалізація класу GIFEncryptorByPaletteExtensionMethod аналогічна реалізації класу GIFEncryptorByLSBMethod, основна відмінність полягає у способі запису та читанні байтів повідомлення з палітри.

Робота програми

Метод LBS

Допустимо є таке зображення:

Стеганографія в GIF

У цьому зображенні панель складається з 256 кольорів (так зберігає Paint). Перші чотири кольори: білий, чорний, червоний, зелений. Інші кольори - чорні. Послідовність біт глобальної палітри буде така:

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

Стеганографія в GIF

Після використання повідомлення підкреслені біти будуть замінені бітами з повідомлення. Отримане зображення майже відрізняється від оригіналу.

Оригінал
Зображення із впровадженим повідомленням

Стеганографія в GIF
Стеганографія в GIF

Метод розширення палітри

Відкривши зображення, в яке розміщено повідомлення за даним методом, можна виявити таку картину:

Стеганографія в GIF

Зрозуміло, що для повноцінної шпигунської діяльності такий метод не піде, і вимагає, може, додаткового шифрування повідомлення.

Шифрування/дешифрування в анімованих зображеннях працює, як і звичайних статичних зображеннях, при цьому анімація не порушується.

Використані джерела:

Завантажити:

Джерело: habr.com

Додати коментар або відгук