Steganografi i GIF

Innledning

Hei.
For not long ago, when I was studying at university, I had a course on the subject of "Software Methods for Information Protection." The task required creating a program to embed a message into GIF format files. I decided to do this in Java.

In this article, I will describe some theoretical points as well as how this small program was created.

Theoretical Part

GIF Format

GIF (English: Graphics Interchange Format) is a format for storing graphic images, capable of holding compressed data without loss of quality in a format with up to 256 colors. This format was developed in 1987 (GIF87a) by CompuServe for transmitting raster images over networks. In 1989, the format was modified (GIF89a), adding support for transparency and animation.

GIF format files have a block structure. These data blocks always have a fixed length (or it depends on certain flags), so it is virtually impossible to make mistakes about where each block is located. The structure of the simplest non-animated GIF image in GIF89a format is:

Steganografi i GIF

Of all the blocks in the structure, we will be interested in the global palette block and the parameters responsible for the palette:

  • CT — the presence of a global palette. If this flag is set, the global palette must start immediately after the logical screen descriptor.
  • Size — the size of the palette and the number of colors in the image. The values of this parameter are:

Size
Number of colors
Palette size, bytes

7
256
768

6
128
384

5
64
192

4
32
96

3
16
48

2
8
24

1
4
12

0
2
6

Encryption Methods

The following methods will be used for encrypting messages in image files:

  • LSB Method (Least Significant Bit)
  • Palette Padding Method

LSB Method — a widely used steganography method. It involves replacing the least significant bits in the container (in our case, the bytes of the global palette) with bits of the hidden message.

Programmet bruker de to siste bitene i byte i den globale paletten. Dette betyr at for et 24-bits bilde, hvor fargepaletten representerer tre byte for henholdsvis rød, blå og grønn, vil hver fargekomponent endres med maksimalt 3/255 grader etter innsetting av meldingen. Denne endringen vil først og fremst være usynlig eller vanskelig å oppdage for det menneskelige øye, og for det andre, vil den ikke være merkbar på lavkvalitets utdataenheter.

Mengden informasjon vil være direkte avhengig av størrelsen på bildepaletten. Siden den maksimale størrelsen på paletten er 256 farger, og hvis to biter av meldingen skrives inn i hver fargekomponent, vil den maksimale lengden på meldingen (ved maksimal palett i bildet) være 192 byte. Etter innsetting av meldingen i bildet, endres ikke filstørrelsen.

Metoden for palettutvidelse, fungerer kun for GIF-strukturen. Den vil være mest effektiv i bilder med små paletter. Hovedideen er at den øker størrelsen på paletten, og gir dermed tilleggsplass for å skrive nødvendige byte på plassen til fargebyte. Hvis man tar hensyn til at minimumsstørrelsen på paletten er 2 farger (6 byte), kan den maksimale størrelsen på den innsettingen meldingen være 256×3–6=762 byte. Ulempen er lav kryptosikkerhet; den innsettingsmeldingen kan leses ved hjelp av enhver tekstredigering, med mindre meldingen er gjenstand for ytterligere kryptering.

Praktisk del

Programdesign

Alle nødvendige verktøy for implementering av krypterings- og dekrypteringsalgoritmer vil være i pakken com.tsarik.steganography. Denne pakken inkluderer grensesnittet Encryptor med metodene encrypt og decrypt, klassen Binary, som gir muligheten til å jobbe med bit-arrays, samt unntaksklasser UnableToEncryptException og UnableToDecryptException, som skal brukes i metodene til grensesnittet Encryptor i tilfelle feil under koding og dekoding.

Hovedpakken i programmet com.tsarik.programs.gifed vil inkludere kjørbar klasse for programmet med en statisk metode main, som gjør det mulig å kjøre programmet; en klasse som lagrer programmets parametere; og pakker med andre klasser.

Implementeringen av algoritmene vil bli presentert i pakken com.tsarik.programs.gifed.gif klassene GIFEncryptorByLSBMethod og GIFEncryptorByPaletteExtensionMethod. Begge disse klassene vil implementere grensesnittet Encryptor.

Basert på strukturen til GIF-formatet kan man utarbeide en generell algoritme for å integrere meldinger i bildets palett:

Steganografi i GIF

For å bestemme tilstedeværelsen av en melding i bildet, må man legge til en sekvens av biter i begynnelsen av meldingen, som dekoderen først leser og sjekker for gyldighet. Dersom den ikke stemmer, anses det at det ikke finnes noen skjult melding i bildet. Deretter må lengden på meldingen angis. Så selve teksten i meldingen.

Klassediagrammet for hele applikasjonen:

Steganografi i GIF

Implementeringen av programmet

Implementeringen av hele programmet kan deles inn i to komponenter: implementering av krypterings- og dekrypteringsmetodene for grensesnittet Encryptor, i klassene GIFEncryptorByLSBMethod og GIFEncryptorByPaletteExtensionMethod, og implementeringen av brukergrensesnittet.

La oss se nærmere på klassen GIFEncryptorByLSBMethod.

Steganografi i GIF

Feltene firstLSBit og secondLSBit inneholder bitnumrene for hver byte i bildet, hvor meldingen skal lagres og hvorfra den skal leses. Feltet checkSequence lagrer kontrollsekvensen av biter for å sikre gjenkjenning av den innebygde meldingen. Den statiske metoden getEncryptingFileParameters returnerer parametrene for den angitte filen samt egenskapene til den potensielle meldingen.

Algoritmen til metoden encrypt klassen GIFEncryptorByLSBMethod:

Steganografi i GIF

Og koden:

@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");
	}
	
	// les bytes fra inndatafilen
	byte[] bytes = new byte[(int)in.length()];
	InputStream is = new FileInputStream(in);
	is.read(bytes);
	is.close();
	
	// sjekk format
	if (!(new String(bytes, 0, 6)).equals("GIF89a")) {
		throw new UnableToEncodeException("Input file has wrong GIF format");
	}
	
	// les palettstørrelse fra de tre første bitene i den 10. byte fra filen
	byte[] b10 = Binary.toBitArray(bytes[10]);
	byte bsize = Binary.toByte(new byte[] {b10[0], b10[1], b10[2]});
	
	// kalkuler antall farger og mulig meldingslengde
	int bOrigColorCount = (int)Math.pow(2, bsize+1);
	int possibleMessageLength = bOrigColorCount*3/4;
	int possibleTextLength = possibleMessageLength-2; // en byte for sjekk og en byte for meldingslengde
	
	if (possibleTextLength < text.length()) {
		throw new UnableToEncodeException("Text is too big");
	}
	
	int n = 13;
	
	// skriv sjekkssekvens
	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 tekstlengde
	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 melding
	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 utdatafilen
	OutputStream os = new FileOutputStream(out);
	os.write(bytes);
	os.close();
}

Algoritmen og kildekoden for 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");
	}
	
	// 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);
}

Implementasjonen av klassen GIFEncryptorByPaletteExtensionMethod vil være lik, men metoden for lagring/lesing av informasjon er forskjellig.

I klassen MainFrame er det beskrevet wrapper-metoder: encryptImage(Encryptor encryptor) og decryptImage(Encryptor encryptor), som behandler resultatene fra grensesnittmetodene Encryptor og håndterer interaksjonen med brukeren, det vil si åpner dialoger for filvalg, viser feilmeldinger osv.; samt andre metoder: openImage(), som gir brukeren mulighet til å velge et bilde, exit(), som avslutter programmet. Disse metodene kalles fra Actionde tilsvarende menyvalgene. I denne klassen er det også implementert hjelpe-metoder: createComponents() — opprettelse av skjemaets komponenter, loadImageFile(File f) — lasting av bilde i en spesiell komponent fra en fil. Implementasjonen av klassen GIFEncryptorByPaletteExtensionMethod er lik implementasjonen av klassen GIFEncryptorByLSBMethod, hovedforskjellen er måten meldingens byte leses og skrives fra paletten.

Programmets arbeid

LBS-metoden

Anta at det finnes et bilde som dette:

Steganografi i GIF

I dette bildet består paletten av 256 farger (slik Paint lagrer det). De første fire fargene: hvit, svart, rød, grønn. De andre fargene er svarte. Bitsekvensen i den globale paletten vil være som følger:

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

Steganografi i GIF

Etter implementeringen vil de understrekede bitene bli erstattet med bitene fra meldingen. Det resulterende bildet skiller seg nesten ikke fra originalen.

Original
Bilde med implantatmelding

Steganografi i GIF
Steganografi i GIF

Metoden for palettutvidelse

Når man åpner et bilde som har en melding plassert med denne metoden, kan man oppdage et slikt bilde:

Steganografi i GIF

Det er klart at denne metoden ikke er egnet for fullt utspionering, og kanskje krever ekstra kryptering av meldingen.

Kryptering/dekryptering i animerte bilder fungerer som i vanlige stillbilder, samtidig som animasjonen ikke bli påvirket.

Brukte kilder:

Last ned:

Kilde: habr.com

Kjøp pålitelig hosting for nettsteder med DDoS-beskyttelse, VPS VDS-servere 🔥 Kjøp pålitelig hosting for nettsteder med DDoS-beskyttelse, VPS VDS-servere | ProHoster