GIF steganography

Introduction


Greetings.
Not so long ago, when I was studying at the university, there was a coursework in the discipline "Software methods for information protection." On assignment, it was necessary to create a program that embeds the message in GIF files. I decided to do it 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 (Eng. Graphics Interchange Format - a format for exchanging images) is a format for storing graphic images that can store compressed data without loss of quality in a format of up to 256 colors. This format was developed in 1987 (GIF87a) by CompuServe to transmit raster images over networks. In 1989, the format was modified (GIF89a), support for transparency and animation was added.

GIF files have a block structure. These blocks always have a fixed length (or it depends on some flags), so it’s almost impossible to make a mistake about where which block is located. The structure of the simplest non-animated GIF-image in GIF89a format:



Of all the blocks in the structure, in this case 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, then immediately after the descriptor of the logical screen, the global palette should begin.
  • Size- the size of the palette and the number of colors in the picture. Values ​​of this parameter:

SizeNumber of colorsPalette size, bytes
7256768
6128384
564192
43296
31648
2824
1412
026


Encryption methods

As methods of encrypting messages in image files will be used:
  • LSB Method (Least Significant Bit, Least Significant Bit)
  • Palette Addition Method

The LSB method is a common steganography method. It consists in replacing the last significant bits in the container (in our case, bytes of the global palette) with the bits of the message to be hidden.

The program will use the last two bits in bytes of the global palette as part of this method. This means that for a 24-bit image, where the color of the palette is three bytes for red, blue, and green, after the message is embedded in it, each color component will change by a maximum of 3/255 gradations. Such a change, firstly, will be imperceptible or difficult to notice for the human eye, and secondly, it will not be distinguishable on low-quality information output devices.

The amount of information will directly depend on the size of the image palette. Since the maximum size of the palette is 256 colors, and if you write two bits of the message into the component of each color, then the maximum length of the message (with the maximum palette in the image) is 192 bytes. After embedding the message in the image, the file size does not change.

Palette extension methodworking only for GIF structure. It will be most effective in images with a small palette. Its essence is that it increases the size of the palette, thereby giving additional space for writing the necessary bytes in place of the bytes of colors. If we consider that the minimum size of the palette is 2 colors (6 bytes), then the maximum size of the embedded message can be 256 × 3-6 = 762 bytes. The disadvantage is low cryptographic protection; you can read the embedded message using any text editor if the message has not been subjected to additional encryption.

Practical part


Program design

All the necessary tools for implementing encryption and decryption algorithms will be in the package com.tsarik.steganography. This package includes an interface Encryptorwith methods encryptand decrypt, a class Binarythat provides the ability to work with bit arrays, as well as exception classes UnableToEncryptExceptionand UnableToDecryptException, which should be used in interface methods Encryptorin case of encoding and decoding errors, respectively.

The main program package com.tsarik.programs.gifedwill include a start-up class of the program with a static method mainthat allows you to run the program; a class that stores program parameters; and packages with other classes.

The implementation of the algorithms themselves will be presented in the package by com.tsarik.programs.gifed.gifclasses GIFEncryptorByLSBMethodandGIFEncryptorByPaletteExtensionMethod. Both of these classes will implement the interface Encryptor.

Based on the structure of the GIF format, you can create a general algorithm for embedding a message in the image palette:



To determine the presence of a message in the image, you need to add a certain sequence of bits to the beginning of the message, which the decoder reads first and checks for correctness. If it does not match, then it is considered that there is no hidden message in the image. Next, you must specify the length of the message. Then the text of the message itself.

Class diagram of the entire application:



Program implementation

The implementation of the entire program can be divided into two components: the implementation of the encryption and decryption methods of the interface Encryptorin the GIFEncryptorByLSBMethodand classes GIFEncryptorByPaletteExtensionMethod, and the implementation of the user interface.

Consider the class GIFEncryptorByLSBMethod.



The fields firstLSBitand secondLSBitcontain the bit numbers of each byte of the image into which the message should be entered and from where. The field checkSequencestores a check sequence of bits to enable recognition of an embedded message. The static method getEncryptingFileParametersreturns the parameters of the specified file and the characteristics of the potential message. Class

Method Algorithm : And its code:encryptGIFEncryptorByLSBMethod




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


The algorithm and source code of the decryptclass method GIFEncryptorByLSBMethod:



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


The implementation of the class GIFEncryptorByPaletteExtensionMethodwill be similar, only the method of saving / reading information is different.

The class MainFramedescribes “wrapper” methods : encryptImage(Encryptor encryptor)and decryptImage(Encryptor encryptor)that process the results of interface methods Encryptorand interact with the user, that is, open a file selection dialog, show error messages, etc .; as well as other methods: openImage()enabling the user to select an image, exit()exiting the application. These methods are called from the Actioncorresponding menu items. In this class, auxiliary methods are additionally implemented: createComponents()- creating form components, loadImageFile(File f)- loading an image into a special component from a file. Class GIFEncryptorByPaletteExtensionMethodimplementation is similar to class implementationGIFEncryptorByLSBMethod, the main difference is the way you write and read the bytes of the message from the palette.

Program work


LBS Method

Let's say there is such an image:



In this image, the palette consists of 256 colors (this is how Paint preserves). The first four colors: white, black, red, green. The remaining colors are black. The sequence of bits of the global palette will be as follows:

111111 11 111111 11 111111 11 000 000 00 000 000 00 000 000 00 111111 11 000 000 00 000 000 00 000 000 00 111111 11 000 000 00 ...



After the message is inserted, the underlined bits will be replaced by the bits from the message. The resulting image is almost no different from the original.

OriginalImage with embedded message
OriginalImage with embedded message


Palette extension method

Opening the image in which the message was posted using this method, you can see the following picture:



It’s clear that this method will not work for full espionage activity, and may require additional encryption of the message.

Encryption / decryption in animated images works as in regular static images, while the animation is not broken.

Used sources:


Download:

Also popular now: