JavaEnterprise JavaSteganography 101 using Java

Steganography 101 using Java

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Java Programming, Notes # 732





Preface


What in the world is steganography?


This lesson is about steganography, (hidden writing) which should not be confused with stenography (shorthand).


According to Wikipedia,



Steganography is the art and science of writing hidden messages in such a way that no one apart from the intended recipient knows of the existence of the message; this is in contrast to cryptography, where the existence of the message is clear, but the meaning is obscured. The name comes from Johannes Trithemius‘s Steganographia: a treatise on cryptography and steganography disguised as a book on black magic, and is Greek for “hidden writing.”


Steganography in spam


Because my email address is plastered all over the Internet, I receive an average of about 10,000 spam messages each month.  I have recently been receiving spam messages where the names and the prices of certain drugs are made to appear on the screen using steganographic techniques in conjunction with the rendering of HTML text.  In particular, the images of the letters and numbers that appear on the screen are constructed by carefully arranging the positions of very large numbers of very small characters.



(If you view the raw HTML source text, you see that the characters spell out random words and sentences and the name of the drugs don’t appear anywhere in the text.)


In this case, the intended recipient of the secret content (names and prices of drugs) is the recipient of the email message.  The intent of the spammer is to disguise that content in such a way that it cannot be detected and flagged by spam filtering programs.  In other words, the intent of the spammer is to prevent the spam filtering software know of the existence of the spam message.


A cover message


Once again, according to Wikipedia,



Generally a steganographic message will appear to be something else, like a shopping list, an article, a picture, or some other “cover” message.


In this case, the cover message is an email message containing a seemingly innocuous page of raw HTML text.  The secret steganographic message is embedded in the HTML text by adjusting the placement and the size of characters that make up the innocuous raw HTML text.  It is the rendering of the HTML text by the browser that causes the secret content to appear on the screen when the email message is opened by the intended recipient.


Successful so far


Up until now, this has proven to be a successful steganographic technique for use in distributing spam and getting around my spam filtering software.  At this point in time, my spam filtering software is incapable of detecting the hidden content and categorizing the message as spam on the basis of that content.  This success will probably be short lived, however.  A new rule could be incorporated into the spam filter to the effect that an email message containing HTML text that is rendered in a font that is too small to be readable by a human observer is probably spam.


Steganography versus cryptography


As mentioned above, cryptography is often used in situations where the existence of the message is clear, but the meaning of the message is obscured.  In particular, the sender transforms the message into a form that (hopefully) only the intended recipient of the message can decrypt and read.


Steganography is often used in situations where the actual existence of the message needs to be obscured.


Steganography and cryptography can be combined


The message can also be encrypted before it is hidden inside a cover message.  This provides a double layer of protection.  To begin with, encryption may make the existence of the message even more difficult to detect, due to the fact that some encryption techniques cause the patterns of the characters in the encrypted version to be more random than in the original version.  In addition, even if the existence of the encrypted message is detected, it is unlikely that an evesdropper will be able to read the message.


A practical comparison of steganography and cryptography


Let’s take a look at a couple of examples that illustrate the use of steganography and cryptography both separately and together.


Purchasing some books online


As our first example, we will assume that you purchase some Java programming books from an online vendor.  When time comes to pay for the books by supplying your credit card number, you check to make certain that your browser has switched into the secure mode.  Even though it is no secret that you are sending your credit card number to pay for the books, it is your hope that the transmission is encrypted by the browser in such a way that only the online vendor can decrypt the message and read your credit card number.  In this case, there is no need for steganography because there is no need to hide the existence of the message.


A romantic tryst


As our second example, we will assume that you are sending a message to Alice (your best friend’s wife) to arrange a romantic tryst.  In this case, you would probably prefer that neither your wife nor your best friend know of the existence of the message.



 (If they learn of the secret message, they may not continue to be your wife and your best friend for very much longer.)


Consequently it would probably be wise for you to use some form of steganography to hide the existence of the secret message.  It also wouldn’t hurt to encrypt the message using Alice’s public key.  That way, even if the existence of the message is detected, it wouldn’t be possible for your wife or your best friend to read it.



(That may increase your chances of survival even if the existence of the message is detected.)


Embed the message in an image


There are several different forms of steganography that you could use to hide the existence of the message.  One way would be to send your latest set of digital vacation photos to Alice and her husband, and to embed the message in one of the photos.


Having planned for and having written the necessary software for just such a situation in advance, Alice could extract and read your message.  As far as Alice’s husband is concerned, the only thing being transmitted would probably be a set of (probably boring) vacation photos.  The existence of a secret message would certainly not be obvious in viewing the photos.


Alice could embed her answer in a digital photo of her championship Weimaraner (showing the blue ribbon that her dog won at the most recent show).  She could send that picture to you and your wife.  You could extract and read the message.  Your wife could say courteous things about the dog.


Detection is unlikely


As you will see later in this lesson, it is extremely unlikely that either your wife or Alice’s husband would be able to detect the existence of the secret messages simply from viewing the photos.  They would probably need some other clues, (such as otherwise suspicious behavior on the part of you or Alice), in order to even suspect that a secret message is embedded in the photo.


You and Alice could continue to secretly communicate in this manner for as long as you could come up with reasons to exchanges digital photos.



(That approach might get stale after a while and you would need to come up with some other form of steganography to continue the dialog.)


Embedding a message in an image


In this lesson, I will show you how to embed a secret message in an image and how to extract the message from the image in order to convert it back into a readable form.


A merger


This lesson represents a merger between my series of lessons dealing with the manipulation of pixels in images and my series dealing with cryptography and security.  You may find it useful to review the lessons in the following list while studying this lesson:



This lesson builds upon those earlier lessons.  In particular, you will need to understand the code in the lesson entitled Processing Image Pixels using Java, Getting Started before the code in this lesson will make much sense.


You will need a driver program


The lesson entitled Processing Image Pixels Using Java, Controlling Contrast and Brightness provided and explained a program named ImgMod02a.  This program makes it easy to:



  • Manipulate and modify the pixels that belong to an image.
  • Display the processed image along with the original image for a visual comparison of the two.

ImgMod02a serves as a driver that controls the execution of a second program that actually processes the pixels.

The programs that I will explain in this lesson run under the control of ImgMod02a.  In order to compile and run the programs that I will provide in this lesson, you will need to go to the lessons entitled Processing Image Pixels Using Java, Controlling Contrast and Brightness and Processing Image Pixels using Java, Getting Started to get copies of the program named ImgMod02a and the interface named ImgIntfc02.

Viewing tip


You may find it useful to open another copy of this lesson in a separate browser window.  That will make it easier for you to scroll back and forth among the different figures and listings while you are reading about them.


Display format


The output shown in Figure 1 was produced by the driver program named ImgMod02a and the image-processing program named ImgMod28.  The program named ImgMod02a was explained in an earlier lesson.  I will explain the program named ImgMod28 (along with another program named ImgMod28a) in this lesson.







Figure 1

As in all of the graphic output produced by the driver program named ImgMod02a, the original image is shown at the top and the processed image is shown at the bottom.


Background Information


The earlier lesson entitled Processing Image Pixels using Java, Getting Started provided a great deal of background information as to how images are constructed, stored, transported, and rendered.  I won’t repeat that material here, but will simply refer you to the earlier lesson.


That earlier lesson introduced and explained the concept of a pixel.  In addition, the lesson provided a brief discussion of image files, and indicated that the program named ImgMod02a is compatible with gif files, jpg files, and possibly some other file formats as well.


However, with the exception of the discussion on file compression errors later in this lesson, the lessons in this series are not particularly concerned with file formats.  Rather, the lessons are concerned with what to do with the pixels after they have been extracted from an image file.  Therefore, there is very little discussion about file formats in this series.


A three-dimensional array of pixel data as type int


The driver program named ImgMod02a:



  • Extracts the pixels from an image file.
  • Converts the pixel data to type int.
  • Stores the pixel data in a three-dimensional array of type int that is well suited for processing.
  • Passes the three-dimensional array object’s reference to an image-processing program.
  • Receives a reference to a three-dimensional array object containing processed pixel data from the image-processing program.
  • Displays the original image and the processed image in a stacked display as shown in Figure 1.

The manner in which that is accomplished was explained in the earlier lesson entitled Processing Image Pixels using Java, Getting Started.


Will concentrate on the three-dimensional array of type int


The lessons in this series will show you how to write image-processing programs that implement a variety of image-processing algorithms.  The image-processing programs will receive raw pixel data in the form of a three-dimensional array of type int, and will return processed pixel data in the form of a three-dimensional array of type int.


The two programs that I will explain in this lesson will embed secret text messages in the pixels of an image before returning the processed image for display.  A future program will explain how to embed a secret image in another image as a more technically advanced form of steganography.


A grid of colored pixels


Each three-dimensional array object represents one image consisting of a grid of colored pixels.  The pixels in the grid are arranged in rows and columns when they are rendered.  One of the dimensions of the array represents rows.  A second dimension represents columns.  The third dimension represents the color (and transparency) of the pixels.


Fundamentals


Once again, I will refer you to the earlier lesson entitled Processing Image Pixels using Java, Getting Started to learn:



  • How the primary colors of red, green, and blue and the transparency of a pixel are represented by four unsigned 8-bit bytes of data.
  • How specific colors are created by mixing different amounts of red, green, and blue.
  • How the range of each primary color and the range of transparency extends from 0 to 255.
  • How black, white, and the colors in between are created.
  • How the overall color of each individual pixel is determined by the values stored in the three color bytes for that pixel, as modified by the transparency byte.

Supplementary material


I recommend that you also study the other lessons in my extensive collection of online Java tutorials.  You will find those lessons published at Gamelan.com.  However, as of the date of this writing, Gamelan doesn’t maintain a consolidated index of my Java tutorial lessons, and sometimes they are difficult to locate there.  You will find a consolidated index at www.DickBaldwin.com.


Preview


Five programs and one interface


The image-processing programs that I will discuss in this lesson require the driver program named ImgMod02a and the interface named ImgIntfc02 for compilation and execution.  I provided and explained that material in the earlier lessons entitled Processing Image Pixels Using Java, Controlling Contrast and Brightness and Processing Image Pixels using Java, Getting Started.


I will present and explain two new Java programs named ImgMod28 and ImgMod28a in this lesson.  These programs, when run under control of the program named ImgMod02a, will produce outputs similar to those shown in Figure 1.



(The results will be different if you use a different image file.)


The processed image matches the original image


In previous lessons in this series, the processed image was usually different from the original image, and it was this difference that was significant with respect to the topic of the lesson.  The significant thing about Figure 1, Figure 3, Figure 6, and Figure 7 is that the processed image at the bottom looks just like the original image at the top.


This is significant because in Figure 1, Figure 3, Figure 6, and Figure 7 there is a text message hidden in the bottom image.  A short text message is hidden in the bottom image of Figure 1 and Figure 3.  A very long text message is hidden in the bottom image of Figure 6 and Figure 7.  In all four cases, the existence of the message cannot be detected simply by viewing the image.


I will show you how to write the Java code to hide the message in the image.  I will also show you how to extract and display the message that is hidden in the image.


The processImg method


The programs named ImgMod28 and ImgMod28a, (and all image-processing programs that are capable of being driven by ImgMod02a), must implement the interface named ImgIntfc02.  That interface declares a single method named processImg, which must be defined by all implementing classes.


When the user runs the program named ImgMod02a, that program instantiates an object of the image-processing program class and invokes the processImg method on that object.


A three-dimensional array containing the pixel data for the image is passed to the processImg method.  The processImg method returns a three-dimensional array containing the pixel data for a processed version of the original image.


A before and after display


When the processImg method returns, the driver program named ImgMod02a causes the original image and the processed image to be displayed in a frame with the original image above the processed image as shown in Figure 1.



The image file


The image file can be a gif file or a jpg file.  Other file types may be compatible as well.  If the program is unable to load the image file within ten seconds, it will abort with an error message.



(You should be able to right-click on the image in Figure 8 to download and save the image locally.  Then you should be able to replicate the output produced in Figure 1 by running the program named ImgMod28 and specifying that image file as the input.)


The Replot button


A Replot button appears at the bottom of the frame in Figure 1. If the user clicks the Replot button, the processImg method is rerun, the image is reprocessed, and the new version of the processed image replaces the old version in the display.


This feature is of no significance with the program named ImgMod28 because there is no change between successive runs of the processImg method.  This feature could be significant for the program named ImgMod28a, because a new message containing random characters is generated and hidden each time the processImg method is run.  Thus, it is possible that the output image could be different from one run to the next for the program named ImgMod28a.


Discussion and Sample Code


The program named ImgMod28


This program, which is designed to be driven by the program named ImgMod02a, illustrates one form of steganography by:



  • Hiding a text message in an image.
  • Displaying the original message.
  • Extracting and displaying the hidden message for comparison with the original.
  • Displaying the modified image below the original image for a visual comparison of the two images.

Running the program


Enter the following on the command line to run this program:


java ImgMod02a ImgMod28 ImagePathAndFileName


Hiding the message


This program hides the message in the image by doing the following:



  • Reducing the message to the 64-character set beginning with the space character in the ASCII table.  Each character can then be represented by six bits.
  • Decomposing each six-bit character into three groups of two bits each.
  • Replacing the two least-significant bits (LSB) in the red, green, and blue values for a given pixel by the three groups of two bits.  Thus, each pixel can carry one six-bit character.

Camouflage


In an attempt to make it difficult for an evesdropper to detect that a message is embedded in the image, the message is not embedded at the beginning of the image.  Rather the image is embedded beginning at a predetermined pixel location somewhere internal to the image.


More camouflage


In an attempt to make it even more difficult for an evesdropper to detect that a message is embedded in the image, message values are not embedded into adjacent pixels.  Rather, a space consisting of 0, 1, 2, or 3 pixels is allowed between modified pixels.  Furthermore, the space that is allowed between any two modified pixels is probably random because it is based on the value of two bits from the previous message character.


May or may not be helpful


While this feature probably makes the existence of the hidden message more difficult to detect, it also makes the overall process more vulnerable to problems resulting from data transmission errors.  Once a data error causes the reconstruction process to lose synchronization, it is probably not possible for the reconstruction process to recover.  Therefore, this feature may, or may not be advantageous depending on the likelihood of transmission errors affecting the low order bits in the pixel color values. 


This feature is easy to disable (see the program named ImgMod28a for a version of the program in which this feature has been disabled).  This feature should be disabled if there is any possibility of transmission errors affecting the low order bits in the pixel color values.


The character set


The program supports a message character set consisting of 64 characters beginning with the space character in the ASCII table.  This set includes the space character, the numeric characters, the upper-case alphabetic characters, and most of the punctuation and special characters.


By using this format, each character can be represented in six bits, and each modified pixel can carry one message character.


The end-of-message character


The program uses an underscore character to indicate the end of the message.  Therefore, the underscore character cannot be used elsewhere within the body of the message.  Thus, the effective number of allowable characters is reduced from 64 to 63 by the use of the underscore character to flag the end of the message.


An output example


This program produces the output shown in Figure 2 when run with an image file named imgmod10.jpg.  (This is the image shown in Figure 1.  You can download this image from Figure 8 later in the lesson.)


Note that line breaks were manually inserted in Figure 2 to force this material to fit into this narrow publication format.





Processing program: ImgMod28
Image file: imgmod10.jpg
Width = 324
Height = 330
Original message:
Four score and seven years ago our fathers brough
t forth on this continent a new nation, conceived
in liberty and dedicated to the proposition that
all men are created equal.
Msg Length = 175
Insertion point = 5000
Reconstructed message:
FOUR SCORE AND SEVEN YEARS AGO OUR FATHERS BROUGH
T FORTH ON THIS CONTINENT A NEW NATION, CONCEIVED
IN LIBERTY AND DEDICATED TO THE PROPOSITION THAT
ALL MEN ARE CREATED EQUAL._
Figure 2

Conversion to all upper case


The text of the original message that was converted to a 64-character set and embedded in the image is shown near the middle of Figure 2.  That text consists of 175 characters beginning with the word Four and ending with EQUAL._


As you can see, the text was converted from mixed case to all upper case.  This conversion was a part of converting the message to the set of only 64 characters.  This reduction in the size of the character set is not a requirement for embedding a message in an image.  It simply makes it easier because the six bits that comprise a character fit nicely into the two LSB of the three color values.  The use of 8 or 16-bit characters would require additional control logic in the character embedding and character extraction process but would be entirely feasible.


A fairly short message


The message used to test the program in this case was fairly short (175 characters).  I did this on purpose to make it practical to display and to compare the original message and the reconstructed message in a reasonable amount of screen space.


A much longer message


An image of this size could be used to hide a much longer message.  For example, if the pixel-skipping feature remains enabled and if it is assumed that on the average one character is embedded in every three pixels, this image would accommodate a message with a length in excess of 35,600 characters.  As you will see later, if the pixel-skipping feature is disabled and one character is embedded in every pixel, this image would accommodate a message with a length in of 106,920 characters.


As you can see, the message-hiding capacity of this approach is substantial.


Not visible in the modified image


If you scroll back to Figure 1 and compare the modified image on the bottom with the original image at the top, you will see that there is no visual indication that the message is embedded in the image.


What about other images?


One might hypothesize that the existence of the embedded message is not visible in the modified image for the following reasons:



  • The image contains a lot of detail with many different shades of several different colors.  Thus, a slight change in the shade of a given pixel would be difficult to see.
  • The number of pixels in the image is much greater than the number of characters in the message.  Therefore, only 175 of 106,920 pixels were actually modified, and those were not adjacent pixels.

One might further hypothesize that if either of these conditions is not met, the existence of the message will become visible.


The next example will test the hypothesis that the message would be visible in an image with less color variation.  The program named ImgMod028a will test the hypothesis that the message would become visible in the image if the number of characters is a large percentage of the number of pixels.


The image named imgmod11.jpg


Figure 3 shows the result of using the same program to embed the same message in a different image named imgmod11.jpg.






Figure 3

Once again, the original image is shown at the top, and the image containing the embedded message is shown at the bottom.  (The two images are separated by a very narrow yellow horizontal line in Figure 3.)


No visual indication of a message


There is no visual indication that the message was embedded in the bottom image in spite of the fact that there was virtually no color detail in this image.  This image contains large blocks of only eight colors consisting of:



  • Black and white
  • The primary colors red, green, and blue
  • The secondary colors aqua, yellow, and fuchsia 

The text output


Just to prove that the lower image in Figure 3 does contain a message, the text output produced by this program for this image is shown in Figure 4.





Processing program: ImgMod28
Image file: imgmod11.jpg
Width = 480
Height = 120
Raw message:
Four score and seven years ago our fathers brough
t forth on this continent a new nation, conceived
in liberty and dedicated to the proposition that
all men are created equal.
Msg Length = 175
Insertion point = 5000
Extracted message:
FOUR SCORE AND SEVEN YEARS AGO OUR FATHERS BROUGH
T FORTH ON THIS CONTINENT A NEW NATION, CONCEIVED
IN LIBERTY AND DEDICATED TO THE PROPOSITION THAT
ALL MEN ARE CREATED EQUAL._
Figure 4

The only differences in the text output in Figure 4 and the text output in Figure 2 has to do with the name and the size of the image file.  The same message was embedded in the modified image in both cases and was later extracted and displayed.


Conclusion


The conclusion is that the human eye is incapable of discerning changes that take place in the two LSB of the color values when an image that uses three 8-bit color values and one 8-bit alpha value is displayed on a computer screen.



(At least, that is the case for my eyes and my computer screen.)


This suggests that it might be possible to use three and possibly four of the least significant bits to embed the message provided that the image was carefully chosen to contain lots of color and lots of fine detail.  However, I am unable to test that hypothesis without making major modifications to the program.



(That sounds like a good homework assignment.)


Enough talk, let’s see some code


I will break this program down and discuss it in fragments.  You can view the entire program named ImgMod28 in Listing 10 near the end of the lesson.


The program named ImgMod28


As shown in Listing 1, the class named ImgMod28 begins just like most of my classes that implement the interface named ImgIntfc02.  I have explained this code in several previous lessons and won’t repeat that explanation here.  Rather, I will simply allow the comments to speak for themselves.





class ImgMod28 implements ImgIntfc02{
//This method must be defined to implement
// the interface named ImgIntfc02.
public int[][][] processImg(
int[][][] threeDPix,
int imgRows,
int imgCols){
//Make a working copy of the 3D array to
// avoid making permanent changes to the
// image data.
int[][][] temp3D =
new int[imgRows][imgCols][4];
for(int row = 0;row < imgRows;row++){
for(int col = 0;col < imgCols;col++){
temp3D[row][col][0] =
threeDPix[row][col][0];
temp3D[row][col][1] =
threeDPix[row][col][1];
temp3D[row][col][2] =
threeDPix[row][col][2];
temp3D[row][col][3] =
threeDPix[row][col][3];
}//end inner loop
}//end outer loop
System.out.println(“Width = ” + imgCols);
System.out.println(“Height = ” + imgRows);
Listing 1

The original message


Listing 2 defines the message that will be embedded in the image.  This message is created as a String object using the 16-bit Unicode character set that is standard for Java.  The message is terminated with an underscore character.



(The use of a termination character is necessary so that the code that later extracts the message from the image will know when it has reached the end of the message.)





    String msg =
“Four score and seven years ago our fathers”
+ ” brought forth on this continent a new”
+ ” nation, conceived in liberty and”
+ ” dedicated to the proposition that all”
+ ” men are created equal.”;
System.out.println(
“Original message:n” + msg);

//Terminate the message with an underscore.
msg = msg + “_”;
Listing 2


Listing 2 also displays the original message as shown in Figure 2 and Figure 4.


Convert to six-bit characters


The next step is to convert the Unicode characters to a set of 64 characters, which can be represented as six bits per character.  This is accomplished in Listing 3.





    //Convert the message to 64-char six-bit
// data format. This includes the space,
// the numbers, the upper-case characters,
// and most of the punctuation characters
// and other special characters.
//Begin by converting to all upper-case
// characters.
msg = msg.toUpperCase();
//Create an object to receive the modified
// version of the message.
StringBuffer sixBitMsg = new StringBuffer();
//Subtract a value of 32 causing all of the
// modified character values to fall between
// 0 and 63. This bias value of 32 will be
// added back later when the message is
// reconstructed.
char temp;
for(int cnt = 0;cnt < msg.length();cnt++){
temp = (char)(msg.charAt(cnt) – 32);
sixBitMsg.append(temp);
}//end for loop
//Replace the original Unicode message with
// the 6-bit message. This is the message
// that will be hidden in the image.
msg = new String(sixBitMsg);
Listing 3

Listing 3 is rather long, but the code in Listing 3 is straightforward and should not require a detailed explanation.  Therefore, once again, I will let the comments speak for themselves.


Embed the message in the image


The next step is to embed the message in the image.  The overall process consists of two steps:



  • Break each six-bit character down into three pairs of bits
  • Substitute those three pairs of bits for the two least-significant bits (LSB) of the red, green, and blue color values in selected pixels in the image

Extract and save the pairs of bits


The process begins by extracting each pair of bits from each six-bit character and storing the two bits in the two LSB of a byte in an array of bytes.  This makes it easier to access the pairs of bits later for use in modifying the pixel values.


Start the process by executing the first statement in Listing 4 to convert the message from a String of Unicode characters into an array of bytes.



(Note that this process preserves only the 8 LSB of each 16-bit Unicode character.)





    byte[] msgBytes = msg.getBytes();
System.out.println(
“Msg Length = ” + msgBytes.length);
//Create an array to store the bytes
// containing the pairs of bits.
byte[] twoBitBytes =
new byte[3 * msgBytes.length];

//Shift and mask the bytes in the array
// containing the message bytes to get and
// save the pairs of bits.
int twoBitByteCnt = 0;
for(byte element:msgBytes){
twoBitBytes[twoBitByteCnt++]
= (byte)(element & 0x03);
twoBitBytes[twoBitByteCnt++]
= (byte)((element >> 2) & 0x03);
twoBitBytes[twoBitByteCnt++]
= (byte)((element >> 4) & 0x03);
}//end for-each
Listing 4


Then extract the bit pairs from the characters and store them in the array referred to by twoBitBytes.  This is all accomplished in Listing 4.


The code in Listing 4 is relatively straightforward, particularly if you are familiar with bit-level programming in Java.


Embed the bits in the pixels


The next step is to embed the pairs of message bits into the pixel color values beginning at a predetermined pixel location in the image.


Two bits from a single 6-bit message character are embedded into the two LSB of the red, green, and blue pixel values belonging to a single image.


After a pixel has been modified, the values of the two bits that were embedded into the blue pixel are used to determine the number of pixels to skip before modifying another pixel.  The number of pixels skipped will be 0, 1, 2, or 3.  This prevents adjacent pixels from being modified about three-fourths of the time.


This is accomplished by the code in Listing 5.





    //Specify the predefined insertion point
int insertionPoint = 5000;
System.out.println(
“Insertion point = ” + insertionPoint);
//Initialize the number of pixels to skip.
int skipValue = 0;
//Use a nested loop to operate on each pixel
// and to embed message bits into the red,
// green, and blue values of selected pixels.
twoBitByteCnt = 0;//Re-use this variable
for(int row = 0;row < imgRows;row++){
for(int col = 0;col < imgCols;col++){
//Embed 6-bit message characters
// beginning at a predefined pixel
// location in the image and skipping
// pixels on the basis of the value of
// two bits in the message character.
if((row * col > insertionPoint)
&& (twoBitByteCnt < twoBitBytes.length)
&& skipValue– == 0){
//Replace the two lsb of the red,
// green, and blue pixel values with
// two bits from the six-bit message
// character.
temp3D[row][col][1] =
(temp3D[row][col][1] & 0xFC)
| twoBitBytes[twoBitByteCnt++];
temp3D[row][col][2] =
(temp3D[row][col][2] & 0xFC)
| twoBitBytes[twoBitByteCnt++];
temp3D[row][col][3] =
(temp3D[row][col][3] & 0xFC)
| twoBitBytes[twoBitByteCnt++];
//Determine number of pixels to skip
// before modifying another pixel.
skipValue =
twoBitBytes[twoBitByteCnt – 1];
}//end if
}//end for loop on col
}//end for loop on row
Listing 5

Once again, although the code in Listing 5 is rather long, it is completely straightforward if you are familiar with bit-level programming in Java.


Once the code in Listing 5 has been executed, the message has been successfully embedded in the image.


Extract the message from the image


Up to this point, the program shows how to modify an image to include a hidden message.  The remainder of the program shows how to extract the message data from the image, reconstruct the message, and display the message.  (Note that this code requires prior knowledge of the insertion point value.)


An array to contain the two-bit message data


Begin by creating an array object large enough to contain one byte for every two bits of message data that can be extracted from every pixel between the insertion point and the end of the message.  This is shown in Listing 6.





    byte[] extractedTwoBitBytes =
new byte[(imgRows * imgCols
– insertionPoint) * 3];
Listing 6

A pair of nested for loops


The next step is to use a pair of nested for loops to process every pixel, extracting the two message bits from the color values for those pixels that contain message data.



(Recall that as a result of the pixel-skipping feature, only about one out of three or four pixels actually contain message data.)


The code in Listing 7 extracts and saves the pairs of message bits in the two LSB of the bytes in the byte array referred to by extractedTwoBitBytes.





    twoBitByteCnt = 0;
skipValue = 0;
//Process every pixel.
for(int row = 0;row < imgRows;row++){
for(int col = 0;col < imgCols;col++){
if((row * col > insertionPoint)
&& (skipValue– == 0)){
//Extract and save the message
// characters two bits at a time.
extractedTwoBitBytes[twoBitByteCnt++]
= (byte)(temp3D[row][col][1] & 0x03);
extractedTwoBitBytes[twoBitByteCnt++]
= (byte)(temp3D[row][col][2] & 0x03);
extractedTwoBitBytes[twoBitByteCnt++]
= (byte)(temp3D[row][col][3] & 0x03);
//Determine number of pixels to skip
// before extracting data for another
// pixel.
skipValue = extractedTwoBitBytes[
twoBitByteCnt – 1];
}//end if
}//end for loop on col
}//end for loop on row
Listing 7

Two-bit message data has been extracted and saved


Once the code in Listing 7 finishes execution, all of the message data has been extracted from the image and saved in the byte array.  At this point, however, the bits for each six-bit character are spread across three bytes in the array, two data bits in each byte.  Those bits must be recombined in order to form a readable message.


Reconstruct the message


The code in Listing 8 uses bit-level programming to reconstruct each six-bit character into an eight-bit byte using the three pairs of bits contained in three consecutive bytes in the byte array referred to by extractedTwoBitBytes.





    //Reconstruct and display the 64-char version
// of the message
StringBuffer extractedMsg =
new StringBuffer();
byte[] sixBitBytes =
new byte[extractedTwoBitBytes.length/3];
twoBitByteCnt = 0;
for(byte element:sixBitBytes){
//Shift and or the pairs of bits into the
// 6-bit format.
element = (byte)(extractedTwoBitBytes[
twoBitByteCnt++]);
element =
(byte)(element | (extractedTwoBitBytes[
twoBitByteCnt++] << 2));
element =
(byte)(element | (extractedTwoBitBytes[
twoBitByteCnt++] << 4));
extractedMsg.append((char)(element + 32));
//Stop processing when terminating
// underscore character is encountered.
if((char)(element + 32) == ‘_’)break;
}//end for-each
Listing 8

Add the bias value back in


In addition, the code in Listing 8 adds in a bias value of 32 to compensate for the fact that a value of 32 was subtracted in Listing 3 during the process of representing the 64 characters in six-bit data.  This causes the six bits that represent the 64 characters to be shifted back to their proper locations in an eight-bit byte.


Monitor for message-termination character


While reconstructing the message, the code in Listing 8 monitors for the occurrence of the underscore terminating character.  When it is encountered, the reconstruction process is terminated by breaking out of the processing loop.  Once this is done, the StringBuffer object referred to by extractedMsg contains the extracted message in 16-bit 64-character format.


Display the results


Listing 9 displays the extracted message on the standard output device as shown in Figure 2 and Figure 4.  Listing 9 also returns the modified image to be displayed as shown in Figure 1 and Figure 3.





    System.out.println(“Extracted message:n”
+ extractedMsg);
//Return the modified image for display.
return temp3D;
}//end processImg
Listing 9

What about a longer message?


It was hypothesized earlier that an embedded message might be visible in an image that contains very little detail.  That hypothesis was refuted by Figure 3 that shows a message embedded in an image containing only eight colors with no detail.  There was no visible indication that the image contains an embedded message.


It was also hypothesized earlier that a message embedded in an image might be visible if the number of characters in the message is a large percentage of the number of pixels in the image.  This hypothesis will be tested by the program named ImgMod28a.


The program named ImgMod28a


Because the program is very similar to the program named ImgMod28, I won’t break it down and discuss the code in fragments.  You can view this program in its entirety in Listing 11 near the end of the lesson.


This program is a modified version of the program named ImgMod28.  The purpose of this program is to determine the extent to which randomly modifying the two LSB of every pixel in an image degrades the visual quality of the image.  In other words, if the two LSB in every color value in every pixel is modified by embedding a message, will be existence of the message be detectable simply by viewing the image?


Program overview


This program is designed to be driven by the program named ImgMod02a.  It illustrates the use of steganography for very long messages by:



  • Embedding a long text message in an image by modifying every pixel in the image to contain one character from the message.  (The message is composed of characters generated on the basis of a sequence of output values provided by a random number generator.  This is similar to what might be expected if the message had been encrypted.)
  • Displaying selected characters from throughout the original message.  (Because of the length of the message, it is not practical to display all of the characters.)
  • Extracting the message from the image and displaying the same set of selected characters from the extracted version for comparison with the original message.
  • Displaying the modified image below the original image for a visual comparison of the two images.  This is the point where visual detection of the message will occur if it does occur.

Running the program


You can run the program by entering the following at the command line:


java ImgMod02a ImgMod28a ImagePathAndFileName


Support for character set


As is the case with the earlier program named ImgMod28, this program supports a message character set consisting of 64 characters beginning with the space character in the ASCII table.  This character set includes the space character, the numeric characters, the upper-case alphabetic characters, and most of the punctuation and special characters.  By using this format, each modified pixel can carry one message character.


A smaller random character set is actually used


Note however that the random message used to test this program does not contain all of the allowable characters.  This is acceptable since the purpose of the program is to test for the ability to visually detect the message caused by replacing the two LSB in every color value for every pixel with a pair of randomly determined bits.


As before, the program uses an underscore character to indicate the end of the message.  Therefore, the underscore character cannot be used within the body of the message so the effective number of allowable characters is reduced from 64 to 63.


The text output


This program produced the output shown in Figure 5 when run with the image file named imgmod10.jpg.



(This is the same image file that was used to test the program named ImgMod28 earlier.)





Processing program: ImgMod28a
Image file: imgmod10.jpg
Width = 324
Height = 330
Characters from original message:
%1″ 9:)<8;>’;4$:!?,,*”‘+4:6$-‘?!2: (3$8 :729$&;%&
‘(-724
Msg Length = 106920
Insertion point = 0
Characters from reconstructed message:
%1″ 9:)<8;>’;4$:!?,,*”‘+4:6$-‘?!2: (3$8 :729$&;%&
‘(-724
Figure 5

Note that line breaks were manually inserted in Figure 5 to force this material to fit into this narrow publication format.  Note also that because the message that is embedded in the image is generated using a random number generator, the output will be different each time the program is run.


Reconstructed characters match original characters


The output shown in Figure 5 contains several interesting aspects.  To begin with, the characters that are displayed from the reconstructed message match the characters displayed from the original message proving that the message was actually embedded in the image and later extracted successfully.


Number of characters equal number of pixels


Next, a message character was embedded in every pixel beginning with the first pixel resulting in a message length of 106,920 characters.  By many standards, that would be a fairly long message.  (In raw HTML form, this tutorial lesson contains about 63,000 characters.)


Was the message visually detectable?


That brings us to the question of visual message detectability resulting from the embedding of one character in every pixel.  The input and output images for the starfish image are shown in Figure 6.  As near as I can tell, there was no visual degradation of the bottom image caused by embedding this message in this image.  There appear to be no visual clues in the output image that would cause someone to suspect that a very long message has been embedded in the image.






Figure 6

A more severe test case


An even more severe test is shown in Figure 7.  This figure shows the input and output images for the image named imgmod11.jpg that consists of only eight solid colors.  Once again, one message character was embedded in every pixel with no visual indication that the modified image contains a hidden message.






Figure 7

Conclusion reinforced


This reinforces the conclusion drawn earlier that the human eye is incapable of discerning changes that take place in the two LSB of the color values when an image that uses three 8-bit color values and one 8-bit alpha value is displayed on a computer screen.


Now for the bad news


Up to this point, all of the news has been good news.  If you want to send that secret message to Alice by embedding it in one of your digital vacation photos, you can probably do so without fear of being caught.


Avoid lossy compression schemes


However, you must be very careful about the handling of the image in order to make certain that data transmission errors won’t cause changes to the two LSB of the color values, thus destroying your secret message.  In particular, you must avoid using lossy image compression schemes to store and transmit the image.  Here is some of what one author has to say about the JPEG file format:



“JPEG, … is a lossy compression method. In other words, to save space it just throws away parts of an image.”


Therefore, you probably don’t want to send your secret message to Alice by embedding and sending it to her in a JPEG image.  (An image file with a jpg extension.)  If you do, the message may be badly garbled or completely impossible to read.


Why did I use JPEG image files?


At this point, you may have observed that the images that I used in this lesson were JPEG images, and you may have wondered why this wasn’t a problem for me.  The reason is that although I read a JPEG image file as input, I didn’t write the modified image back out into another JPEG file before extracting the message.  Instead, I extracted the message from the image while it was still in raw pixel form.  Had I written the image back out into a JPEG file, and then read the file and extracted the message, the results may have been much different.


What about GIF files?


Here is part of what that same author has to say about the GIF file format:



“GIF, … is a lossless method of compression. … when the program that creates a GIF squashes the original image down it takes care not to lose any data. … they are limited to a palette of 256 colours or less. … There is a 24-bit, license-free GIFalike called the PNG format, …”


Therefore, it would probably be safe to embed your secret message to Alice in a GIF file.  Just be sure to convert to GIF format before embedding the message in the image.  Unless data errors crept in from some other source, Alice should be able to successfully extract and read the message.  However, your vacation photos may not look quite as good after you convert them to GIF files as they did when you first retrieved them from your digital camera.


What about PNG files?


Here is part of what another author has to say about the PNG file format:



“PNG file format provides … for lossless image compression. It supports true color (PNG-24) and palletized (PNG-8) images … PNG-24 is a useful image file format for high quality lossless image archiving, …”


Therefore, the PNG file format might be an appropriate format for sending your secret messages to Alice embedded in your digital vacation photos.  Reportedly PNG provides high-quality color as well as lossless compression.


What about lossless JPEG?


Finally, here is what another author has to say about something called lossless JPEG.



“There’s a great deal of confusion on this subject, … there are several different compression methods all known as “JPEG”.  The commonly used method is “baseline JPEG” …  The same ISO standard also defines a very different method called “lossless JPEG”.  …  When I say “lossless”, I mean mathematically lossless: a lossless compression algorithm is one that guarantees its decompressed output is bit-for-bit identical to the original input.  This is a much stronger claim than “visually indistinguishable from the original”.  Baseline JPEG can reach visual indistinguishability for most photo-like images, but it can never be truly lossless.  Lossless JPEG is a completely different method that really is lossless.  … it is now largely obsolete.  … the new PNG standard outcompresses lossless JPEG on most images.)  … It’s worth repeating that cranking a regular JPEG implementation up to its maximum quality setting *does not* get you lossless storage; even at the highest possible quality setting, baseline JPEG is lossy because it is subject to roundoff errors in various calculations.  …”


The bottom line


And that is probably more than you ever wanted to know about lossy and lossless image compression algorithms.


The bottom line is, if you are going to embed a message in an image, be sure to write your modified image into a file using a lossless compression algorithm.  Otherwise, your message will probably become garbled due to the data errors introduced by lossy compression.


Depending on the color quality that you need, the two best choices as of the date of this writing appear to be GIF for lower color quality and PNG for higher color quality.



(You can read more about the distribution of color in GIF and JPEG images in my earlier lesson entitled Processing Image Pixels Using Java: Controlling Contrast and Brightness.  That lesson uses histograms to illustrate and compare the distribution of color for those two image file formats.)

Run the Program


I encourage you to copy, compile and run the following programs that are provided in this lesson:



  • ImgMod28
  • ImgMod28a

Experiment with the programs, making changes and observing the results of your changes.



(Remember, you will also need to copy the program named ImgMod02a and the interface named ImgIntfc02 from the earlier lessons entitled Processing Image Pixels Using Java, Controlling Contrast and Brightness and Processing Image Pixels using Java, Getting Started.)


Test images


To replicate the output images shown in this lesson, you will need to use the same images as input.  Those images are provided below.  Simply right-click on the images in Figure 8 and Figure 9.  Save the images locally under the files named imgmod10.jpg and imgmod11.jpg.  Then use them as input to the programs named ImgMod28 and ImgMod28a.







Figure 8
 





Figure 9

Test some other images


If you search the Internet, you should be able to find lots of images that you can download and experiment with.  See if you can find an image in which an embedded message is visually detectable.  Just remember, as explained in the lesson entitled Processing Image Pixels Using Java, Controlling Contrast and Brightness, if you download a gif image, it will probably contain a lot less color information than a comparable jpg image.


Try three and four-bit embedding


Try modifying the program to embed the message in the three LSB of the color values of the image.  See if this causes the message to become visually detectable.  If not, try embedding the message in the four LSB of the color values to see if that results in a visually detectable message.  See how far you can push this before the existence of the message in the image becomes visually detectable.



(There is one thing that I can say for sure.  If you embed the message in the eight LSB of every color value for every pixel, the existence of the message in the image will be visually detectable.)


Have fun and learn


Above all, have fun and use these programs to learn as much as you can about this form of steganography using Java as your programming language.


Summary


In this lesson, I showed you how to write Java code to embed a long text message in an image in such a way that there is no visual indication that the image contains a hidden secret message.  I also showed you how to write the code to extract the message from the image and reconstruct it into readable form.


In addition, I explained how the use of image compression can garble a message that has been embedded in an image and provided some information on both lossy and lossless image compression algorithms.


What’s Next?


My next lesson on steganography will probably show you how to hide a secret image in another image and how to extract and reconstruct the secret image later.


A topic that is very similar to steganography is the topic of digital watermarking.  Visible watermarks are often used to indicate the ownership of an image to all viewers of the image.  Invisible watermarks are sometimes used to prove ownership of an image during a dispute regarding the rightful use of the image.


Future lessons will explain:



  • How to create visible watermarks in images in the space domain.
  • How to create invisible watermarks in images in the space domain.
  • How to create invisible watermarks in images in the wave-number domain.

These lessons will also discuss the vulnerability (or lack thereof) of the watermarks to destruction in the face of various modifications to the image such as scaling, rotation, cropping, etc.


Complete Program Listings


Complete listings of the programs discussed in this lesson are provided in Listing 10 and Listing 11.  In order to compile and run these programs, you will also need copies of the program named ImgMod02a and the interface named ImgIntfc02 from the earlier lessons entitled Processing Image Pixels Using Java, Controlling Contrast and Brightness and Processing Image Pixels using Java, Getting Started.


A disclaimer


The programs that I am providing and explaining in this series of lessons are not intended to be used for high-volume production work.  Numerous integrated image-processing programs are available for that purpose.  In addition, the Java Advanced Imaging API (JAI) has a number of built-in special effects if you prefer to write your own production image-processing programs using Java.


The programs that I am providing in this series are intended to make it easier for you to develop and experiment with image-processing algorithms and to gain a better understanding of how they work, and why they do what they do.






/*File ImgMod28.java.java
Copyright 2005, R.G.Baldwin
This program is designed to be driven by the
program named ImgMod02a.java.
This program illustrates one form of
steganography by:
1. Burying a text message in an image.
2. Displaying the original message.
3. Extracting and displaying the extracted
message for comparison with the original.
4. Displaying the modified image alongside the
original image for a visual comparison of the
two.
Enter the following on the command line to run
this program:
java ImgMod02a ImgMod28 ImageFileName
In an attempt to make it more difficult for an
evesdropper to determine that a message is
embedded in the image, the message is not
embedded at the beginning of the image. Rather
the image is embedded at a predetermined pixel
location somewhere internal to the image.
In a further attempt to make it difficult for an
evesdropper to determine that a message is
embedded in the image, message values are not
embedded into adjacent pixels. Rather, a space
consisting of 0, 1, 2, or 3 pixels is allowed
between modified pixels. Furthermore, the space
that is allowed between any two modified pixels
is probably random because it is based on
the value of two bits from the previous message
character. While this probably makes the
existence of the hidden message harder to detect,
it also makes the overall process more vulnerable
to problems resulting from data errors. Once a
data error causes the reconstruction process to
lose synchronization, it is probably not possible
to recover. Therefore, this feature may, or may
not be advantageous depending on the liklihood of
transmission errors with the image. However, it
is relatively easy to disable this feature. (See
the program named ImgMod28a for a version of the
program in which this feature has been disabled.)
The program supports a message character set
consisting of 64 characters beginning with the
space character in the ASCII table. This set
includes the space character, the numeric
characters, the upper-case characters, and most
of the punctuation and special characters. By
using this format, each modified pixel can carry
one message character.
The program uses an underscore character to
indicate the end of the message. Therefore, the
underscore character cannot be used within the
body of the message so the effective number of
available characters is reduced from 64 to 63.
This program produces the following output when
run with an image file named imgmod10.jpg. Note
that line breaks were manually inserted to force
this material to fit into this narrow publication
format.
Processing program: ImgMod28
Image file: imgmod10.jpg
Width = 324
Height = 330
Original message:
Four score and seven years ago our fathers brough
t forth on this continent a new nation, conceived
in liberty and dedicated to the proposition that
all men are created equal.
Msg Length = 175
Insertion point = 5000
Reconstructed message:
FOUR SCORE AND SEVEN YEARS AGO OUR FATHERS BROUGH
T FORTH ON THIS CONTINENT A NEW NATION, CONCEIVED
IN LIBERTY AND DEDICATED TO THE PROPOSITION THAT
ALL MEN ARE CREATED EQUAL._
Although the message used to test the program
was fairly short, an image of this size could
hide a message with a length in excess of 33,000
characters, assuming one character for every
three pixels. If the pixel-skipping feature is
disabled, an image of this size could hide a
message with a length in excess of 106,000
characters. As you can see, the message-hiding
capacity of this approach is substantial.
Tested using J2SE 5.0 and WinXP
************************************************/
class ImgMod28 implements ImgIntfc02{
//This method must be defined to implement
// the interface named ImgIntfc02.
public int[][][] processImg(
int[][][] threeDPix,
int imgRows,
int imgCols){
//Make a working copy of the 3D array to
// avoid making permanent changes to the
// image data.
int[][][] temp3D =
new int[imgRows][imgCols][4];
for(int row = 0;row < imgRows;row++){
for(int col = 0;col < imgCols;col++){
temp3D[row][col][0] =
threeDPix[row][col][0];
temp3D[row][col][1] =
threeDPix[row][col][1];
temp3D[row][col][2] =
threeDPix[row][col][2];
temp3D[row][col][3] =
threeDPix[row][col][3];
}//end inner loop
}//end outer loop
System.out.println(“Width = ” + imgCols);
System.out.println(“Height = ” + imgRows);

//Define the raw message
String msg =
“Four score and seven years ago our fathers”
+ ” brought forth on this continent a new”
+ ” nation, conceived in liberty and”
+ ” dedicated to the proposition that all”
+ ” men are created equal.”;
System.out.println(
“Original message:n” + msg);
//Note: The text for this message was taken
// from
// http://www.law.ou.edu/hist/getty.html

//Terminate the message with an underscore.
msg = msg + “_”;

//Convert the message to 64-char six-bit
// data format. This includes the space,
// the numbers, the upper-case characters,
// and most of the punctuation characters
// and other special characters.
//Begin by converting to all upper-case
// characters.
msg = msg.toUpperCase();
//Create an object to receive the modified
// version of the message.
StringBuffer sixBitMsg = new StringBuffer();
//Subtract a value of 32 causing all of the
// modified character values to fall between
// 0 and 63. This bias value of 32 will be
// added back later when the message is
// reconstructed.
char temp;
for(int cnt = 0;cnt < msg.length();cnt++){
temp = (char)(msg.charAt(cnt) – 32);
sixBitMsg.append(temp);
}//end for loop
//Replace the original Unicode message with
// the 6-bit message. This is the message
// that will be hidden in the image.
msg = new String(sixBitMsg);
//The overall process is to break each six-
// bit character into three pairs of bits,and
// to substitute those three pairs of bits
// for the two lsb of the red, green, and
// blue color values in selected pixels in
// the image.
//Extract each pair of bits from each six-bit
// char and store the two bits in the two lsb
// of a byte in an array of bytes. This will
// make it easier to access the pairs of bits
// later for use in modifying the pixel
// values.
//Begin by converting the message into an
// array of bytes. This process preserves
// only the 8 lsb of each character.
byte[] msgBytes = msg.getBytes();
System.out.println(
“Msg Length = ” + msgBytes.length);
//Create an array to store the bytes
// containing the pairs of bits.
byte[] twoBitBytes =
new byte[3 * msgBytes.length];

//Shift and mask the bytes in the array
// containing the message bytes to get and
// save the pairs of bits.
int twoBitByteCnt = 0;
for(byte element:msgBytes){
twoBitBytes[twoBitByteCnt++]
= (byte)(element & 0x03);
twoBitBytes[twoBitByteCnt++]
= (byte)((element >> 2) & 0x03);
twoBitBytes[twoBitByteCnt++]
= (byte)((element >> 4) & 0x03);
}//end for-each

//Begin embedding the pairs of message bits
// at a predetermined non-zero pixel location
// in the image. Two bits from a single 6-bit
// message character are embedded into the
// lsb of the red, green, and blue pixel
// values. After a pixel has been modified,
// use the value of the two bits that were
// embedded into the blue pixel to determine
// the number of pixels to skip before
// modifying another pixel. The number of
// pixels to be skipped will be either 0, 1,
// 2, or 3. This prevents adjacent pixels
// from being modified about three-fourths of
// the time.
//Specify the predefined insertion point
int insertionPoint = 5000;
System.out.println(
“Insertion point = ” + insertionPoint);
//Initialize the number of pixels to skip.
int skipValue = 0;
//Use a nested loop to operate on each pixel
// and to embed message bits into the red,
// green, and blue values of selected pixels.
twoBitByteCnt = 0;//Re-use this variable
for(int row = 0;row < imgRows;row++){
for(int col = 0;col < imgCols;col++){
//Embed 6-bit message characters
// beginning at a predefined pixel
// location in the image and skipping
// pixels on the basis of the value of
// two bits in the message character.
if((row * col > insertionPoint)
&& (twoBitByteCnt < twoBitBytes.length)
&& skipValue– == 0){
//Replace the two lsb of the red,
// green, and blue pixel values with
// two bits from the six-bit message
// character.
temp3D[row][col][1] =
(temp3D[row][col][1] & 0xFC)
| twoBitBytes[twoBitByteCnt++];
temp3D[row][col][2] =
(temp3D[row][col][2] & 0xFC)
| twoBitBytes[twoBitByteCnt++];
temp3D[row][col][3] =
(temp3D[row][col][3] & 0xFC)
| twoBitBytes[twoBitByteCnt++];
//Determine number of pixels to skip
// before modifying another pixel.
skipValue =
twoBitBytes[twoBitByteCnt – 1];
}//end if
}//end for loop on col
}//end for loop on row

//Up to this point, the program demonstrates
// how to modify an image to include a hidden
// message.
//The remainder of the program demonstrates
// how to extract that message from the
// image. Note that this code requires
// prior knowledge of the insertion point
// value.
//Create an array object large enough to
// contain one byte for every two-bit byte
// that can be extracted from every pixel
// between the insertion point and the end of
// the message.
byte[] extractedTwoBitBytes =
new byte[(imgRows * imgCols
– insertionPoint) * 3];
twoBitByteCnt = 0;
skipValue = 0;
//Process every pixel.
for(int row = 0;row < imgRows;row++){
for(int col = 0;col < imgCols;col++){
if((row * col > insertionPoint)
&& (skipValue– == 0)){
//Extract and save the message
// characters two bits at a time.
extractedTwoBitBytes[twoBitByteCnt++]
= (byte)(temp3D[row][col][1] & 0x03);
extractedTwoBitBytes[twoBitByteCnt++]
= (byte)(temp3D[row][col][2] & 0x03);
extractedTwoBitBytes[twoBitByteCnt++]
= (byte)(temp3D[row][col][3] & 0x03);
//Determine number of pixels to skip
// before extracting data for another
// pixeProcessing program: ImgMod28
Image file: imgmod11.jpg
Width = 480
Height = 120
Raw message:
Four score and seven years ago our fathers brough
t forth on this continent a new nation, conceived
in liberty and dedicated to the proposition that
all men are created equal.
Msg Length = 175
Insertion point = 5000
Extracted message:
FOUR SCORE AND SEVEN YEARS AGO OUR FATHERS BROUGH
T FORTH ON THIS CONTINENT A NEW NATION, CONCEIVED
IN LIBERTY AND DEDICATED TO THE PROPOSITION THAT
ALL MEN ARE CREATED EQUAL._
skipValue = extractedTwoBitBytes[
twoBitByteCnt – 1];
}//end if
}//end for loop on col
}//end for loop on row

//Reconstruct and display the 64-char version
// of the message
StringBuffer extractedMsg =
new StringBuffer();
byte[] sixBitBytes =
new byte[extractedTwoBitBytes.length/3];
twoBitByteCnt = 0;
for(byte element:sixBitBytes){
//Shift and or the pairs of bits into the
// 6-bit format.
element = (byte)(extractedTwoBitBytes[
twoBitByteCnt++]);
element =
(byte)(element | (extractedTwoBitBytes[
twoBitByteCnt++] << 2));
element =
(byte)(element | (extractedTwoBitBytes[
twoBitByteCnt++] << 4));
extractedMsg.append((char)(element + 32));
//Stop processing when terminating
// underscore character is encountered.
if((char)(element + 32) == ‘_’)break;
}//end for-each

System.out.println(“Extracted message:n”
+ extractedMsg);
//Return the modified image for display.
return temp3D;
}//end processImg
//——————————————-//
}//end class ImgMod28
Listing 10






/*File ImgMod28a.java.java
Copyright 2005, R.G.Baldwin
This program is an update of the program named
ImgMod28. The purpose of this program is to
determine the extent to which randomly modifying
the two least-significant bits of every pixel in
an image degrades the visual quality of the
image.
This program is designed to be driven by the
program named ImgMod02a.java.
This program illustrates steganography for very
long messages by:
1. Burying a long text message in an image by
modifying every pixel in the image to contain
one character from the message.
2. Displaying selected characters from throughout
the original message.
3. Extracting the message from the image and
displaying the same set of selected characters
for comparison of the extracted message with the
original message.
2. Displaying the modified image alongside the
original image for a visual comparison of the
two.
Enter the following on the command line to run
this program:
java ImgMod02a ImgMod28a ImageFileName
The program supports a message character set
consisting of 64 characters beginning with the
space character in the ASCII table. This set
includes the space character, the numeric
characters, the upper-case characters, and most
of the punctuation and special characters. By
using this format, each modified pixel can carry
one message character. (Note however that the
random message used to test this program does
not contain all of the allowed characters.)
The program uses an underscore character to
indicate the end of the message. Therefore, the
underscore character cannot be used within the
body of the message so the effective number of
available characters is reduced from 64 to 63.
This program produced the following output when
run with an image file named imgmod10.jpg. Note
that line breaks were manually inserted to force
this material to fit into this narrow publication
format. Note also that because the message that
is embedded in the image is generated using a
random number generator, the output will be
different each time the program is run.
Processing program: ImgMod28a
Image file: imgmod10.jpg
Width = 324
Height = 330
Characters from original message:
%1″ 9:)<8;>’;4$:!?,,*”‘+4:6$-‘?!2: (3$8 :729$&;%&
‘(-724
Msg Length = 106920
Insertion point = 0
Characters from reconstructed message:
%1″ 9:)<8;>’;4$:!?,,*”‘+4:6$-‘?!2: (3$8 :729$&;%&
‘(-724
As you can see, for this image, it was possible
to embed a message containing 106,920 characters
in the image. As you will also see if you run
this program using the same image, there was no
discernable deterioration in the visual quality
of the image.
Tested using J2SE 5.0 and WinXP
************************************************/
import java.util.*;
class ImgMod28a implements ImgIntfc02{
//This method must be defined to implement
// the interface named ImgIntfc02.
public int[][][] processImg(
int[][][] threeDPix,
int imgRows,
int imgCols){
//Make a working copy of the 3D array to
// avoid making permanent changes to the
// image data.
int[][][] temp3D =
new int[imgRows][imgCols][4];
for(int row = 0;row < imgRows;row++){
for(int col = 0;col < imgCols;col++){
temp3D[row][col][0] =
threeDPix[row][col][0];
temp3D[row][col][1] =
threeDPix[row][col][1];
temp3D[row][col][2] =
threeDPix[row][col][2];
temp3D[row][col][3] =
threeDPix[row][col][3];
}//end inner loop
}//end outer loop
System.out.println(“Width = ” + imgCols);
System.out.println(“Height = ” + imgRows);
//For this version of the program, define a
// raw message consisting of an array of
// random bytes equal in size to the number
// of pixels in the image.
byte[] rawMsg = new byte[imgCols * imgRows];
Random randomGenerator =
new Random(new Date().getTime());
randomGenerator.nextBytes(rawMsg);

//Force all of the bytes to represent
// printable characters within the range of
// character values from 33 to 63.
for(int cnt = 0;cnt < rawMsg.length;cnt++){
//Eliminate characters above 63
rawMsg[cnt] = (byte)(rawMsg[cnt] & 0x003F);
//Eliminate control codes. Preserve space
// and above.
if(rawMsg[cnt] <= 31) rawMsg[cnt] += 32;
}//end for loop
//Replace the last character with an
// underscore to terminate the message.
rawMsg[rawMsg.length – 1] = ‘_’;
//Convert bytes to a message String
String msg = new String(rawMsg);
System.out.println(
“Characters from original message:”);
//Select and print every (6*imgCols + 1)
// character from the message
for(int cnt = 0;cnt < msg.length();cnt++){
if(cnt%(6*imgCols + 1) == 0){
System.out.print(msg.charAt(cnt));
}//end if
}//end for loop
System.out.println();//blank line
//Make the memory occupied by the byte array
// eligible for garbage collection.
rawMsg = null;
//Convert the message to 64-char six-bit
// data format. This includes the space,
// the numbers, the upper-case characters,
// and most of the punctuation characters
// and other special characters. Note
// however that many of these characters were
// not included in the set of random bytes
// that constitute the message.
//Begin by converting to all upper-case
// characters.
msg = msg.toUpperCase();
//Create an object to receive the modified
// version of the message.
StringBuffer sixBitMsg = new StringBuffer();
//Subtract a value of 32 causing all of the
// modified character values to fall between
// 0 and 63. This bias value of 32 will be
// added back later when the message is
// reconstructed.
char temp;
for(int cnt = 0;cnt < msg.length();cnt++){
temp = (char)(msg.charAt(cnt) – 32);
sixBitMsg.append(temp);
}//end for loop
//Replace the original Unicode message with
// the 6-bit message. This is the message
// that will be hidden in the image.
msg = new String(sixBitMsg);
//The overall process is to break each six-
// bit character into three pairs of bits,and
// to substitute those three pairs of bits
// for the two lsb of the red, green, and
// blue color values in selected pixels in
// the image.
//Extract each pair of bits from each six-bit
// char and store the two bits in the two lsb
// of a byte in an array of bytes. This will
// make it easier to access the pairs of bits
// later for use in modifying the pixel
// values.
//Begin by converting the message into an
// array of bytes. This process preserves
// only the 8 lsb of each character.
byte[] msgBytes = msg.getBytes();
System.out.println(
“Msg Length = ” + msgBytes.length);
//Create an array to store the bytes
// containing the pairs of bits.
byte[] twoBitBytes =
new byte[3 * msgBytes.length];

//Shift and mask the bytes in the array
// containing the message bytes to get and
// save the pairs of bits.
int twoBitByteCnt = 0;
for(byte element:msgBytes){
twoBitBytes[twoBitByteCnt++]
= (byte)(element & 0x03);
twoBitBytes[twoBitByteCnt++]
= (byte)((element >> 2) & 0x03);
twoBitBytes[twoBitByteCnt++]
= (byte)((element >> 4) & 0x03);
}//end for loop

//Begin embedding the pairs of message bits
// at a predetermined pixel location
// in the image. Two bits from a single 6-bit
// message character are embedded into the
// lsb of the red, green, and blue pixel
// values. DISABLE THE PIXEL-SKIPPING
// FEATURE IN THIS VERSION OF THE PROGRAM.
// EMBED ONE CHARACTER INTO EVERY PIXEL
// STARTING WITH THE FIRST PIXEL.
//Specify the predefined insertion point.
//SET IT TO 0 FOR THIS VERSION OF THE
// PROGRAM.
int insertionPoint = 0;
System.out.println(
“Insertion point = ” + insertionPoint);
//Use a nested loop to operate on each pixel
// and to embed message bits into the red,
// green, and blue values of every pixel.
twoBitByteCnt = 0;//Re-use this variable
for(int row = 0;row < imgRows;row++){
for(int col = 0;col < imgCols;col++){
//Embed 6-bit message characters
// into every pixel location.
if((row * col > insertionPoint) &&
(twoBitByteCnt < twoBitBytes.length)){
//Replace the two lsb of the red,
// green, and blue pixel values with
// two bits from the six-bit message
// character.
temp3D[row][col][1] =
(temp3D[row][col][1] & 0xFC)
| twoBitBytes[twoBitByteCnt++];
temp3D[row][col][2] =
(temp3D[row][col][2] & 0xFC)
| twoBitBytes[twoBitByteCnt++];
temp3D[row][col][3] =
(temp3D[row][col][3] & 0xFC)
| twoBitBytes[twoBitByteCnt++];
}//end if
}//end for loop on col
}//end for loop on row

//Up to this point, the program demonstrates
// how to modify an image to include a hidden
// message.
//The remainder of the program demonstrates
// how to extract that message from the
// image. Note that this code requires
// prior knowledge of the insertion point
// value.
//Create an array object large enough to
// contain one byte for every two-bit byte
// that can be extracted from every pixel
// between the insertion point and the end of
// the message.
byte[] extractedTwoBitBytes =
new byte[(imgRows * imgCols
– insertionPoint) * 3];
twoBitByteCnt = 0;
for(int row = 0;row < imgRows;row++){
for(int col = 0;col < imgCols;col++){
if(row * col > insertionPoint){
//Extract and save the message
// characters two bits at a time.
extractedTwoBitBytes[twoBitByteCnt++]
= (byte)(temp3D[row][col][1] & 0x03);
extractedTwoBitBytes[twoBitByteCnt++]
= (byte)(temp3D[row][col][2] & 0x03);
extractedTwoBitBytes[twoBitByteCnt++]
= (byte)(temp3D[row][col][3] & 0x03);
}//end if
}//end for loop on col
}//end for loop on row

//Reconstruct and display the 64-char version
// of the message
StringBuffer extractedMsg =
new StringBuffer();
byte[] sixBitBytes =
new byte[extractedTwoBitBytes.length/3];
twoBitByteCnt = 0;
for(byte element:sixBitBytes){
//Shift and or the pairs of bits into the
// 6-bit format.
element = (byte)(extractedTwoBitBytes[
twoBitByteCnt++]);
element =
(byte)(element | (extractedTwoBitBytes[
twoBitByteCnt++] << 2));
element =
(byte)(element | (extractedTwoBitBytes[
twoBitByteCnt++] << 4));
extractedMsg.append(
(char)(element + 32));
//Stop processing when terminating
// underscore character is encountered.
if((char)(element + 32) == ‘_’)break;
}//end for-each

//Select and print every (6*imgCols + 1)
// character from the extracted message
System.out.println(
“Characters from reconstructed message:”);
for(int cnt = 0;cnt < extractedMsg.length()
;cnt++){
if(cnt%(6*imgCols + 1) == 0){
System.out.print(
extractedMsg.charAt(cnt));
}//end if
}//end for loop
System.out.println();//blank line
//Return the modified image for display.
return temp3D;
}//end processImg
//——————————————-//
}//end class ImgMod28a
Listing 11





Copyright 2005, Richard G. Baldwin.  Reproduction in whole or in part in any form or medium without express written permission from Richard Baldwin is prohibited.


About the author

Richard Baldwin is a college professor (at Austin Community College in Austin, TX) and private consultant whose primary focus is a combination of Java, C#, and XML. In addition to the many platform and/or language independent benefits of Java and C# applications, he believes that a combination of Java, C#, and XML will become the primary driving force in the delivery of structured information on the Web.

Richard has participated in numerous consulting projects and he frequently provides onsite training at the high-tech companies located in and around Austin, Texas.  He is the author of Baldwin’s Programming Tutorials, which have gained a worldwide following among experienced and aspiring programmers. He has also published articles in JavaPro magazine.


In addition to his programming expertise, Richard has many years of practical experience in Digital Signal Processing (DSP).  His first job after he earned his Bachelor’s degree was doing DSP in the Seismic Research Department of Texas Instruments.  (TI is still a world leader in DSP.)  In the following years, he applied his programming and DSP expertise to other interesting areas including sonar and underwater acoustics.


Richard holds an MSEE degree from Southern Methodist University and has many years of experience in the application of computer technology to real-world problems.


Baldwin@DickBaldwin.com

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories