Steganografio en GIF

Enkonduko

Salutojn.
Antaŭ ne longe, kiam mi studis en la universitato, estis kurso pri la fako "Programaraj metodoj de informa sekureco." La tasko postulis, ke ni kreu programon, kiu enigas mesaĝon en GIF-dosierojn. Mi decidis fari ĝin en Java.

En ĉi tiu artikolo mi priskribos kelkajn teoriajn punktojn, same kiel kiel ĉi tiu malgranda programo estis kreita.

Teoria parto

GIF formato

GIF (Graphics Interchange Format - formato por interŝanĝado de bildoj) estas formato por stoki grafikajn bildojn, kapabla konservi kunpremitajn datumojn sen perdo de kvalito en formato de ĝis 256 koloroj. Tiu formato estis evoluigita en 1987 (GIF87a) fare de CompuServe por elsendado de rastrumbildoj tra retoj. En 1989, la formato estis modifita (GIF89a), subteno por travidebleco kaj animacio estis aldonita.

GIF-dosieroj havas blokan strukturon. Ĉi tiuj blokoj ĉiam havas fiksan longon (aŭ dependas de iuj flagoj), do preskaŭ neeble erari pri kie troviĝas ĉiu bloko. La strukturo de la plej simpla ne-vigla GIF-bildo en GIF89a formato:

Steganografio en GIF

El ĉiuj blokoj de la strukturo, ĉi-kaze ni interesiĝos pri la tutmonda paletrobloko kaj la parametroj respondecaj por la paletro:

  • CT — ĉeesto de tutmonda paletro. Se ĉi tiu flago estas agordita, la tutmonda paletro devas komenciĝi tuj post la logika ekrantenilo.
  • Size - paletro grandeco kaj nombro da koloroj en la bildo. Valoroj por ĉi tiu parametro:

grandeco
Nombro de koloroj
Paletro grandeco, bajtoj

7
256
768

6
128
384

5
64
192

4
32
96

3
16
48

2
8
24

1
4
12

0
2
6

Ĉifradaj metodoj

La sekvaj metodoj estos uzataj por ĉifri mesaĝojn en bilddosieroj:

  • LSB (Malplej Signifa Bito) metodo
  • Metodo de aldono de paletro

LSB-metodo - ofta metodo de steganografio. Ĝi konsistas el anstataŭigo de la lastaj signifaj bitoj en la ujo (en nia kazo, la tutmondaj paletrobajtoj) per la bitoj de la kaŝita mesaĝo.

La programo uzos la lastajn du bitojn en la tutmondaj paletrobajtoj kiel parto de ĉi tiu metodo. Ĉi tio signifas, ke por 24-bita bildo, kie la paletra koloro estas tri bajtoj por ruĝa, blua kaj verda, post enkonstruado de mesaĝo en ĝi, ĉiu kolorkomponento ŝanĝiĝos je maksimume 3/255 gradaĵoj. Tia ŝanĝo, unue, estos nevidebla aŭ malfacile rimarkebla por la homa okulo, kaj due, ĝi ne estos videbla ĉe malkvalitaj informaj eligo-aparatoj.

La kvanto de informoj rekte dependos de la grandeco de la bilda paletro. Ĉar la maksimuma grandeco de la paletro estas 256 koloroj, kaj se du mesaĝbitoj estas skribitaj en la komponenton de ĉiu koloro, tiam la maksimuma mesaĝlongo (kun la maksimuma paletro en la bildo) estas 192 bajtoj. Post kiam la mesaĝo estas enigita en la bildon, la dosiergrandeco ne ŝanĝiĝas.

Metodo de ekspansio de paletro, kiu funkcias nur por la GIF strukturo. Ĝi estos plej efika sur bildoj kun malgranda paletro. Ĝia esenco estas, ke ĝi pliigas la grandecon de la paletro, tiel disponigante plian spacon por skribi la necesajn bajtojn anstataŭ la kolorbajtojn. Se ni konsideras, ke la minimuma grandeco de la paletro estas 2 koloroj (6 bajtoj), tiam la maksimuma grandeco de la enigita mesaĝo povas esti 256 × 3–6 = 762 bajtoj. La malavantaĝo estas malalta kriptiga sekureco; la enigita mesaĝo povas esti legita uzante ajnan tekstredaktilon se la mesaĝo ne estis submetita al kroma ĉifrado.

Praktika parto

Program-dezajno

Ĉiuj necesaj iloj por efektivigo de ĉifrado kaj malĉifrado-algoritmoj estos inkluzivitaj en la pakaĵo com.tsarik.steganography. Ĉi tiu pako inkluzivas la interfacon Encryptor kun metodoj encrypt и decrypt, Klaso Binary, kiu disponigas la kapablon labori kun bitaj tabeloj, same kiel esceptklasoj UnableToEncryptException и UnableToDecryptException, kiu devus esti uzata en interfacmetodoj Encryptor en kazo de eraroj de kodado kaj malkodado respektive.

Ĉefa programpakaĵo com.tsarik.programs.gifed inkludos ruleblan programklason kun statika metodo main, permesante al vi ruli la programon; klaso kiu stokas programajn parametrojn; kaj pakaĵoj kun aliaj klasoj.

La efektivigo de la algoritmoj mem estos prezentita en la pakaĵo com.tsarik.programs.gifed.gif klasoj GIFEncryptorByLSBMethod и GIFEncryptorByPaletteExtensionMethod. Ambaŭ ĉi tiuj klasoj efektivigos la interfacon Encryptor.

Surbaze de la strukturo de la formato GIF, vi povas krei ĝeneralan algoritmon por enkonduki mesaĝon en la bildpaletron:

Steganografio en GIF

Por determini la ĉeeston de mesaĝo en bildo, necesas aldoni certan sekvencon de bitoj al la komenco de la mesaĝo, kiun la malĉifrilo legas unue kaj kontrolas por ĝusteco. Se ĝi ne kongruas, tiam oni konsideras, ke ne estas kaŝita mesaĝo en la bildo. Poste vi devas specifi la longecon de la mesaĝo. Poste la teksto de la mesaĝo mem.

Klasdiagramo de la tuta aplikaĵo:

Steganografio en GIF

Efektivigo de la programo

La efektivigo de la tuta programo povas esti dividita en du komponentojn: efektivigo de interfaco-ĉifrado kaj malĉifrado-metodoj Encryptor, en klasoj GIFEncryptorByLSBMethod и GIFEncryptorByPaletteExtensionMethod, kaj la efektivigo de la uzantinterfaco.

Konsideru la klason GIFEncryptorByLSBMethod.

Steganografio en GIF

kampoj firstLSBit и secondLSBit enhavas la nombrojn da bitoj de ĉiu bajto de la bildo, en kiu la mesaĝo devus esti enigita kaj de kie la mesaĝo devus esti legita. Kampo checkSequence stokas ĉekbitsekvencon por certigi rekonon de la enigita mesaĝo. Statika metodo getEncryptingFileParameters resendas la parametrojn de la specifita dosiero kaj la karakterizaĵojn de la ebla mesaĝo.

Metoda algoritmo encrypt klaso GIFEncryptorByLSBMethod:

Steganografio en GIF

Kaj lia kodo:

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

Algoritmo kaj fontkodo de la metodo decrypt klaso GIFEncryptorByLSBMethod:

Steganografio en 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);
}

Efektivigo de la klaso GIFEncryptorByPaletteExtensionMethod estos simila, nur la metodo konservi/legi informojn estas malsama.

En klaso MainFrame envolvaj metodoj estas priskribitaj: encryptImage(Encryptor encryptor) и decryptImage(Encryptor encryptor), prilaborante la rezultojn de interfacmetodoj Encryptor kaj interagado kun la uzanto, t.e. malfermante dosierelektan dialogon, montrante erarmesaĝojn, ktp.; same kiel aliaj metodoj: openImage(), permesante al la uzanto elekti bildon, exit(), kiu eliras la aplikaĵon. Ĉi tiuj metodoj estas nomitaj de Actionla respondaj menueroj de. Ĉi tiu klaso aldone efektivigas helpajn metodojn: createComponents() - kreado de formkomponentoj, loadImageFile(File f) — ŝarĝi bildon en specialan komponanton el dosiero. Efektivigo de la klaso GIFEncryptorByPaletteExtensionMethod simila al la klasa efektivigo GIFEncryptorByLSBMethod, la ĉefa diferenco estas en la maniero kiel mesaĝbajtoj estas skribitaj kaj legitaj el la paletro.

Programa funkciado

LBS-metodo

Ni diru, ke estas bildo tia:

Steganografio en GIF

En ĉi tiu bildo, la paletro konsistas el 256 koloroj (kiel Paint konservas). La unuaj kvar koloroj estas: blanka, nigra, ruĝa, verda. Aliaj koloroj estas nigraj. La tutmonda paletra bitsekvenco estos kiel sekvas:

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

Steganografio en GIF

Post kiam la mesaĝo estas enigita, la substrekitaj bitoj estos anstataŭigitaj per la bitoj de la mesaĝo. La rezulta bildo preskaŭ ne diferencas de la originalo.

Originala
Bildo kun enigita mesaĝo

Steganografio en GIF
Steganografio en GIF

Metodo de ekspansio de paletro

Kiam vi malfermas bildon enhavantan mesaĝon per ĉi tiu metodo, vi vidos la sekvan bildon:

Steganografio en GIF

Estas klare, ke ĉi tiu metodo ne funkcios por plenrajtaj spionaj agadoj, kaj eble postulos plian ĉifradon de la mesaĝo.

Ĉifrado/malĉifrado en viglaj bildoj funkcias same kiel en regulaj senmovaj bildoj, sed la animacio ne estas rompita.

Fontoj uzataj:

Elŝuti:

fonto: www.habr.com

Aldoni komenton