/* * Original author: William_Wilson * Original authorship date: May 12, 2007 * * Derivation: Matthew McCullough * Derivation date: 2010-05-10 */ import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.awt.image.DataBufferByte; import java.awt.image.WritableRaster; import java.io.File; import javax.imageio.ImageIO; import javax.swing.JOptionPane; public class Steganography { public Steganography() { } /* *Encrypt an image with text, the output file will be of type .png *@param path The path (folder) containing the image to modify *@param original The name of the image to modify *@param ext1 The extension type of the image to modify (jpg, png) *@param stegan The output name of the file *@param message The text to hide in the image *@param type integer representing either basic or advanced encoding */ public boolean encode(String path, String original, String ext1, String stegan, String message) { String file_name = image_path(path,original,ext1); BufferedImage image_orig = getImage(file_name); //user space is not necessary for Encrypting BufferedImage image = user_space(image_orig); image = add_text(image,message); return(setImage(image,new File(image_path(path,stegan,"png")),"png")); } /* *Decrypt assumes the image being used is of type .png, extracts the hidden text from an image *@param path The path (folder) containing the image to extract the message from *@param name The name of the image to extract the message from *@param type integer representing either basic or advanced encoding */ public String decode(String path, String name) { byte[] decode; try { //user space is necessary for decrypting BufferedImage image = user_space(getImage(image_path(path,name,"png"))); decode = decode_text(get_byte_data(image)); return(new String(decode)); } catch(Exception e) { JOptionPane.showMessageDialog(null, "There is no hidden message in this image!","Error", JOptionPane.ERROR_MESSAGE); return ""; } } /* *Returns the complete path of a file, in the form: path\name.ext *@param path The path (folder) of the file *@param name The name of the file *@param ext The extension of the file *@return A String representing the complete path of a file */ private String image_path(String path, String name, String ext) { return path + "/" + name + "." + ext; } /* *Get method to return an image file *@param f The complete path name of the image. *@return A BufferedImage of the supplied file path *@see Steganography.image_path */ private BufferedImage getImage(String f) { BufferedImage image = null; File file = new File(f); try { image = ImageIO.read(file); } catch(Exception ex) { JOptionPane.showMessageDialog(null, "Image could not be read!","Error",JOptionPane.ERROR_MESSAGE); } return image; } /* *Set method to save an image file *@param image The image file to save *@param file File to save the image to *@param ext The extension and thus format of the file to be saved *@return Returns true if the save is succesful */ private boolean setImage(BufferedImage image, File file, String ext) { try { file.delete(); //delete resources used by the File ImageIO.write(image,ext,file); return true; } catch(Exception e) { JOptionPane.showMessageDialog(null, "File could not be saved!","Error",JOptionPane.ERROR_MESSAGE); return false; } } /* *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 BufferedImage add_text(BufferedImage image, String text) { //convert all items to byte arrays: image, message, message length byte img[] = get_byte_data(image); byte msg[] = text.getBytes(); byte len[] = bit_conversion(msg.length); try { encode_text(img, len, 0); //0 first positiong encode_text(img, msg, 32); //4 bytes of space for length: 4bytes*8bit = 32 bits } catch(Exception e) { JOptionPane.showMessageDialog(null, "Target File cannot hold message!", "Error",JOptionPane.ERROR_MESSAGE); } 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 BufferedImage user_space(BufferedImage image) { //create new_img with the attributes of image BufferedImage new_img = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_3BYTE_BGR); Graphics2D graphics = new_img.createGraphics(); graphics.drawRenderedImage(image, null); graphics.dispose(); //release all allocated memory for this image return new_img; } /* *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 *@see Raster *@see WritableRaster *@see DataBufferByte */ private byte[] get_byte_data(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 byte[] bit_conversion(int i) { //originally integers (ints) cast into bytes //byte byte7 = (byte)((i & 0xFF00000000000000L) >>> 56); //byte byte6 = (byte)((i & 0x00FF000000000000L) >>> 48); //byte byte5 = (byte)((i & 0x0000FF0000000000L) >>> 40); //byte byte4 = (byte)((i & 0x000000FF00000000L) >>> 32); //only using 4 bytes 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 Returns data Array of merged image and addition data */ private byte[] encode_text(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]; for(int bit=7; bit>=0; --bit, ++offset) //ensure the new offset value carries on through both loops { //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 byte[] decode_text(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; } }