Click here to Skip to main content
15,884,099 members
Articles / Programming Languages / C#
Article

Steganography III - Change only one Bit per Pixel

Rate me:
Please Sign up or sign in to vote.
4.76/5 (21 votes)
3 Apr 2004CPOL1 min read 209.5K   5.8K   64   52
An article about hiding each byte of a message bit-by-bit in eight pixels.

Sample Image - steganodotnet3.png

Introduction

The application described in the last article Steganography II always added visible noise to the carrier bitmaps. This article is about splitting the bytes and hiding each bit in a different pixel. The hidden message is really invisible, because only the lowest bit of one color component is changed.

Background

Before reading this, you should have read at least part one, Steganography - Hiding messages in the Noise of a Picture. This article uses the application described in part one and two, but you don't need the features added in part two to understand this one.

End of the rainbow

Replacing a whole byte of a RGB color produces a rainbow pattern. This is how a white image (all pixels just white) looks like after hiding a text message:

typical colour noise

On a colorful photo, these rainbow pixels may be alright. In grayscale mode, the same message in the same picture looks like this:

typical grayscale noise

As you can see, too many bits of the pixels have been changed. Now, we’re going to spread each byte over eight pixels and make the rainbow disappear.

Get and set bits

spreading a byte over eight pixels

For each byte of the message, we have to:

  1. Grab a pixel.
  2. Get the first bit of the message byte.
  3. Get one color component of the pixel.
  4. Get the first bit from the color component.
  5. If the color-bit is different from the message-bit, set/reset it.
  6. Do the same for the other seven bits.

The C#-functions for getting and setting single bits are simple:

C#
private static bool GetBit(byte b, byte position){
	return ((b & (byte)(1 << position)) != 0);
}

private static byte SetBit(byte b, byte position, bool newBitValue){
	byte mask = (byte)(1 << position);
	if(newBitValue){
		return (byte)(b | mask);
	}else{
		return (byte)(b & ~mask);
	}
}

The rest of the code has not changed much. Hiding works like that now...

hide a message

...and extraction works like that:

extract a message

That's enough text for today. If you want to know the details, you should download the source.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Germany Germany
Corinna lives in Hanover/Germany and works as a C# developer.

Comments and Discussions

 
Questioni need to hide text inside image without key Pin
sonbolty1-Mar-12 1:10
sonbolty1-Mar-12 1:10 
AnswerRe: i need to hide text inside image without key Pin
Corinna John1-Mar-12 10:53
Corinna John1-Mar-12 10:53 
GeneralRe: i need to hide text inside image without key Pin
sonbolty12-Mar-12 1:49
sonbolty12-Mar-12 1:49 
GeneralPLEASE PLEASE : I AM BEGGINNER : CAN YOU HELP ME SOURCE CODE FOR stegonagraphy in picture and audio !! Pin
kutomba6-Dec-11 12:58
kutomba6-Dec-11 12:58 
Questioni need code of steganography urgently ..... Pin
himanshu199020-Nov-11 19:46
himanshu199020-Nov-11 19:46 
Questionthe length of the bitmap image Pin
174t26-Jun-11 6:23
174t26-Jun-11 6:23 
QuestionOut image Pin
174t26-Jun-11 2:11
174t26-Jun-11 2:11 
Generalproject Pin
Member 784332614-Apr-11 20:59
Member 784332614-Apr-11 20:59 
QuestionSteganography code in java Pin
Mukesh konde4-Mar-11 18:01
Mukesh konde4-Mar-11 18:01 
GeneralImage Steganography Pin
Anshuli23-Sep-10 5:21
Anshuli23-Sep-10 5:21 
Generalbadly need image steganography source code using java..... Pin
suresh54624-Jul-10 21:03
suresh54624-Jul-10 21:03 
Generalimage steganography - embed message in image using LSB Pin
star_light16-Jul-10 17:07
star_light16-Jul-10 17:07 
GeneralModule Pin
vinayanand527-Feb-10 22:19
vinayanand527-Feb-10 22:19 
GeneralSteganography Code for BMP Pin
pamdee8323-Jan-10 19:19
pamdee8323-Jan-10 19:19 
Generalcode required using java Pin
palaash16-Dec-09 4:38
palaash16-Dec-09 4:38 
Generalneed java source code for steganography Pin
prima iman21-May-09 20:22
prima iman21-May-09 20:22 
GeneralRe: need java source code for steganography Pin
chaitumad24-May-09 19:49
chaitumad24-May-09 19:49 
GeneralRe: need java source code for steganography Pin
speaker1331-May-09 6:53
speaker1331-May-09 6:53 
GeneralRe: need java source code for steganography Pin
cjeniffer10-Mar-12 21:22
cjeniffer10-Mar-12 21:22 
GeneralI Need Java code for stegnography.... Pin
D.Prasad23-Feb-09 18:57
D.Prasad23-Feb-09 18:57 
GeneralRe: I Need Java code for stegnography.... Pin
vijayalakshmi_bnmit21-Apr-09 2:36
vijayalakshmi_bnmit21-Apr-09 2:36 
GeneralRe: I Need Java code for stegnography.... Pin
chaitumad24-May-09 19:46
chaitumad24-May-09 19:46 
GeneralRe: I Need Java code for stegnography.... Pin
puvva26-Dec-10 18:17
puvva26-Dec-10 18:17 
GeneralRe: I Need Java code for stegnography.... Pin
ankit00245-Jan-10 18:56
ankit00245-Jan-10 18:56 
THis code will run using command line argument
// Steganography.java


/* A steganography class for hiding text inside a PNG image, and for
extracting the text later.

The class has two public static methods:

* boolean hide(String textFnm, String imFnm)
// the modified image is stored in <imFnm>Msg.png

* boolean reveal(String imFnm)
// the extracted message is stored in <imFnm>.txt


This class stores a stego message (stego for short), which has 2

fields:

<size of binary message>
<binary message>

The stego is spread out over the image's bytes by modifying each

byte's
right most bit (the least significant bit, LSB). So 1 byte of stego

data requires
the modification of 8 bytes of the image (i.e. 1 stego data bit is

stored in
1 image byte).

The stego is added once into the LSBs of the image's bytes at its

beginning.

More details on the stego's fields:

The message size is a Java integer (i.e. 4 bytes long),
which requires 4*8 bytes in the image.

Each byte of the message requires 8 bytes
of storage in the image.
*/


import java.io.*;
import java.awt.*;
import java.util.*;
import java.awt.image.*;
import javax.imageio.*;



public class Steganography
{
private static final int MAX_INT_LEN = 4;
private static final int DATA_SIZE = 8;
// number of image bytes required to store one stego byte



public static boolean hide(String textFnm, String imFnm)
/* hide message read from textFnm inside image read from imFnm;
the resulting image is stored in <inFnm>Msg.png */
{
// read in the message
String inputText = readTextFile(textFnm);
if ((inputText == null) || (inputText.length() == 0))
return false;

byte[] stego = buildStego(inputText);

// access the image's data as a byte array
BufferedImage im = loadImage(imFnm);
if (im == null)
return false;
byte imBytes[] = accessBytes(im);

if (!singleHide(imBytes, stego)) // im is modified with the stego
return false;

// store the modified image in <fnm>Msg.png
String fnm = getFileName(imFnm);
return writeImageToFile( fnm + "Msg.png", im);
} // end of hide()



private static String readTextFile(String fnm)
// read in fnm, returning it as a single string
{
BufferedReader br = null;
StringBuffer sb = new StringBuffer();

try {
br = new BufferedReader(new FileReader( new File(fnm) ));

String text = null;
while ((text = br.readLine()) != null)
sb.append(text + "\n");
}
catch (Exception e) {
System.out.println("Could not completely read " + fnm);
return null;
}
finally {
try {
if (br != null)
br.close();
}
catch (IOException e) {
System.out.println("Problem closing " + fnm);
return null;
}
}
System.out.println("Read in " + fnm);
return sb.toString();
} // end of readTextFile()



private static byte[] buildStego(String inputText)
/* Build a stego (a byte array), made up of 2 fields:
<size of binary message>
<binary message> */
{
// convert data to byte arrays
byte[] msgBytes = inputText.getBytes();
byte[] lenBs = intToBytes(msgBytes.length);

int totalLen = lenBs.length + msgBytes.length;
byte[] stego = new byte[totalLen]; // for holding the resulting

stego

// combine the 2 fields into one byte array
// public static void arraycopy(Object src, int srcPos, Object

dest, int destPos, int length);
System.arraycopy(lenBs, 0, stego, 0, lenBs.length); //

length of binary message
System.arraycopy(msgBytes, 0, stego, lenBs.length,

msgBytes.length); // binary message

// System.out.println("Num. pixels to store fragment " + i + ": " +

totalLen*DATA_SIZE);
return stego;
} // end of buildStego()



private static byte[] intToBytes(int i)
// split integer i into a MAX_INT_LEN-element byte array
{
// map the parts of the integer to a byte array
byte[] integerBs = new byte[MAX_INT_LEN];
integerBs[0] = (byte) ((i >>> 24) & 0xFF);
integerBs[1] = (byte) ((i >>> 16) & 0xFF);
integerBs[2] = (byte) ((i >>> 8) & 0xFF);
integerBs[3] = (byte) (i & 0xFF);

// for (int j=0; j < integerBs.length; j++)
// System.out.println(" integerBs[ " + j + "]: " + integerBs[j]);

return integerBs;
} // end of intToBytes()



private static BufferedImage loadImage(String imFnm)
// read the image from the imFnm file
{
BufferedImage im = null;
try {
im = ImageIO.read( new File(imFnm) );
System.out.println("Read " + imFnm);
}
catch (IOException e)
{ System.out.println("Could not read image from " + imFnm); }

return im;
} // end of loadImage()



private static byte[] accessBytes(BufferedImage image)
// access the data bytes in the image
{
WritableRaster raster = image.getRaster();
DataBufferByte buffer = (DataBufferByte) raster.getDataBuffer();
return buffer.getData();
} // end of accessBytes()



private static boolean singleHide(byte[] imBytes, byte[] stego)
// store stego in image bytes
{
int imLen = imBytes.length;
System.out.println("Byte length of image: " + imLen);

int totalLen = stego.length;
System.out.println("Total byte length of message: " + totalLen);

// check that the stego will fit into the image
// multiply stego length by number of image bytes required to store

one stego byte
if ((totalLen*DATA_SIZE) > imLen) {
System.out.println("Image not big enough for message");
return false;
}

hideStego(imBytes, stego, 0); // hide at start of image
return true;
} // end of singleHide()



private static void hideStego(byte[] imBytes, byte[] stego, int

offset)
// store stego in image starting at byte posn offset
{
for (int i = 0; i < stego.length; i++) { // loop through

stego
int byteVal = stego[i];
for(int j=7; j >= 0; j--) { // loop through the 8 bits of each

stego byte
int bitVal = (byteVal >>> j) & 1;

// change last bit of image byte to be the stego bit
imBytes[offset] = (byte)((imBytes[offset] & 0xFE) | bitVal);
offset++;
}
}
} // end of hideStego()




private static String getFileName(String fnm)
// extract the name from the filename without its suffix
{
int extPosn = fnm.lastIndexOf('.');
if (extPosn == -1) {
System.out.println("No extension found for " + fnm);
return fnm; // use the original file name
}

return fnm.substring(0, extPosn);
} // end of getFileName()




private static boolean writeImageToFile(String outFnm, BufferedImage

im)
// save the im image in a PNG file called outFnm
{
if (!canOverWrite(outFnm))
return false;

try {
ImageIO.write(im, "png", new File(outFnm));
System.out.println("Image written to PNG file: " + outFnm);
return true;
}
catch(IOException e)
{ System.out.println("Could not write image to " + outFnm);
return false;
}
} // end of writeImageToFile();



private static boolean canOverWrite(String fnm)
/* If fnm already exists, get a response from the
user about whether it should be overwritten or not. */
{
File f = new File(fnm);
if (!f.exists())
return true; // can overewrite since the file is new

// prompt the user about whether the file can be overwritten
Scanner in = new Scanner(System.in);
String response;
System.out.print("File " + fnm + " already exists. ");
while (true) {
System.out.print("Overwrite (y|n)? ");
response = in.nextLine().trim().toLowerCase();
if (response.startsWith("n")) // no
return false;
else if (response.startsWith("y")) // yes
return true;
}
} // end of canOverWrite()




// --------------------------- reveal a message

-----------------------------------


public static boolean reveal(String imFnm)
/* Retrieve the hidden message from imFnm from the beginning
of the image after first extractibg its length information.
The extracted message is stored in <imFnm>.txt
*/
{
// get the image's data as a byte array
BufferedImage im = loadImage(imFnm);
if (im == null)
return false;
byte[] imBytes = accessBytes(im);
System.out.println("Byte length of image: " + imBytes.length);

// get msg length at the start of the image
int msgLen = getMsgLength(imBytes, 0);
if (msgLen == -1)
return false;
System.out.println("Byte length of message: " + msgLen);

// get message located after the length info in the image
String msg = getMessage(imBytes, msgLen, MAX_INT_LEN*DATA_SIZE);


if (msg != null) {
String fnm = getFileName(imFnm);
return writeStringToFile(fnm + ".txt", msg); // save message in

a text file
}
else {
System.out.println("No message found");
return false;
}
} // end of reveal()



private static int getMsgLength(byte[] imBytes, int offset)
// retrieve binary message length from the image
{
byte[] lenBytes = extractHiddenBytes(imBytes, MAX_INT_LEN, offset);
// get the binary message length as a byte array
if (lenBytes == null)
return -1;

// for (int j=0; j < lenBytes.length; j++)
// System.out.println(" lenBytes[ " + j + "]: " + lenBytes[j]);

// convert the byte array into an integer
int msgLen = ((lenBytes[0] & 0xff) << 24) |
((lenBytes[1] & 0xff) << 16) |
((lenBytes[2] & 0xff) << 8) |
(lenBytes[3] & 0xff);
// System.out.println("Message length: " + msgLen);

if ((msgLen <= 0) || (msgLen > imBytes.length)) {
System.out.println("Incorrect message length");
return -1;
}
// else
// System.out.println("Revealed message length: " + msgLen);

return msgLen;
} // end of getMsgLength()



private static String getMessage(byte[] imBytes, int msgLen, int

offset)
/* Extract a binary message of size msgLen from the image, and
convert it to a string
*/
{
byte[] msgBytes = extractHiddenBytes(imBytes, msgLen, offset);
// the message is msgLen bytes long
if (msgBytes == null)
return null;

String msg = new String(msgBytes);

// check the message is all characters
if (isPrintable(msg)) {
// System.out.println("Found message: \"" + msg + "\"");
return msg;
}
else
return null;
} // end of getMessage()



private static byte[] extractHiddenBytes(byte[] imBytes, int size,

int offset)
// extract 'size' hidden data bytes, starting from 'offset' in the

image bytes
{
int finalPosn = offset + (size*DATA_SIZE);
if (finalPosn > imBytes.length) {
System.out.println("End of image reached");
return null;
}

byte[] hiddenBytes = new byte[size];

for (int j = 0; j < size; j++) { // loop through each hidden

byte
for (int i=0; i < DATA_SIZE; i++) { // make one hidden byte

from DATA_SIZE image bytes
hiddenBytes[j] = (byte) ((hiddenBytes[j] << 1) |

(imBytes[offset] & 1));
// shift existing 1 left; store right most

bit of image byte
offset++;
}
}
return hiddenBytes;
} // end of extractHiddenBytes()



private static boolean isPrintable(String str)
// is the string printable?
{
for (int i=0; i < str.length(); i++)
if (!isPrintable(str.charAt(i))) {
System.out.println("Unprintable character found");
return false;
}
return true;
} // end of isPrintable()



private static boolean isPrintable(int ch)
// is ch a 7-bit ASCII character that could (sensibly) be printed?
{
if (Character.isWhitespace(ch) && (ch < 127)) // whitespace, 7-bit
return true;
else if ((ch > 32) && (ch < 127))
return true;

return false;
} // end of isPrintable()



private static boolean writeStringToFile(String outFnm, String

msgStr)
// write the message string into the outFnm text file
{
if (!canOverWrite(outFnm))
return false;

try {
FileWriter out = new FileWriter( new File(outFnm) );
out.write(msgStr);
out.close();
System.out.println("Message written to " + outFnm);
return true;
}
catch(IOException e)
{ System.out.println("Could not write message to " + outFnm);
return false;
}
} // end of writeStringToFile()


//} // end of Steganography class

public static void main(String args[])
{
Steganography st= new Steganography();
st.hide(args[0],args[1]);
}
}
GeneralImage hiding project Pin
M.Arulraj10-Dec-08 0:04
M.Arulraj10-Dec-08 0:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.