Overview
Your challenge is to enhance BitmapHacker.java to perform steganography , which is the art
and science of hiding information, often inside other information. This differs from cryptog
raphy , which involves transforming information in order to protect it from adversaries, but
not necessarily concealing the presence of the information.
In particular, your goal is to hide information inside Bitmap files (uncompressed, 24-bit-color
Bitmap files), and also to extract such hidden information. For most BMP images, it is pos
sible to hide 6 bits inside each pixel without altering the image in a way that is noticeable to
the human eye. This is done by replacing the least significant 2 bits of each color component
of some of the pixels with 2 bits of hidden information. Because these are the low-end bits,
modifying them rarely alters the appearance of the image perceptibly. (Note that we’re only
playing around with pixel data; we don’t touch the header portion of a BMP file, in order
to avoid corrupting the file.)
To be precise, suppose you want to hide a file of length B bytes inside a BMP file. Form an
int array of length B + 4, each position of which will store a single byte as a value in the
range 0 . . . 255. Use the first 4 positions to encode the value B as a 4-byte (32-bit) integer
in the little-endian format . Then fill the remaining positions in the array with the bytes of
the file in order (so the first byte of the file goes in the 5-th position in the array, i.e., in the
position with index 4). This array of data can be hidden inside a BMP file as long as the
number of bits it contains is less than or equal to the “hiding room” of the BMP file, which
is 6 bits per pixel.
(As you may have guessed, the reason for including the length of the to-be-hidden file in
the hidden data is that this makes it possible for an extraction procedure to determine how
many bytes to extract.)
To be even more precise, suppose the bytes in the array are x 0 , x 1 , x 2 , . . . .
• The 2 most significant bits of x 0 will replace the 2 least significant bits of the red
component of the pixel in row 0, column 0. (To be even more precise, the most
significant bit of x 0 will replace the second least significant bit of the red component
of this top-left pixel.)
• The 3-rd and 4-th most significant bits of x 0 will replace the 2 least significant bits of
the green component of the pixel in row 0, column 0.
• The 5-th and 6-th most significant bits of x 0 will replace the 2 least significant bits of
the blue component of the pixel in row 0, column 0.
• The two least significant bits of x 0 will replace the 2 least significant bits of the red
component of the pixel in row 0, column 1.
• . . . and so on . . .
Additions to the BitmapHacker Class
(The two methods described below potentially throw IOException s. As before, you are not
required to use try / catch to handle these; instead, each method signature can just specify
that it throws IOException .)
• hide — This method has a single parameter of type File , and returns a boolean . If
the argument is null , construct and throw an IllegalArgumentException containing
an appropriate message. If the argument is not null , but does not specify a disk
file, construct and throw an IllegalArgumentException containing an appropriate
message. Otherwise, attempt to hide the specified file in the current 2-D pixel array.
If the data to be hidden (the 4 “size bytes” plus the specified file) is too large for the
pixel array, return false . Otherwise, hide the data and return true .
• unhide — This method has a single parameter of type File , and returns a boolean . If
the argument is null , construct and throw an IllegalArgumentException containing
an appropriate message. Otherwise, attempt to extract a hidden file from the current
2-D pixel array. If the number of pixels is too small even to allow the hiding of the
4-byte “size bytes”, return false . If there is enough room for the size bytes, but, when
extracted, the indicated file size is larger than the amount of remaining “hiding room”
in the pixel array, return false . Otherwise, extract the file and write it to disk at the
location specified by the File argument.
Sample Files
You have been given three BMP files, each containing a hidden file:
• baby-owl-mod.bmp – contains a hidden PDF file
• cat-mod.bmp — contains a hidden PDF file
• dragon-mod.bmp – contains a hidden JPEG file
Fine Points
• Note that hiding in / unhiding from a BMP file does not involve the possible zero
padding bytes that you needed to consider in Phase 1. When hide is called, the
BitmapHacker constructor has already extracted the pixel data from a BMP file and
placed it in the 2-D pixels array, so simply hide the file to be hidden in the color com
ponents of the pixels in pixels , as specified. There is no change to writeImageToFile ;
it creates an output BMP file based on the pixel data in pixels , whether or not any
thing has been hidden.
Similarly, when unhide is called, the BitmapHacker constructor has already placed
pixel data from a BMP file into the pixels array, so just attempt to extract a hidden
file from this pixel data.
• Claim: The size of any file that is correctly hidden inside a BMP file can never be
too large to fit in an int . The reason is that a BMP file has maximum size 2 32 − 1
bytes (see the end of the Phase 1 handout), which means that the maximum number
of pixels is approximately 1 . 4 billion ((2 32 − 1) / 3). Since only 3/4 of a byte can be
hidden in each pixel, clearly the total number of bytes that can be hidden is less than
1.4 billion, so this number will fit safely in an int .
However, what if unhide is applied to a BMP file in which nothing has been hidden?
In this case, the “size” information that is extracted could be any 32-bit value. If your
unhide method extracts the size value into an int , how will your code behave if this
value overflows an int (leaving you with a negative number)?