Steganografi i GIF

Introduktion

Hej.
For ikke så længe siden, da jeg studerede på universitetet, havde jeg et projekt om emnet 'Programmeringsmetoder til beskyttelse af information'. Opgaven krævede, at jeg skulle lave et program, der indsatte beskeder i GIF-filer. Jeg besluttede at programmere det i Java.

I denne artikel vil jeg beskrive nogle teoretiske aspekter samt, hvordan jeg skabte dette lille program.

Teoretisk del

GIF-format

GIF (engelsk: Graphics Interchange Format) er et format til opbevaring af grafiske billeder, som kan gemme komprimerede data uden tab af kvalitet i op til 256 farver. Dette format blev udviklet i 1987 (GIF87a) af CompuServe til overførsel af bitmapbilleder over netværk. I 1989 blev formatet modificeret (GIF89a) med tilføjelser af support til gennemsigtighed og animation.

GIF-filer har en blokstruktur. Disse blokke har altid en fast længde (eller den afhænger af visse flag), så det er næsten umuligt at fejle i at finde ud af, hvor hvilken blok er. Strukturen af det enkleste ikke-animerede GIF-billede i GIF89a-formatet:

Steganografi i GIF

Af alle blokke i strukturen vil vi i dette tilfælde være interesserede i den globale palet og parametrene, der vedrører paletten:

  • CT — tilstedeværelsen af en global palet. Hvis dette flag er sat, skal den globale palet umiddelbart starte efter descriptoren for den logiske skærm.
  • Størrelse — størrelsen af paletten og antallet af farver i billedet. Værdierne for denne parameter:

Størrelse
Antal farver
Palette størrelse, byte

7
256
768

6
128
384

5
64
192

4
32
96

3
16
48

2
8
24

1
4
12

0
2
6

Krypteringsmetoder

Som metoder til at kryptere beskeder i billedfiler vil følgende blive anvendt:

  • LSB-metoden (Least Significant Bit, mindst signifikante bit)
  • Paletteudvidelsesmetode

LSB-metoden — en udbredt steganografimetode. Den går ud på at erstatte de sidste signifikante bits i containeren (i vores tilfælde bytes i den globale palet) med bits af den skjulte besked.

Metoden vil bruge de to sidste bits i bytes af den globale palet. Det betyder, at for et 24-bit billede, hvor farvepaletten repræsenterer tre bytes til rød, blå og grøn, vil hver farvekomponent ændre sig med maksimalt 3/255 nuancer efter indførsel af meddelelsen. Sådanne ændringer vil først og fremmest være usynlige eller svære at bemærke for det menneskelige øje, og for det andet vil de ikke være differentierbare på lavkvalitets udstyr.

Mængden af information vil direkte afhænge af størrelsen på billedepaletten. Da den maksimale størrelse af paletten er 256 farver, og hvis man indskriver to bits af meddelelser i hver farvekomponent, vil den maksimale længde af meddelelsen (ved maksimal palet i billedet) være 192 bytes. Efter indførselen af meddelelsen i billedet ændres filens størrelse ikke.

Metode til at udvide paletten, som kun virker for GIF-strukturen. Det vil være mest effektivt i billeder med små paletter. Dets essens ligger i, at det øger størrelsen af paletten, hvilket giver ekstra plads til at skrive nødvendige bytes i stedet for farvebytes. Hvis man overvejer, at den mindste størrelse af paletten er 2 farver (6 bytes), kan den maksimale størrelse af den indskrevne meddelelse være 256×3–6=762 bytes. Ulempen er lav krypteringsbeskyttelse; den indskrevne meddelelse kan læses med enhver teksteditor, hvis meddelelsen ikke er blevet underkastet yderligere kryptering.

Praktisk del

Programdesign

Alle nødvendige værktøjer til implementering af krypterings- og dekrypteringsalgoritmer vil være i pakken com.tsarik.steganography. Denne pakke inkluderer grænsefladen Encryptor med metoder krypter og decrypt, klassen Binary, som giver mulighed for at arbejde med bitarrays, samt undtagelsesklasserne UnableToEncryptException og UnableToDecryptException, der skal anvendes i metoderne i grænsefladen Encryptor i tilfælde af fejlkodning og dekodning hhv.

Hovedpakken i programmet com.tsarik.programs.gifed vil inkludere programmets kørende klasse med den statiske metode main, der giver mulighed for at starte programmet; en klasse, der gemmer programmets parametre; og pakker med andre klasser.

Implementeringen af selve algoritmerne vil blive præsenteret i pakken com.tsarik.programs.gifed.gif klasser GIFEncryptorByLSBMethod og GIFEncryptorByPaletteExtensionMethod. Begge disse klasser vil implementere et interface Encryptor.

Baseret på strukturen af GIF-formatet kan man udarbejde en generel algoritme til at indsætte beskeder i billedets palette:

Steganografi i GIF

For at bestemme om der er en besked i billedet, skal man tilføje en sekvens af bits i starten af beskeden, som dekoderen først læser og tjekker for korrekthed. Hvis den ikke stemmer overens, anses det for, at der ikke er nogen skjult besked i billedet. Derefter skal længden af beskeden angives. Herefter selve tekstbeskeden.

Klassediagrammet for hele applikationen:

Steganografi i GIF

Implementeringen af programmet

Implementeringen af hele programmet kan opdeles i to komponenter: implementeringen af krypterings- og dekrypteringsmetoderne i interfacet Encryptor, i klasserne GIFEncryptorByLSBMethod og GIFEncryptorByPaletteExtensionMethod, og implementeringen af brugergrænsefladen.

Lad os se på klassen GIFEncryptorByLSBMethod.

Steganografi i GIF

Felter firstLSBit og secondLSBit indeholder numrene på bits i hver byte af billedet, hvor beskeden skal indsættes og fra hvor den skal læses. Feltet checkSequence opbevarer en kontrolsekvens af bits for at sikre genkendelsen af den indbyggede besked. Den statiske metode getEncryptingFileParameters returnerer parametrene for den angivne fil og karakteristikaene for den potentielle besked.

Algoritmen for metoden krypter klassen GIFEncryptorByLSBMethod:

Steganografi i GIF

Og dens kode:

@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");
	}
	
	// læs bytes fra input fil
	byte[] bytes = new byte[(int)in.length()];
	InputStream is = new FileInputStream(in);
	is.read(bytes);
	is.close();
	
	// tjek format
	if (!(new String(bytes, 0, 6)).equals("GIF89a")) {
		throw new UnableToEncodeException("Input file has wrong GIF format");
	}
	
	// læs palette størrelse egenskab fra de første tre bits i den 10. byte fra filen
	byte[] b10 = Binary.toBitArray(bytes[10]);
	byte bsize = Binary.toByte(new byte[] {b10[0], b10[1], b10[2]});
	
	// beregn farveantal og mulig meddelelser længde
	int bOrigColorCount = (int)Math.pow(2, bsize+1);
	int possibleMessageLength = bOrigColorCount*3/4;
	int possibleTextLength = possibleMessageLength-2; // én byte til kontrol og én byte til meddelelsens længde
	
	if (possibleTextLength < text.length()) {
		throw new UnableToEncodeException("Text is too big");
	}
	
	int n = 13;
	
	// skriv kontrol sekvens
	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++;
	}
	
	// skriv tekst længde
	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++;
	}
	
	// skriv meddelelse
	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++;
		}
	}
	
	// skriv output fil
	OutputStream os = new FileOutputStream(out);
	os.write(bytes);
	os.close();
}

Algoritmen og kildekoden til metoden decrypt klassen GIFEncryptorByLSBMethod:

Steganografi i GIF

@Override
public String decrypt(File in) throws UnableToDecodeException, NullPointerException, IOException {
	if (in == null) {
		throw new NullPointerException("Input file is null");
	}
	
	// læs bytes fra inputfilen
	byte[] bytes = new byte[(int)in.length()];
	InputStream is = new FileInputStream(in);
	is.read(bytes);
	is.close();
	
	// tjek format
	if (!(new String(bytes, 0, 6)).equals("GIF89a")) {
		throw new UnableToDecodeException("Input file has wrong GIF format");
	}
	
	// læs paletstørrelse ejendom fra de første tre bits i den 10. byte fra filen
	byte[] b10 = Binary.toBitArray(bytes[10]);
	byte bsize = Binary.toByte(new byte[] {b10[0], b10[1], b10[2]});
	
	// beregn farveantal og mulig meddelelseslængde
	int bOrigColorCount = (int)Math.pow(2, bsize+1);
	int possibleMessageLength = bOrigColorCount*3/4;
	int possibleTextLength = possibleMessageLength-2; // en byte til kontrol og en byte til meddelelsens længde
	
	int n = 13;
	
	// læs kontrolsekvens
	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)");
	}
	
	// læs tekstlængde
	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 + "))");
	}
	
	// læs tekstbits og lav tekstbytes
	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);
}

Implementeringen af klassen GIFEncryptorByPaletteExtensionMethod vil være ligesom, men adskiller sig i metoden til at gemme/læse information.

I klassen MainFrame beskrives wrapper-metoderne: encryptImage(Encryptor encryptor) og decryptImage(Encryptor encryptor), som behandles resultaterne fra interface-metoderne Encryptor og interagerer med brugeren, dvs. åbner dialoger til filvalg, viser fejlmeddelelser osv.; samt andre metoder: openImage(), der giver brugeren mulighed for at vælge et billede, exit(), der afslutter applikationen. Disse metoder kaldes fra Handlingde relevante menupunkter. I denne klasse er der desuden implementeret hjælpe-metoder: createComponents() — oprettelse af formularens komponenter, loadImageFile(File f) — indlæsning af billede i en speciel komponent fra en fil. Implementeringen af klassen GIFEncryptorByPaletteExtensionMethod ligner implementeringen af klassen GIFEncryptorByLSBMethod, det primære forskel er måden at skrive og læse bytes af meddelelsen fra paletten.

Programmets drift

Metoden LBS

Lad os antage, at der er et sådant billede:

Steganografi i GIF

I dette billede består paletten af 256 farver (som Paint gemmer). De første fire farver: hvid, sort, rød, grøn. De øvrige farver er sorte. Sekvensen af bits i den globale palette vil være som følger:

11111111 11111111 11111111 00000000 00000000 00000000 11111111 00000000 00000000 00000000 11111111 00000000…

Steganografi i GIF

Efter implementeringen vil de understregede bits blive erstattet med bits fra meddelelsen. Det resulterende billede adskiller sig næsten ikke fra originalen.

Original
Billede med indsatte meddelelser

Steganografi i GIF
Steganografi i GIF

Metode til at udvide paletten

Når man åbner billedet, hvori meddelelsen er placeret ved denne metode, kan man møde et sådant syn:

Steganografi i GIF

Det er indlysende, at en sådan metode ikke er egnet til effektiv spionage og kræver muligvis yderligere kryptering af meddelelsen.

Kryptering/dekryptering i animerede billeder fungerer som i normale statiske billeder, samtidig med at animationen ikke forstyrres.

Brugte kilder:

Download:

Kilde: habr.com

Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere 🔥 Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere | ProHoster