Steganography Howto, Compsci 100, Fall 2010

In developing and writing your code, please use the classes provided as part of this assignment as a starting point. You'll need a basic understanding of pictures/pixels and text/bytes to hide and extract pictures and images, respectively. Details on these and other aspects of the code you'll develop are provided here as are some hints in developing the programs.

There are five sections in this howto: an overview section that provides background on images and pixels and how the programs work; an explanation of pixels and the details you'll need for HideImage and ExtractImage; an explanation of hiding text and bit-chunks you'll need for HideText and ExtractText; details about the design of the code for each class you must write; and a caveat section which you should review before coding and while coding;

Overview and Development

First we'll provide details about how pictures are comprised of pixels represented in the programs you'll write by the java.awt.Color class.

A brief big-picture look at the ideas used in these programs is provided here to help guide your reading of the more detailed explanations that follow.

The key to hiding source image or text in a target image is to use some bits of each pixel in the target image to store some bits from the hidden source. When hiding source text, you'll hide as many characters as possible --- except for really long text or really small target images you'll be able to hide the entire text without substantially degrading the image. When hiding a source image, you'll reduce the quality of the source image and then hide this reduced quality image.

For example, the class/program ClearBitsFromImage clears the low-order bits from each (R,G,B) value in each pixel. The table below shows the gradual degradation of an image as the number of cleared bits increases. It's pretty amazing that in clearing seven of the eight bits in each RGB value of each pixel (image on far right below) that the original image is still discernible.

Original Clear 2 bits Clear 4 bits Clear 6 bits Clear 7 bits
Picasso image cleared bits at a time
Duke Chapel cleared bits at a time

Development of Hiding Source in Target

You should be sure that each of the programs/classes you write works before proceeding to the next. To determine if HideImage works you may need to implement ExtractImage, although you can gain some confidence that hiding an image works by using the program PixelPng which prints the RGB values of the first several (user-prompted) pixels in an image.

For example, what does it mean to use two bits in each RGB value to hide an image? You'll clear the low-order two bits of each RGB value in each pixel of the target, e.g., the hidden image will start looking like the image second from the left in the table-above. However, instead of leaving these two bits as 0 (or clear) you'll store two bits from the source image.

To store source information in only two bits you'll need to scale or reduce the the source information. You'll scale or reduce each RGB value of the source image to fit into two bits, e.g., you'd store the image second from the right in the table above if that was the source image since clearing six bits essentially means taking an eight-bit RGB value such as 255 (11111111) and reducing it to a two-bit value of 3 (11) --- dividing by 64 = 26. Similarly the eight-bit RGB 128 would be reduced to the three-bit value 2 (dividing again by 64), but 127 would be reduced to 1. More details are found below.

Text/ASCII

When hiding text, you'll break each character into its constituent pieces. Whereas pictures are comprised of pixels each of which is comprised of three RGB values, each character in a string/text is made of bytes (where a byte is eight bits). We'll break strings into eight-bit chunks called bytes since this works even when Unicode is used for a character. Unicode represents a character with 16 bits, but as explained below, breaking a String into bytes will make things work even when Unicode characters are used. You'll hide each byte from the hidden source text in the cleared bits of the target image similarly to how your code hid pixels when hiding a source image.

Pixels

Images are made of individual picture elements or pixels. Although there are different color models, including gray-scale, RGB, and CMYK, we'll be using the standard RGB or red, green, blue color model. You can find details in the Wikipedia RGB entry. For the purposes of this assignment you need to understand that each pixel is made of a red, green, and blue value, and each of these values is an integer between 0 and 255 (inclusive). For example, you can extract the values of a pixel represented by the java.awt.Color class we are using as shown in the code below. This code illustrates how to construct a Color object representing a pixel from RGB values and how to extract the individual RGB values from a pixel/Color. java.awt.Color c1 = new Color(255,0,0); // this is red java.awt.Color c2 = new Color(0,255,0); // this is green java.awt.Color c3 = new Color(0,0,255); // this is blue java.awt.Color c4 = new Color(0,0,0); // this is black java.awt.Color c5 = new Color(255,255,255); // this is white java.awt.Color cc = Color.magenta; int vr = cc.getRed(); // 255 int vg = cc.getGreen() // 0 int vb = cc.getBlue(); // 255

The program ClearBitsFromImage shows the standard way you'll loop over every pixel in a image, processing each pixel to create a new image. You'll use similar code in all the classes you write to hide and extract text and images. The code in HideImage has been started for you as well, it's similar to the clearing-bits code. The method reduce from ClearBitsFromImage constructs a new pixel/Color, you'll write a modified but similar method for the other classes. The basic idea is to process every pixel, creating a new pixel for the image being produced (e.g., when hiding text or an image or extracting an image). Sometimes the methods you write will have multiple parameters, e.g., a source and target pixel as well as the number of bits in writing HideImage.

Here's the relevant code from ClearBitsFromImage:

public Color reduce(Color c) { int r = c.getRed(); int g = c.getGreen(); int b = c.getBlue(); int factor = (int) Math.pow(2, BITS_TO_CLEAR); r = (r / factor) * factor; g = (g / factor) * factor; b = (b / factor) * factor; return new Color(r, g, b); } public Picture clear(Picture pic) { int width = pic.width(); int height = pic.height(); Picture nextPic = new Picture(width, height,"reduced-by-"+BITS_TO_CLEAR); for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { Color sc = pic.get(i, j); Color cc = reduce(sc); nextPic.set(i, j, cc); } } return nextPic; }

If you change the body of method reduce as shown in the code fragment below, the effects are shown by the images below the code. Magenta is a combination of maximal red and blue values, which is why the rightmost image has a magenta hue.

public Color reduce(Color c) { return new Color(c.getRed(), VALUE, c.getBlue()); }

Original VALUE=255 (all green) VALUE=0 (no green)

Hiding an Image

To hide an image using two bits per RGB value/pixel you'll need to clear two bits in each of the R, G, and B values of the target pixels and store two bits from the source image R,G,B, values in this cleared space. Thinking and reasoning with decimal numbers, for example, if you cleared two digits from the number 1578 you'd get 1500. You can then store 23 in the cleared space yielding 1523. The target value of 1578 has been replaced by 1523 in the newly created image, and the value 23 can be extracted from 1523 by arithmetic operations.

In the diagrams below the process of hiding a source eight-bit RGB value in the target value is explained. You don't need to use binary values in any calculations for this program, the binary values are shown to provide a deeper understanding of the process of hiding and extracting image values. When changing pixels you'll change each of the three eight-bit R, G, and B values. Only one eight-bit value is shown in the pictures below.

first stage The target value in which we'll hide information is 179. This could be either the R, G, or B value from a Color/pixel. We're using two bits to store hidden information, so we clear two bits by dividing and multiplying by 4 = 22 (e.g., see method reduce above).
The source value we're hiding is 142. Since we have two bits in which to hide the value we must hide a two-bit value: either 0, 1, 2, or 3. To determine what value to hide we calculate 142/64 = 2. We choose 64 since we want to hide 2-bits which can represent four values and 256/4 = 64 (or you can think of hiding two-bits as using 64 because it's equal to 2(8-2) = 26). If we were using one bit to hide a value, we'd divide by 128 since we can represent two values with one bit and 256/2 = 128. If we we had 3 bits to hide information in we'd divide by 32 since we can represent eight values with three bits and 256/8 = 32. second stage
third stage The target value of 179 has been replaced by 178 in the new image --- this is the value used in the constructed image that represents both the target image bits (176) and the hidden, source image bits (2). The bits in red are the bits we're hiding.
When we're extracting the hidden image we need to know that two bits have been used to hide a value. We extract the hidden value by calculating 178 % 4 = 2 since 178 = 44*4+2. The value used to create the corresponding 8-bits of a pixel in the reconstructed hidden image is 2*64 = 128. We rescale by 64 since that's the number we divided by when hiding the original source value of 142. In our newly constructed/extracted image We've now got 128 as the eight-bit value that was originally 142 in the source image before the 142 was scaled down and hidden. fourth stage

Information is stored using binary values, but you don't need to use binary arithmetic in doing the simple arithmetic operations that are part of inserting and extracting values/bits. Although you don't need to use the binary representations in the code you write, understanding the binary may help when you debug. However, you can do this entire assignment using only decimal/base-10 arithmetic operations.

Suppose a source pixel/color is represented by the RGB triple (57, 108, 213). In base two this triple is (00111001, 01101100, 11010101). What happens if you want to store/hide this source pixel of (57, 108, 213) using two bits? If you are using two bits to store this value you must reduce each value by a factor of 64 so that it's one of four different values since you can represent four values with two bits: 0, 1, 2, 3. Reducing (57, 108, 213) by dividing by 64 yields (1, 1, 3). These are the values that would be hidden. When they're extracted, the values would be multiplied by 64 yielding (64, 64, 192). As a result, the original source pixel of (57, 108, 213) is hidden and then extracted as (64, 64, 192).

Alternatively, suppose the RGB triple (57, 108, 213) is a target pixel/value in which information will be hidden using two bits. To clear two bits you divide and multiply by 4 as shown in the diagram above, e.g., you replace 57 by 57/4*4 = 56. This results in replacing (57, 108, 213) by (56, 108, 212). In binary these values are (00111000, 01101100, 11010100). Note that the rightmost two bits of each value are zero -- they've been "cleared". In the image you create you don't store these cleared values in a pixel, you use them to add the hidden information from the source, and then you store them in a pixel.

You'll use regular decimal/arithmetic operations to reduce values unless you have a good understanding of bit operators. You can convert between base-10 and base-2 using the Google query 57 in base 2 or 0b00011011 in base 10. You can debug by using the static Integer.toBinaryString(i) method that returns a string representing the base two/binary version of an int value. You can examine individual pixels using the PixelPng class.

HideImage Development

You'll need to complete method hide that works on pixels. The R,G,B values have been obtained and cleared of the specified number of bits, e.g., to clear 1578 to 1500 in base 10 you could simply divide by 100 and then multiply by 100: that clears two decimal digits. You do the same thing with 4 instead of 100 to clear two bits (binary digits).

If you're clearing two bits You can store any of the values 0, 1, 2, or 3 in the two cleared bits since you can represent four values with two bits. For each RGB value in each pixel of the source (to be hidden) image you divide the RGB value by 64 since the original values were in the range 0-255 and you need to map them to 0-3. In general, you scale down by dividing each R,G,B, value by 256/(2bits).

You'll likely need to write ExtractImage to see that you've hidden an image successfully. When extracting RGB values from each pixel of an image that hides two-bits per pixel you'll get 0, 1, 2, or 3. When creating a new image, scale these by multiplying by 64 to get a value in the range 0-255. You can use the PixelPng class to help debug --- it prints (R,G,B) pixel values from an image.


Text

In computing text is represented using characters and each character is represented by an ASCII or Unicode value. Java uses Unicode to represent each character, and Unicode uses 16 bits/character. However, we'll ignore the ASCII/Unicode distinction and treat text/strings as a sequence of bytes, where bytes are eight bit values. The text stored in files on computers is typically stored in bytes, and two bytes can be combined to create a Unicode character when that's what's actually stored, just as four bytes can be combined to create an int when that's what is stored. For this suite of programs we'll treat Strings as simply a sequence of bytes. This simplifies the process of hiding text.

Fortunately, the String class provides the getBytes method as shown below. Using printf makes it simple to print a value as either a character or an integer to illustrate what's going on.

Code Output
String s = "abcd efg"; byte[] array = s.getBytes(); for(byte b : array){ System.out.printf("%c %d %s\n", b,b,Integer.toBinaryString(b)); }
 a 97 1100001
 b 98 1100010
 c 99 1100011
 d 100 1100100
   32 100000
 e 101 1100101
 f 102 1100110
 g 103 1100111

To hide text in an image, you'll need to convert an entire file to a sequence of bytes. We can do that simply in Java with the following code that leverages the power of the java.util.Scanner class.

String filename = ... //somehow get a filename, e.g., with JFileChooser Scanner scan = new Scanner(new File(filename)); // create scanner String all = scan.useDelimiter("\\Z").next(); // read entire file byte[] text = all.getBytes(); // convert to bytes

Once you're converted a file to a sequence/array of bytes, you'll hide each of these bytes using by clearing either one or two bits of each RGB value in each pixel of the target image in which the text will be hidden. You clear either one or two bits using techniques and arithmetic operations described above in the section on hiding images. However, when hiding text, you don't reduce the information content of the text by scaling as was done with the hidden image. If the target image doesn't have enough pixels in which to hide all the text, hide as much as can fit. This means you'll need to break the text into a sequence of 1- or 2-bit chunks to hide the entire text. More on this below.

The process of hiding text in an image is similar to the process of hiding one image in another, but in the image hiding code we made the assumption that both images were the same size in width and height. Thus one pair of nested loops was sufficient to process both source and target pixels.

In hiding text, you'll need to either loop over pixels in the target image or bytes/bits in the source text. Do not try to loop over both at the same time. For example, if you loop over pixels in the target image, you'll need to hide different bits from the source text in each RGB value of each pixel. If these bits are in an array, you can access them simply by indexing into the array and incrementing the index after each access. It's recommended that you store all bytes in an array that's globally accessible (or a parameter) and then loop over each pixel storing 1 or 2 bits in each (R,G,B) value of each pixel. The recommendation is to loop over pixels, keeping a counter/index into the byte array of text.

Getting bits from a byte

When hiding text, you'll store either one- or two-bit chunks of text as specified by the user. One easy way to access and extract one or two bits at a time is to break the entire array of bytes into a larger array of appropriate size chunks. For example, when using two-bit chunks in hiding text whose size is 200 bytes you could first create an array of 800 bytes where each of the original 200 bytes is broken into four different two-bit chunks. You'll need to extract these text bit-chunks from each byte. Perhaps the simplest way to extract the chunks is to use code similar to the following which prints each decimal digit of a number from least significant digit to most significant using mod/div operators -- this code shows how to use the arithmetic operators with base-10 digits.

code output
int value = 12345; System.out.printf("value of %d backwards: ",value); for(int k=0; k < 5; k++) { int lsd = value % 10; value /= 10; System.out.printf("%d",lsd); } System.out.println();

value of 12345 backwards: 54321

Using values of 2 or 4 rather than 10 when extracting digits in the loop above would result in getting eight one-bit chunks or four two bit chunks/byte, respectively. Of course you'll need to change the number of times the loop iterates, e.g., eight or four times depending on whether you're extracting 1 or 2 bits, respectively (the loop above iterates five times because the int has five digits). However, the code above results in extracting digits (or bits) in order from least to most significant just as the 5 is printed first in the code fragment above. You'll need to somehow reverse this order, or extract differently, so that your code will store the most significant bit-chunk from each byte first, then the next most significant, and finally the least significant bit(s). You could, of course, store values in an array and then reverse the order of the values.

For example, if you're using two bits per RGB value to hide the value whose binary representation is given by 01110001 (decimal 113) you'll need to store the two-bit chunks in the order 01, 11, 00 and 01. If the first three chunks are stored in one pixel, the last chunk, 01 will be hidden/stored in the next pixel. Again, one simple way to do this is to store each two-bit chunk in an array and index them one-at-a-time.

One way to make the process of getting bits from a byte simpler is to create a new byte-array from the one obtained from String.getBytes. The new array would be either four or eight times bigger depending on whether you're using two or one bit-sized chunks, respectively. This method is fine to use, though more memory intensive than avoiding the creation of the additional array. However, creating the second array makes it much simpler to iterate over the array when creating new pixels/Colors from the target image in which to store the chunks representing the hidden message. You can also extract bits using arithmetic operations or bit-shifting operations.

For example, you could modify the extract-bytes-from Scanner code above as follows:

Scanner scan = new Scanner(new File(filename)); // create scanner String all = scan.useDelimiter("\\Z").next(); // read entire file byte[] text = all.getBytes(); // convert to bytes int bits = 2; // could be 1, user-entered? byte[] chunks = chunkize(text,bits); byte[] chunkize(byte[] text, int bits){ byte[] ret = new byte[text.length*8/bits]; // room for all chunks int index = 0; // loop over bytes in text, extracting/storing chunks into ret return ret; }

Byte/Int Warnings

If you use mod/div, e.g., % and / operators, to extract "digits" from a byte, you'll need to be careful about conversion from byte to int and you'll need to watch out for negative byte values. In some text files, e.g., french.txt that's given to you, a byte will be negative. This is because the first bit will be 1, which indicates a negative value. In Java the value-range for bytes is -128..127, but we really want 0..255 for the values used to store bits in each pixel.

If you're using % and / to extract bits/digits as shown above, e.g., you're using % 2 or % 4 to extract 1 or 2 bits, respectively, you'll want to extract these from an int not from a byte. If you use a byte you'll have problems with negative values when using the % operator.

To fix this problem, access each byte in turn as described above. But don't use % and / on the byte value, convert the byte to an int as follows:

byte b = // get value from byte array int value = 0xFF & b; newArray[index] = (byte) (value % 4); // don't use 4, use a variable value /= 4; // don't use 4, use a variable If you use this construct, e.g., use an int value created from the byte use the bit-wise & operator with 0xFF, problems with negative values and bad colors should go away.

Extracting Text from an Image

When extracting text from an image you'll get the low order one or two-bits from each (R,G,B) value from each pixel. You'll need to create characters or bytes from these bits. If you hide the bits from most-to-least significant, then you'll extract them in this order. For example, consider extracting the following sequence of two-bits from an image: 01 10 11 01.

You'll need to write code that creates the eight-bit byte 01101101 from these two-bit chunks. To see how to do this, consider creating a number from the array of digits below which prints the value 23481.

int[] digits = {2,3,4,8,1}; int result = 0; for(int d : digits){ result = result*10 + d; } System.out.printf("%d\n",result); You can write similar code to create a byte from bit-chunks: multiplying and adding the chunks but multiplying by 2 or 4 depending on whether you're adding 1 or 2 bit chunks, respectively.

Once you have a byte, you can cast it to the type char and append it to a StringBuilder object you maintain to represent the text being extracted.

Code and Class Details

The class ClearBitsFromImage illustrates one way to organize code for the classes you write in this assignment. One idea that can help as you develop your programs is to write a method that returns a new Picture based on the parameters to the method. In ClearBitsFromImage the method clear does this -- and you can see its use in the main method. Writing a method to alter each pixel, as illustrated by the method reduce is also a good idea. Developing the code by isolating functionality in a method helps when debugging and checking that your code is working properly.

Starter classes for HideImage and ExtractImage are provided to take care of some of the boilerplate code in opening files.

When developing ExtractText it will help to write a method that returns the String representing the extracted text. Writing such a method will help as you try to find all the images in a directory that contain text: part of writing StegoBenchmark. It's not a good idea for the method that extracts text to simply print the text. If you return the text, you can print the string returned if you want to, e.g., for testing/debugging. But you can also pass the string to other methods, e.g., for determining if the string represents text or is simply gibberish.

This means three of the four hide/extract classes you write will have a method that returns an Picture object. You should make sure that you call the show method on this object since that pops up a frame in which the image appears and the frame has a Save-option that allows the user to save the image. When writing ExtractText you won't generate a new Picture, you'll generate the String extracted from an image. You should likely print this String in the main method you write, but you won't print it when you call the method from StegoBenchmark.

StegoBenchmark

The idea in writing the benchmark program is to determine which images in a directory of images store text. You'll need to try both one- and two-bit parameters to see which of them (if any) results in extracting text from an image. To determine if the String extracted from an image is text you'll need to write a method in the benchmark code that returns a boolean value indicating whether a String is text. You should do this by using properties of text: the average word length is one of the properties you'll use. You can use the Google query "average word length" to determine what the average word length is in English and other languages. You can find words in String using the String.split method, e.g., the code below breaks a String on white-space into an array of "words" -- this is typically how we break text represented by a String into its component words. String s = "the quick brown fox"; String[] all = s.split("\\s+"); // all contains "the" "quick" "brown" "fox" In addition to using average word length to determine if a String represents text you might look at the characters to make sure that they're actually letters and not gibberish. The static method Character.isLetter returns true if its parameter is a letter (think 'a'-'z', upper or lowercase). Of course you can also use a dictionary of English words, but for identifying text this is probably not necessary.

Caveats

Here's a list of things to keep in mind when developing these programs. These caveats can lead to subtle and hard-to-find bugs if you're not careful in avoiding them.

  1. Save images by supplying a ".png" suffix. The Picture class supports saving with either a .jpg or a .png suffix. If you don't supply a prefix .png is used by default. However, .png is a lossless compression scheme whereas .jpg is lossy -- information is lost when compressing with the jpg format. You need to keep all the information when you hide text or an image so you must use a .png suffix when saving images using the Picture class.

  2. Except when using the String.getBytes method which returns an array of bytes, use int values. Bytes in Java are signed, i.e., have values between -128 and 127.

  3. Make sure that the order in which you store bit chunks when hiding text, which should be from most- to least-significant, is the same order in which you reconstruct the text. Your program ExtractText must be able to extract text from the images in which your program HideText hid the information, but ideally your program should be able to extract text from every image in which the text is stored most-significant-bits first, e.g., from images in which your classmates' code hid text.

  4. Your code should create new Picture objects in HideImage, ExtractImage and HideText. Be sure that each of these classes has a main method that allows the user to hide/extract information. The Picture object created should be made visible to the user by calling its show method, this allows the user to save the image. Each of these classes and ExtractText should have a main method that allows them to run, prompting the user for files and bits as appropriate.

  5. When extracting text from an image, don't process any eight-bit chunks that are all zero, i.e., if a constructed byte/int has the value 0, don't create a character from it that you concatenate onto the String you're building with the code. Just skip such values. On a related note --- when hiding text you may run out of pixels in which to hide text, and you may run out of text to hide. In the latter case, you should hide bit-chunks (one- or two-bit as chosen by the user) with the value zero. These won't result in any characters being extracted because your code will skip zeros. But you won't get gibberish by extracting when nothing was hidden. In summary: process every pixel when hiding text; when there is no text to hide, hide the value zero.