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)?