📜 ⬆️ ⬇️

GIF Steganography

Introduction


Greetings.
Not so long ago, when I was studying at the university, there was a term paper on the discipline “Software methods of information protection”. According to the task, it was necessary to make a program embedding the message in the GIF format files. Decided to do 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 - the format for the exchange of images) - the storage format of graphic images, is able to store compressed data without loss of quality in the format up to 256 colors. This format was developed in 1987 (GIF87a) by CompuServe for transferring 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 certain flags), so it’s almost impossible to err where the block is. The structure of the simplest non-animated GIF-format GIF89a:



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

SizeNumber of colorsPalette size, byte
7256768
6128384
five64192
four3296
3sixteen48
2eight24
onefour12
026


Encryption methods

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

The LSB method is a common method of steganography. It consists in replacing the last significant bits in the container (in our case, the global palette bytes) 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 embedding the message in it, each component of the color will change by a maximum of 3/255 gradation. Such a change, firstly, will be imperceptible or difficult for the human eye, and secondly, it will not be distinguishable on low-quality output devices.

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

A palette expansion method that works only for the GIF structure. It will be most effective in images with a palette of small sizes. Its essence is that it increases the size of the palette, thereby giving additional space to record the necessary bytes in place of bytes of colors. If we consider that the minimum size of the palette is 2 colors (6 bytes), then the maximum size of the message being injected can be 256 × 3–6 = 762 bytes. The disadvantage is low crypto-security; 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 com.tsarik.steganography package. This package includes the Encryptor interface with the encrypt and decrypt methods, the Binary class, which provides the ability to work with arrays of bits, as well as the exception classes UnableToEncryptException and UnableToDecryptException , which should be used in Encryptor error methods for encoding and decoding errors, respectively.

The main program package com.tsarik.programs.gifed will include a program class being run with a static main method that allows it to run the program; class that stores the parameters of the program; and packages with other classes.

The implementation of the algorithms themselves will be represented in the package com.tsarik.programs.gifed.gif classes GIFEncryptorByLSBMethod and GIFEncryptorByPaletteExtensionMethod . Both of these classes will implement the Encryptor interface.

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 an image, it is necessary 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, it is considered that there is no hidden message in the image. Next you need to specify the length of the message. Then the text of the message itself.

The class diagram of the entire application:



Implementation of the program

The implementation of the entire program can be divided into two components: the implementation of the Encryptor interface encryption and decryption methods, in the GIFEncryptorByLSBMethod and GIFEncryptorByPaletteExtensionMethod , and the implementation of the user interface.

Consider the class GIFEncryptorByLSBMethod .



The firstLSBit and secondLSBit contain the bit numbers of each byte of the image into which the message is to be entered and from where. The checkSequence field stores a check bit sequence to ensure recognition of the embedded message. The static getEncryptingFileParameters method returns the parameters of the specified file and the characteristics of the potential message.

The algorithm method encrypt class GIFEncryptorByLSBMethod :



And its code:
 @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 for the decrypt method of the class 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 GIFEncryptorByPaletteExtensionMethod class will be similar, only the method of storing / reading information is different.

The MainFrame class describes “wrappers” encryptImage(Encryptor encryptor) : encryptImage(Encryptor encryptor) and decryptImage(Encryptor encryptor) , processing the results of Encryptor interface methods and interacting with the user, that is, opening a file selection dialog, showing error messages, etc. ; as well as other methods: openImage() , allowing the user to select an image, exit() , exiting the application. These methods are called from the Actions of the corresponding menu items. In this class, the following additional methods are implemented: createComponents() - creating form components, loadImageFile(File f) - loading an image into a special component from a file. The implementation of the GIFEncryptorByPaletteExtensionMethod class GIFEncryptorByPaletteExtensionMethod similar to the implementation of the GIFEncryptorByLSBMethod class, the main difference lies in the way in which you write and read message bytes from a palette.

Work program


LBS method

Suppose 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 in the global palette will be as follows:

111111 11 111111 11 111111 11 000000 00 000000 00 000000 00 111111 11 000000 00 000000 00 000000 00 111111 11 000000 00 ...



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

OriginalImage with embedded message
OriginalImage with embedded message


Palette expansion method

Opening the image in which the message is placed by this method, you can find the following picture:



It is clear that for a full-fledged espionage activity, this method will not work, and it may require additional encryption of the message.

Encryption / decryption in animated images works, as in normal static images, and the animation is not disturbed.

Sources used:


Download:

Source: https://habr.com/ru/post/128327/


All Articles