GIF форматындағы стеганография

Кіріспе

Құттықтау.
Жақында мен университетте оқып жүрген кезімде «Ақпаратты қорғаудың бағдарламалық әдістері» пәні бойынша курстық жұмыс болатын. Тапсырма бізден GIF файлдарына хабарлама енгізетін бағдарлама жасауды талап етті. Мен мұны Java тілінде жасауды шештім.

Бұл мақалада мен кейбір теориялық ойларды, сондай-ақ бұл шағын бағдарламаның қалай жасалғанын сипаттаймын.

Теориялық бөлім

GIF пішімі

GIF (Graphics Interchange Format – кескіндермен алмасу форматы) – 256 түске дейінгі форматта сапа жоғалтпай қысылған деректерді сақтауға қабілетті графикалық кескіндерді сақтауға арналған пішім. Бұл форматты 1987 жылы (GIF87a) CompuServe желі арқылы растрлық кескіндерді жіберу үшін әзірледі. 1989 жылы пішім өзгертілді (GIF89a), мөлдірлік пен анимацияны қолдау қосылды.

GIF файлдарының блоктық құрылымы бар. Бұл блоктар әрқашан бекітілген ұзындыққа ие (немесе ол кейбір жалаушаларға байланысты), сондықтан әр блоктың қай жерде орналасқаны туралы қателесу мүмкін емес. GIF89a пішіміндегі ең қарапайым анимацияланбаған GIF кескінінің құрылымы:

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 форматындағы стеганография

Бұл әдіс толыққанды тыңшылық әрекеттер үшін жұмыс істемейтіні анық және хабарламаны қосымша шифрлауды қажет етуі мүмкін.

Анимацияланған кескіндердегі шифрлау/шифрды шешу әдеттегі статикалық кескіндердегідей жұмыс істейді, бірақ анимация бұзылмайды.

Қолданылған көздер:

Жүктеу:

Ақпарат көзі: www.habr.com

пікір қалдыру