Question
In this assignment, we'll be writing code that hides secret messages (strings) in binary files. This is a technique known as steganography, which basically means
In this assignment, we'll be writing code that hides secret messages (strings) in binary files. This is a technique known as "steganography," which basically means hiding messages in "plain sight." Anyone eavesdropping thinks you're just sharing an awesome audio file with your buddy, but there's actually a secret hidden inside.
The example file we'll be using is a .wav audio file which is well suited to this type of information hiding. This picture shows how it works:
The ...s in the picture indicate big chunks of data that we don't touch. Each int is 4 bytes and each char is 2 bytes (see the notes below about why java thinks you should waste 2 bytes on a character).
After the .wav file header (this is 50 bytes of data about the data itself and includes things like the number of audio channels and how to interpret the data the comes after), we'll put information about our message into the file. If we scribble our message into the first 50 bytes, we'll likely break the file so that it can't be played anymore. By hiding our message after that, the file will play back just fine, and the only hint that our message is hidden there is that there will be some clicking noises added. Our message will be pretty short (10s of characters maybe?) compared to the size of the data which is millions of bytes, so our secret will go (mostly) unnoticed.
First, we'll write an int that contains the length of the message. After that, we'll put one character of the message, then an int saying where to find the next character. At that location of the file, we'll do the same (the character and the position of the next character). We repeat this until the end of the message (the int after the last number should be 0). If you took 1571, you can think of this as a sort of linked list.
Bytes vs Chars (Java and UTF16)
Historically, computers were designed to work with the latin alphabet (a-z) since a lot of early development happened in North America. All the characters they cared about fit in 1 byte, so variables of type char were 1 byte in size.
However, as computers became more common, it became more and more important to support other character sets ( Arabic, Chinese characters, etc. Also, more recently, emoji). A group of smart CS people called the Unicode Consortium got together to figure out a good way to store all of these character sets in a unified way. They came up with a few good approaches. The best is a system called UTF-8, where the latin alphabet is stored the same way it was historically, so programs expecting data like that will still "just work." Java chose not to use UTF-8, and chose a system called UTF-16 instead. In Java, a char is actually 2 bytes, not one. For english text, this is a waste since all english characters fit in 1 byte and the second byte holds no useful information.
Instructions
You'll be writing 4 classes: a class named Steganography with static methods for encoding and decoding secret messages, 2 new exceptions type, and a driver containing main. Here's a description of what should be in them:
NotEnoughSpaceException
This class inherits from exception. Its message should indicate how much space was needed, and how much is available. It should just contain one constructor that takes the size information as parameters and calls the Exception constructor to set the message appropriately.
SecretMessageException
This exception will be thrown when there's an error decoding the message. This means either the input file doesn't exist, or there's no secret message there (probably causing you to try to seek() outside of the file). It should contain a constructor that takes a String message parameter.
Steganography
This class will contain 3 static methods:
public static int[] getDataLocations( int start, int stop, int numLocations) throws NotEnoughSpaceException
This method returns an array that indicates where each character in the message should be stored. For each character, you'll need enough space to store the character itself, and an integer representing where the next character is (how many bytes total will that be?). Start and stop indicate the first and last bytes that can be used. If there's not enough room to store all the bytes, this method should throw a NotEnoughSpaceException.
You can use whatever approach you'd like for this method, provided that there is space between each location. The first location should be equal to start.
A simple solution is to place all the locations as far apart as possible (find the maximum distance you can fit between each one and place them equidistantly far apart). Choosing locations randomly is another approach, but requires some care so that the locations don't cause overlaps. Do not bunch all the data together. It'll make it obvious that we're hiding a secret message and will be very noticeable if we play back the audio file.
public static String decodeMessage(File inputFile) throws SecretMessageException
This method will open an input file and return the secret message hidden inside. If this fails for any reason, you should throw a SecretMessageException (this means you should use a try/catch block, and inside of your catch block, you will throw a SecretMessageException with an appropriate message).
You'll make use of the RandomAccessFile class and its seek(), readChar(), and readInt() methods for this method. Hint: the String class has a constructor that takes an array of chars.
To test this method, see if you can read the secret message in this file: encoded-1.wavClick to view undefined. You should also be able to play the file with an audio player and it won't be obvious that there's a hidden message.
public static void encodeMessage(File inputFile, File outputFile, String message) throws Exception
This method performs the following operations:
make a copy of inputFile, and save it in outputFile. You'll want to make use of the static copy() method in the Files class. File objects have a toPath() method that may be useful for this as well.
compute where to put the data in the file (use your getDataLocations method here). Remember, the first 50 bytes should not be touched. Immediately after that, we'll write the size of the message, so where will the hidden message stuff be written?
write the secret message in the file. Again, this will involve the RandomAccessFile class and its seek(), writeInt(), and writeChar() methods.
The encodeMessage method can pass the buck on errors (it can use a generic "throws Exception" clause at the top and can return immediately if any exceptions are thrown).
Driver
The driver should ask the user to enter a message, then write the secret message to a file, read the secret message back from the file, and verify that it matches. Make sure that you create a NEW file that you hide the message in, and don't overwrite the old one. Here is the original version of the .wav file from above that you can use to test with: DefinitelyNotASecretMessage.wav
length of first message letter (int index of next a letter index of Header, 50 bytes (char) next (char) (int) (int)Step by Step Solution
There are 3 Steps involved in it
Step: 1
Get Instant Access to Expert-Tailored Solutions
See step-by-step solutions with expert insights and AI powered tools for academic success
Step: 2
Step: 3
Ace Your Homework with AI
Get the answers you need in no time with our AI-driven, step-by-step assistance
Get Started