Sinisterly
Steganography Using Java - Printable Version

+- Sinisterly (https://sinister.li)
+-- Forum: Coding (https://sinister.li/Forum-Coding)
+--- Forum: Java, JVM, & JRE (https://sinister.li/Forum-Java-JVM-JRE)
+--- Thread: Steganography Using Java (/Thread-Steganography-Using-Java)

Pages: 1 2


Steganography Using Java - Solixious - 10-31-2013

Steganography Using Java


1. Introduction

Hello my dear friends and fellow members of Hack Community. Today I am going to teach you a little about Steganography and we'll also implement the theory we learn practically, in a code I'll provide you with, at the end of this tutorial.
Steganography is the art using which a secret message or data is hidden inside other data in such a way that no one suspects of it's existence. Many people confuse steganography with cryptography which I assure you is not right. An advantage of steganography over cryptography is that it does not attract unwanted attention.
The theory I am about you explain to you would require understanding of bytes and bit operations which I would happily explain in the following section.

2. Bytes

Byte is a unit of data comprising of 8 bits (0 or 1), which can also be represented in integer form in following manner.
Example :
Code:
00010100 ===> 20 00010110 ===> 22 11111111 ===> 255 00001010 ===> 10 10000000 ===> 128

3. Bit Operations

Bitwise AND

Considering two types of input values, 1 (HIGH) and 0 (LOW), the output from an AND gate will be HIGH only when all its inputs are HIGH. The output of AND gate will be LOW in all other cases.
Code:
0 AND 0 = 0 0 AND 1 = 0 1 AND 0 = 0 1 AND 1 = 1

Now let us consider 2 values 34 and 60. We will see how bitwise AND will function on these two values :
Code:
00100010 ===> 34 00111100 ===> 60 ===================================== 00100000 ===> 34 & 60 = 32 =====================================

Code:
byte b = 34 & 60; //value 32 will be stored in variable b

Bitwise OR

Considering two types of input values, 1 (HIGH) and 0 (LOW), the output from an OR gate will be LOW only when all its inputs are LOW. The output of OR gate will be HIGH in all other cases.
Code:
0 OR 0 = 0 0 OR 1 = 1 1 OR 0 = 1 1 OR 1 = 1

Now let us consider 2 values 34 and 60. We will see how bitwise OR will function on these two values :
Code:
00100010 ===> 34 00111100 ===> 60 ===================================== 00111110 ---> 34 | 60 = 62 =====================================

Code:
byte b = 34 | 60; //value 62 will be stored in variable b

Left Shift

A certain byte being left shifted once denotes that all the bits in that byte will shift itself to the next most significant bit (leftwards) while the left most bit (MSB) is removed. A '0' is added to the position of LSB (the rightmost bit). For higher amount of shifts, this process repeats itself than many times. The following example will hopefully make it clearer.
Code:
00100010 ===> 34 01000100 ===> 34<<1 = 68 10001000 ===> 34<<2 = 136 00010000 ===> 34<<3 = 16

It can be noticed that any byte will result in a value of 0 if it is left shifted 8 or more times.

Right Shift

It is similar to the left shift operation except for the fact that the bit values move towards right. Following are the examples of right shift operations.
Code:
00100010 ===> 34 00010001 ===> 34>>1 = 17 00001000 ===> 34>>2 = 8

4. Pixels

Images we are about to use will be made up of small units of colours called pixels. Each pixel consists of its own value of red, green and blue. Each of these values range from 0-255(or 00 to FF) and occupy a space of 1 byte. Together, these pixels arranged will provide us with the image we see.

5. Theory of Hiding Files inside Images

We know that binary files are made up of units of small data called bytes where each byte consists of 8 bits. Our aim here is to hide this byte's data in pixels of the image file in such a way that it doesn't affect the original image's appearance to our naked eyes. To accomplish this task, we will change the Least Significant Bit (LSB) of the pixel's RGB values to store the data from the byte.
Since we are planning to store 1 bit from the byte in each of R, G and B of a single pixel, one pixel will hold 3 bits of hidden data and 1 byte of data can be hidden using 3 different pixels (with one value remaining unused).
Code:
=============================================== Byte to be hidden : 89 ===> 01011001 =============================================== Pixel 1 Red : 10011010 Green : 00110010 Blue : 00100111 Pixel 2 Red : 10011010 Green : 00110010 Blue : 00100111 Pixel 3 Red : 10011010 Green : 00110010 Blue : 00100111

In order to hide the given byte, we change the values of RBG of the given pixels in following manner.
Code:
New values of Pixels Pixel 1 Red : 10011010 Green : 00110011 Blue : 00100110 Pixel 2 Red : 10011011 Green : 00110011 Blue : 00100110 Pixel 3 Red : 10011010 Green : 00110011 Blue : 00100111

If you haven't noticed yet, the LSBs of RGBs of all 3 pixels (except Blue of pixel 3) have been changed. Write those changed pixels one after other and you'll get the original byte we were trying to hide.

In order to hide an entire file, we can read the binary bytes of that file one after other and hide it in the pixels of an image in the manner we did above. We can extract this data and write it back to a file later when needed. We could collectively use the unused value in 3rd pixels (blue in this case) for each byte to store values like the size of file hidden, file extension, password to extract the file from the image, etc.


6. Source
I've taken help of a sample code written by William Wilson with a little modification from @Deque to showcase simple implementation of Steganography in Java. All credits for this code goes to them.

Code:
import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.awt.image.DataBufferByte; import java.awt.image.WritableRaster; import java.io.File; import java.io.IOException; import javax.imageio.ImageIO; public class Steganography { /** * Encrypt an image with text, the output file will be of type .png * * @param imageIn * The image to modify * * @param imageOut * The output file * * @param message * The text to hide in the image * @throws IOException */ public static void hide(File imageIn, File imageOut, String message) throws IOException { BufferedImage originalImage = ImageIO.read(imageIn); // user space is not necessary for Encrypting BufferedImage image = userSpace(originalImage); image = addText(image, message); String formatName = "png"; ImageIO.write(image, formatName, imageOut); } /** * Decrypt assumes the image being used is of type .png, extracts the hidden * text from an image * * @param file * the image with the hidden message * * @return decoded text * @throws IOException */ public static String reveal(File file) throws IOException { // user space is necessary for decrypting BufferedImage image = userSpace(ImageIO.read(file)); byte[] decode = reveal(getByteData(image)); return new String(decode); } /** * Handles the addition of text into an image * * @param image * The image to add hidden text to * * @param text * The text to hide in the image * * @return Returns the image with the text embedded in it */ private static BufferedImage addText(BufferedImage image, String text) { // convert all items to byte arrays: image, message, message length byte imageBytes[] = getByteData(image); byte messageBytes[] = text.getBytes(); byte messageLength[] = bitConversion(messageBytes.length); hide(imageBytes, messageLength, 0); // 0 first positiong hide(imageBytes, messageBytes, 32); // 4 bytes of space for // length: // 4bytes*8bit = 32 bits return image; } /** * Creates a user space version of a Buffered Image, for editing and saving * bytes * * @param image * The image to put into user space, removes compression * interferences * * @return The user space version of the supplied image */ private static BufferedImage userSpace(BufferedImage image) { // create new_img with the attributes of image BufferedImage newImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_3BYTE_BGR); Graphics2D graphics = newImage.createGraphics(); graphics.drawRenderedImage(image, null); graphics.dispose(); // release all allocated memory for this image return newImage; } /** * Gets the byte array of an image * * @param image * The image to get byte data from * * @return Returns the byte array of the image supplied * */ private static byte[] getByteData(BufferedImage image) { WritableRaster raster = image.getRaster(); DataBufferByte buffer = (DataBufferByte) raster.getDataBuffer(); return buffer.getData(); } /** * Gernerates proper byte format of an integer * * @param i * The integer to convert * * @return Returns a byte[4] array converting the supplied integer into * bytes */ private static byte[] bitConversion(int i) { byte byte3 = (byte) ((i & 0xFF000000) >>> 24); // 0 byte byte2 = (byte) ((i & 0x00FF0000) >>> 16); // 0 byte byte1 = (byte) ((i & 0x0000FF00) >>> 8); // 0 byte byte0 = (byte) ((i & 0x000000FF)); // {0,0,0,byte0} is equivalent, since all shifts >=8 will be 0 return (new byte[] { byte3, byte2, byte1, byte0 }); } /** * Encode an array of bytes into another array of bytes at a supplied offset * * @param image * Array of data representing an image * * @param addition * Array of data to add to the supplied image data array * * @param offset * The offset into the image array to add the addition data * * @return data Array of merged image and addition data */ private static byte[] hide(byte[] image, byte[] addition, int offset) { // check that the data + offset will fit in the image if (addition.length + offset > image.length) { throw new IllegalArgumentException("File not long enough!"); } // loop through each addition byte for (int i = 0; i < addition.length; ++i) { // loop through the 8 bits of each byte int add = addition[i]; // ensure the new offset value carries on through both loops for (int bit = 7; bit >= 0; --bit, ++offset) { // assign an integer to b, shifted by bit spaces AND 1 // a single bit of the current byte int b = (add >>> bit) & 1; // assign the bit by taking: [(previous byte value) AND 0xfe] OR // bit to add // changes the last bit of the byte in the image to be the bit // of addition image[offset] = (byte) ((image[offset] & 0xFE) | b); } } return image; } /** * Retrieves hidden text from an image * * @param image * Array of data, representing an image * * @return Array of data which contains the hidden text */ private static byte[] reveal(byte[] image) { int length = 0; int offset = 32; // loop through 32 bytes of data to determine text length for (int i = 0; i < 32; ++i) { // i=24 will also work, as only the 4th // byte contains real data length = (length << 1) | (image[i] & 1); } byte[] result = new byte[length]; // loop through each byte of text for (int b = 0; b < result.length; ++b) { // loop through each bit within a byte of text for (int i = 0; i < 8; ++i, ++offset) { // assign bit: [(new byte value) << 1] OR [(text byte) AND 1] result[b] = (byte) ((result[b] << 1) | (image[offset] & 1)); } } return result; } }

I'm planning to write a software to accomplish similar task myself. I might share it's source here when it is over.

I hope you enjoyed reading this article.

Regards.


Steganography Using Java - Solixious - 10-31-2013

Steganography Using Java


1. Introduction

Hello my dear friends and fellow members of Hack Community. Today I am going to teach you a little about Steganography and we'll also implement the theory we learn practically, in a code I'll provide you with, at the end of this tutorial.
Steganography is the art using which a secret message or data is hidden inside other data in such a way that no one suspects of it's existence. Many people confuse steganography with cryptography which I assure you is not right. An advantage of steganography over cryptography is that it does not attract unwanted attention.
The theory I am about you explain to you would require understanding of bytes and bit operations which I would happily explain in the following section.

2. Bytes

Byte is a unit of data comprising of 8 bits (0 or 1), which can also be represented in integer form in following manner.
Example :
Code:
00010100 ===> 20 00010110 ===> 22 11111111 ===> 255 00001010 ===> 10 10000000 ===> 128

3. Bit Operations

Bitwise AND

Considering two types of input values, 1 (HIGH) and 0 (LOW), the output from an AND gate will be HIGH only when all its inputs are HIGH. The output of AND gate will be LOW in all other cases.
Code:
0 AND 0 = 0 0 AND 1 = 0 1 AND 0 = 0 1 AND 1 = 1

Now let us consider 2 values 34 and 60. We will see how bitwise AND will function on these two values :
Code:
00100010 ===> 34 00111100 ===> 60 ===================================== 00100000 ===> 34 & 60 = 32 =====================================

Code:
byte b = 34 & 60; //value 32 will be stored in variable b

Bitwise OR

Considering two types of input values, 1 (HIGH) and 0 (LOW), the output from an OR gate will be LOW only when all its inputs are LOW. The output of OR gate will be HIGH in all other cases.
Code:
0 OR 0 = 0 0 OR 1 = 1 1 OR 0 = 1 1 OR 1 = 1

Now let us consider 2 values 34 and 60. We will see how bitwise OR will function on these two values :
Code:
00100010 ===> 34 00111100 ===> 60 ===================================== 00111110 ---> 34 | 60 = 62 =====================================

Code:
byte b = 34 | 60; //value 62 will be stored in variable b

Left Shift

A certain byte being left shifted once denotes that all the bits in that byte will shift itself to the next most significant bit (leftwards) while the left most bit (MSB) is removed. A '0' is added to the position of LSB (the rightmost bit). For higher amount of shifts, this process repeats itself than many times. The following example will hopefully make it clearer.
Code:
00100010 ===> 34 01000100 ===> 34<<1 = 68 10001000 ===> 34<<2 = 136 00010000 ===> 34<<3 = 16

It can be noticed that any byte will result in a value of 0 if it is left shifted 8 or more times.

Right Shift

It is similar to the left shift operation except for the fact that the bit values move towards right. Following are the examples of right shift operations.
Code:
00100010 ===> 34 00010001 ===> 34>>1 = 17 00001000 ===> 34>>2 = 8

4. Pixels

Images we are about to use will be made up of small units of colours called pixels. Each pixel consists of its own value of red, green and blue. Each of these values range from 0-255(or 00 to FF) and occupy a space of 1 byte. Together, these pixels arranged will provide us with the image we see.

5. Theory of Hiding Files inside Images

We know that binary files are made up of units of small data called bytes where each byte consists of 8 bits. Our aim here is to hide this byte's data in pixels of the image file in such a way that it doesn't affect the original image's appearance to our naked eyes. To accomplish this task, we will change the Least Significant Bit (LSB) of the pixel's RGB values to store the data from the byte.
Since we are planning to store 1 bit from the byte in each of R, G and B of a single pixel, one pixel will hold 3 bits of hidden data and 1 byte of data can be hidden using 3 different pixels (with one value remaining unused).
Code:
=============================================== Byte to be hidden : 89 ===> 01011001 =============================================== Pixel 1 Red : 10011010 Green : 00110010 Blue : 00100111 Pixel 2 Red : 10011010 Green : 00110010 Blue : 00100111 Pixel 3 Red : 10011010 Green : 00110010 Blue : 00100111

In order to hide the given byte, we change the values of RBG of the given pixels in following manner.
Code:
New values of Pixels Pixel 1 Red : 10011010 Green : 00110011 Blue : 00100110 Pixel 2 Red : 10011011 Green : 00110011 Blue : 00100110 Pixel 3 Red : 10011010 Green : 00110011 Blue : 00100111

If you haven't noticed yet, the LSBs of RGBs of all 3 pixels (except Blue of pixel 3) have been changed. Write those changed pixels one after other and you'll get the original byte we were trying to hide.

In order to hide an entire file, we can read the binary bytes of that file one after other and hide it in the pixels of an image in the manner we did above. We can extract this data and write it back to a file later when needed. We could collectively use the unused value in 3rd pixels (blue in this case) for each byte to store values like the size of file hidden, file extension, password to extract the file from the image, etc.


6. Source
I've taken help of a sample code written by William Wilson with a little modification from @Deque to showcase simple implementation of Steganography in Java. All credits for this code goes to them.

Code:
import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.awt.image.DataBufferByte; import java.awt.image.WritableRaster; import java.io.File; import java.io.IOException; import javax.imageio.ImageIO; public class Steganography { /** * Encrypt an image with text, the output file will be of type .png * * @param imageIn * The image to modify * * @param imageOut * The output file * * @param message * The text to hide in the image * @throws IOException */ public static void hide(File imageIn, File imageOut, String message) throws IOException { BufferedImage originalImage = ImageIO.read(imageIn); // user space is not necessary for Encrypting BufferedImage image = userSpace(originalImage); image = addText(image, message); String formatName = "png"; ImageIO.write(image, formatName, imageOut); } /** * Decrypt assumes the image being used is of type .png, extracts the hidden * text from an image * * @param file * the image with the hidden message * * @return decoded text * @throws IOException */ public static String reveal(File file) throws IOException { // user space is necessary for decrypting BufferedImage image = userSpace(ImageIO.read(file)); byte[] decode = reveal(getByteData(image)); return new String(decode); } /** * Handles the addition of text into an image * * @param image * The image to add hidden text to * * @param text * The text to hide in the image * * @return Returns the image with the text embedded in it */ private static BufferedImage addText(BufferedImage image, String text) { // convert all items to byte arrays: image, message, message length byte imageBytes[] = getByteData(image); byte messageBytes[] = text.getBytes(); byte messageLength[] = bitConversion(messageBytes.length); hide(imageBytes, messageLength, 0); // 0 first positiong hide(imageBytes, messageBytes, 32); // 4 bytes of space for // length: // 4bytes*8bit = 32 bits return image; } /** * Creates a user space version of a Buffered Image, for editing and saving * bytes * * @param image * The image to put into user space, removes compression * interferences * * @return The user space version of the supplied image */ private static BufferedImage userSpace(BufferedImage image) { // create new_img with the attributes of image BufferedImage newImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_3BYTE_BGR); Graphics2D graphics = newImage.createGraphics(); graphics.drawRenderedImage(image, null); graphics.dispose(); // release all allocated memory for this image return newImage; } /** * Gets the byte array of an image * * @param image * The image to get byte data from * * @return Returns the byte array of the image supplied * */ private static byte[] getByteData(BufferedImage image) { WritableRaster raster = image.getRaster(); DataBufferByte buffer = (DataBufferByte) raster.getDataBuffer(); return buffer.getData(); } /** * Gernerates proper byte format of an integer * * @param i * The integer to convert * * @return Returns a byte[4] array converting the supplied integer into * bytes */ private static byte[] bitConversion(int i) { byte byte3 = (byte) ((i & 0xFF000000) >>> 24); // 0 byte byte2 = (byte) ((i & 0x00FF0000) >>> 16); // 0 byte byte1 = (byte) ((i & 0x0000FF00) >>> 8); // 0 byte byte0 = (byte) ((i & 0x000000FF)); // {0,0,0,byte0} is equivalent, since all shifts >=8 will be 0 return (new byte[] { byte3, byte2, byte1, byte0 }); } /** * Encode an array of bytes into another array of bytes at a supplied offset * * @param image * Array of data representing an image * * @param addition * Array of data to add to the supplied image data array * * @param offset * The offset into the image array to add the addition data * * @return data Array of merged image and addition data */ private static byte[] hide(byte[] image, byte[] addition, int offset) { // check that the data + offset will fit in the image if (addition.length + offset > image.length) { throw new IllegalArgumentException("File not long enough!"); } // loop through each addition byte for (int i = 0; i < addition.length; ++i) { // loop through the 8 bits of each byte int add = addition[i]; // ensure the new offset value carries on through both loops for (int bit = 7; bit >= 0; --bit, ++offset) { // assign an integer to b, shifted by bit spaces AND 1 // a single bit of the current byte int b = (add >>> bit) & 1; // assign the bit by taking: [(previous byte value) AND 0xfe] OR // bit to add // changes the last bit of the byte in the image to be the bit // of addition image[offset] = (byte) ((image[offset] & 0xFE) | b); } } return image; } /** * Retrieves hidden text from an image * * @param image * Array of data, representing an image * * @return Array of data which contains the hidden text */ private static byte[] reveal(byte[] image) { int length = 0; int offset = 32; // loop through 32 bytes of data to determine text length for (int i = 0; i < 32; ++i) { // i=24 will also work, as only the 4th // byte contains real data length = (length << 1) | (image[i] & 1); } byte[] result = new byte[length]; // loop through each byte of text for (int b = 0; b < result.length; ++b) { // loop through each bit within a byte of text for (int i = 0; i < 8; ++i, ++offset) { // assign bit: [(new byte value) << 1] OR [(text byte) AND 1] result[b] = (byte) ((result[b] << 1) | (image[offset] & 1)); } } return result; } }

I'm planning to write a software to accomplish similar task myself. I might share it's source here when it is over.

I hope you enjoyed reading this article.

Regards.


RE: Steganography Using Java - Deque - 10-31-2013

That's a very good introduction. I like that you skim over the basics (although it wouldn't be enough to understand them if you didn't know the basics) and how you make clear how the bytes are saved.

You might add a section about detecting this type of steganography (using a histogram).

For the Java code: While the author is sure not a beginner in coding, he doesn't seem to know much about using Java. The code is actually pretty bad. Do you mind if I correct it?


RE: Steganography Using Java - Deque - 10-31-2013

That's a very good introduction. I like that you skim over the basics (although it wouldn't be enough to understand them if you didn't know the basics) and how you make clear how the bytes are saved.

You might add a section about detecting this type of steganography (using a histogram).

For the Java code: While the author is sure not a beginner in coding, he doesn't seem to know much about using Java. The code is actually pretty bad. Do you mind if I correct it?


RE: Steganography Using Java - Solixious - 10-31-2013

(10-31-2013, 07:50 PM)Deque Wrote: That's a very good introduction. I like that you skim over the basics (although it wouldn't be enough to understand them if you didn't know the basics) and how you make clear how the bytes are saved.

You might add a section about detecting this type of steganography (using a histogram).

For the Java code: While the author is sure not a beginner in coding, he doesn't seem to know much about using Java. The code is actually pretty bad. Do you mind if I correct it?

I sure would like to describe how steganalysis for this type of steganography is done. I would probably make another thread for it, or modify this very thread later depending on the length of that next article I'd write.

I would like it if you corrected the code.

Thank you for your feedback.


Regards


RE: Steganography Using Java - Solixious - 10-31-2013

(10-31-2013, 07:50 PM)Deque Wrote: That's a very good introduction. I like that you skim over the basics (although it wouldn't be enough to understand them if you didn't know the basics) and how you make clear how the bytes are saved.

You might add a section about detecting this type of steganography (using a histogram).

For the Java code: While the author is sure not a beginner in coding, he doesn't seem to know much about using Java. The code is actually pretty bad. Do you mind if I correct it?

I sure would like to describe how steganalysis for this type of steganography is done. I would probably make another thread for it, or modify this very thread later depending on the length of that next article I'd write.

I would like it if you corrected the code.

Thank you for your feedback.


Regards


RE: Steganography Using Java - Deque - 10-31-2013

This is still not perfect, but much better.

Code:
import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.awt.image.DataBufferByte; import java.awt.image.WritableRaster; import java.io.File; import java.io.IOException; import javax.imageio.ImageIO; public class Steganography { /** * Encrypt an image with text, the output file will be of type .png * * @param imageIn * The image to modify * * @param imageOut * The output file * * @param message * The text to hide in the image * @throws IOException */ public static void hide(File imageIn, File imageOut, String message) throws IOException { BufferedImage originalImage = ImageIO.read(imageIn); // user space is not necessary for Encrypting BufferedImage image = userSpace(originalImage); image = addText(image, message); String formatName = "png"; ImageIO.write(image, formatName, imageOut); } /** * Decrypt assumes the image being used is of type .png, extracts the hidden * text from an image * * @param file * the image with the hidden message * * @return decoded text * @throws IOException */ public static String reveal(File file) throws IOException { // user space is necessary for decrypting BufferedImage image = userSpace(ImageIO.read(file)); byte[] decode = reveal(getByteData(image)); return new String(decode); } /** * Handles the addition of text into an image * * @param image * The image to add hidden text to * * @param text * The text to hide in the image * * @return Returns the image with the text embedded in it */ private static BufferedImage addText(BufferedImage image, String text) { // convert all items to byte arrays: image, message, message length byte imageBytes[] = getByteData(image); byte messageBytes[] = text.getBytes(); byte messageLength[] = bitConversion(messageBytes.length); hide(imageBytes, messageLength, 0); // 0 first positiong hide(imageBytes, messageBytes, 32); // 4 bytes of space for // length: // 4bytes*8bit = 32 bits return image; } /** * Creates a user space version of a Buffered Image, for editing and saving * bytes * * @param image * The image to put into user space, removes compression * interferences * * @return The user space version of the supplied image */ private static BufferedImage userSpace(BufferedImage image) { // create new_img with the attributes of image BufferedImage newImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_3BYTE_BGR); Graphics2D graphics = newImage.createGraphics(); graphics.drawRenderedImage(image, null); graphics.dispose(); // release all allocated memory for this image return newImage; } /** * Gets the byte array of an image * * @param image * The image to get byte data from * * @return Returns the byte array of the image supplied * */ private static byte[] getByteData(BufferedImage image) { WritableRaster raster = image.getRaster(); DataBufferByte buffer = (DataBufferByte) raster.getDataBuffer(); return buffer.getData(); } /** * Gernerates proper byte format of an integer * * @param i * The integer to convert * * @return Returns a byte[4] array converting the supplied integer into * bytes */ private static byte[] bitConversion(int i) { byte byte3 = (byte) ((i & 0xFF000000) >>> 24); // 0 byte byte2 = (byte) ((i & 0x00FF0000) >>> 16); // 0 byte byte1 = (byte) ((i & 0x0000FF00) >>> 8); // 0 byte byte0 = (byte) ((i & 0x000000FF)); // {0,0,0,byte0} is equivalent, since all shifts >=8 will be 0 return (new byte[] { byte3, byte2, byte1, byte0 }); } /** * Encode an array of bytes into another array of bytes at a supplied offset * * @param image * Array of data representing an image * * @param addition * Array of data to add to the supplied image data array * * @param offset * The offset into the image array to add the addition data * * @return data Array of merged image and addition data */ private static byte[] hide(byte[] image, byte[] addition, int offset) { // check that the data + offset will fit in the image if (addition.length + offset > image.length) { throw new IllegalArgumentException("File not long enough!"); } // loop through each addition byte for (int i = 0; i < addition.length; ++i) { // loop through the 8 bits of each byte int add = addition[i]; // ensure the new offset value carries on through both loops for (int bit = 7; bit >= 0; --bit, ++offset) { // assign an integer to b, shifted by bit spaces AND 1 // a single bit of the current byte int b = (add >>> bit) & 1; // assign the bit by taking: [(previous byte value) AND 0xfe] OR // bit to add // changes the last bit of the byte in the image to be the bit // of addition image[offset] = (byte) ((image[offset] & 0xFE) | b); } } return image; } /** * Retrieves hidden text from an image * * @param image * Array of data, representing an image * * @return Array of data which contains the hidden text */ private static byte[] reveal(byte[] image) { int length = 0; int offset = 32; // loop through 32 bytes of data to determine text length for (int i = 0; i < 32; ++i) { // i=24 will also work, as only the 4th // byte contains real data length = (length << 1) | (image[i] & 1); } byte[] result = new byte[length]; // loop through each byte of text for (int b = 0; b < result.length; ++b) { // loop through each bit within a byte of text for (int i = 0; i < 8; ++i, ++offset) { // assign bit: [(new byte value) << 1] OR [(text byte) AND 1] result[b] = (byte) ((result[b] << 1) | (image[offset] & 1)); } } return result; } }

If you have any questions about the changes, just ask.


RE: Steganography Using Java - Deque - 10-31-2013

This is still not perfect, but much better.

Code:
import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.awt.image.DataBufferByte; import java.awt.image.WritableRaster; import java.io.File; import java.io.IOException; import javax.imageio.ImageIO; public class Steganography { /** * Encrypt an image with text, the output file will be of type .png * * @param imageIn * The image to modify * * @param imageOut * The output file * * @param message * The text to hide in the image * @throws IOException */ public static void hide(File imageIn, File imageOut, String message) throws IOException { BufferedImage originalImage = ImageIO.read(imageIn); // user space is not necessary for Encrypting BufferedImage image = userSpace(originalImage); image = addText(image, message); String formatName = "png"; ImageIO.write(image, formatName, imageOut); } /** * Decrypt assumes the image being used is of type .png, extracts the hidden * text from an image * * @param file * the image with the hidden message * * @return decoded text * @throws IOException */ public static String reveal(File file) throws IOException { // user space is necessary for decrypting BufferedImage image = userSpace(ImageIO.read(file)); byte[] decode = reveal(getByteData(image)); return new String(decode); } /** * Handles the addition of text into an image * * @param image * The image to add hidden text to * * @param text * The text to hide in the image * * @return Returns the image with the text embedded in it */ private static BufferedImage addText(BufferedImage image, String text) { // convert all items to byte arrays: image, message, message length byte imageBytes[] = getByteData(image); byte messageBytes[] = text.getBytes(); byte messageLength[] = bitConversion(messageBytes.length); hide(imageBytes, messageLength, 0); // 0 first positiong hide(imageBytes, messageBytes, 32); // 4 bytes of space for // length: // 4bytes*8bit = 32 bits return image; } /** * Creates a user space version of a Buffered Image, for editing and saving * bytes * * @param image * The image to put into user space, removes compression * interferences * * @return The user space version of the supplied image */ private static BufferedImage userSpace(BufferedImage image) { // create new_img with the attributes of image BufferedImage newImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_3BYTE_BGR); Graphics2D graphics = newImage.createGraphics(); graphics.drawRenderedImage(image, null); graphics.dispose(); // release all allocated memory for this image return newImage; } /** * Gets the byte array of an image * * @param image * The image to get byte data from * * @return Returns the byte array of the image supplied * */ private static byte[] getByteData(BufferedImage image) { WritableRaster raster = image.getRaster(); DataBufferByte buffer = (DataBufferByte) raster.getDataBuffer(); return buffer.getData(); } /** * Gernerates proper byte format of an integer * * @param i * The integer to convert * * @return Returns a byte[4] array converting the supplied integer into * bytes */ private static byte[] bitConversion(int i) { byte byte3 = (byte) ((i & 0xFF000000) >>> 24); // 0 byte byte2 = (byte) ((i & 0x00FF0000) >>> 16); // 0 byte byte1 = (byte) ((i & 0x0000FF00) >>> 8); // 0 byte byte0 = (byte) ((i & 0x000000FF)); // {0,0,0,byte0} is equivalent, since all shifts >=8 will be 0 return (new byte[] { byte3, byte2, byte1, byte0 }); } /** * Encode an array of bytes into another array of bytes at a supplied offset * * @param image * Array of data representing an image * * @param addition * Array of data to add to the supplied image data array * * @param offset * The offset into the image array to add the addition data * * @return data Array of merged image and addition data */ private static byte[] hide(byte[] image, byte[] addition, int offset) { // check that the data + offset will fit in the image if (addition.length + offset > image.length) { throw new IllegalArgumentException("File not long enough!"); } // loop through each addition byte for (int i = 0; i < addition.length; ++i) { // loop through the 8 bits of each byte int add = addition[i]; // ensure the new offset value carries on through both loops for (int bit = 7; bit >= 0; --bit, ++offset) { // assign an integer to b, shifted by bit spaces AND 1 // a single bit of the current byte int b = (add >>> bit) & 1; // assign the bit by taking: [(previous byte value) AND 0xfe] OR // bit to add // changes the last bit of the byte in the image to be the bit // of addition image[offset] = (byte) ((image[offset] & 0xFE) | b); } } return image; } /** * Retrieves hidden text from an image * * @param image * Array of data, representing an image * * @return Array of data which contains the hidden text */ private static byte[] reveal(byte[] image) { int length = 0; int offset = 32; // loop through 32 bytes of data to determine text length for (int i = 0; i < 32; ++i) { // i=24 will also work, as only the 4th // byte contains real data length = (length << 1) | (image[i] & 1); } byte[] result = new byte[length]; // loop through each byte of text for (int b = 0; b < result.length; ++b) { // loop through each bit within a byte of text for (int i = 0; i < 8; ++i, ++offset) { // assign bit: [(new byte value) << 1] OR [(text byte) AND 1] result[b] = (byte) ((result[b] << 1) | (image[offset] & 1)); } } return result; } }

If you have any questions about the changes, just ask.


RE: Steganography Using Java - Deque - 11-05-2013

Quote:I've taken help of a sample code written by @Deque to showcase simple implementation of Steganography in Java. All credits for this code goes to her.

This is not correct. The credits should go to the original author (I forgot his name). I just corrected his code, so it adheres the Java code conventions and I removed some unnecessary stuff. But it is still his code.
For a source sample that I wrote look here: http://www.hackcommunity.com/Thread-Release-Source-ImageSteg-hide-one-image-in-another


RE: Steganography Using Java - Aut•ono•mous - 11-06-2013

@The Arcanist I always am impressed by your posts. You go in-depth on what's being done instead of holding users' hands through everything. Great work. Smile