Steganography Hide and Display Data Inside Images
Programming Assignment
1 Background
In the exciting world of information security, there is a practice where someone is able to hide data insideof another file. In recent years, agents have used this practice to hide sensitive documents within the bits ofimages in a way that nobody can tell something else is in there. This practice, called steganography, is usedto hide data inside of other files in such a way that anyone looking at the file would never know somethingelse is hidden inside.
2 Description
In your third project, you will be writing a pair of steganography programs that will hide and extract datafrom images. There are two parts to this project:
First Part
In the first part, you will write a program (Stego) that will hide a binary file in a PGM
formatted image. PGM files are grayscale images that encode each pixel as an 8-bit value, ranging between0 (0x00) and 255 (0xFF). The value 0x00 represents a black pixel and 0xFF is a white pixel. The trick tohiding data inside of a picture is to change the least significant bit (LSB) of each pixel’s byte. For example,if the original pixel has the value of 0xAE (10101110), we can encode a one in the LSB to turn it into 0xAF(10101111). The difference in color and brightness between a pixel with the value 0xAE and 0xAF is so tinythat a human would not see any difference! Steganography is hiding data in a way that you can’t even tellanything is hidden in there.
In this first part of the project, you will be hiding a binary data file in the least significant bit of eachbyte of the PGM image. The binary file you are hiding is known as the payload, the original image is thecover, and the modified PGM image that contains your payload is called the stego image.
Second Part
In the second part of your project, you will write a program StegoExtract that will take
the stego image generated from the program you wrote for part one and extract the payload from it. Oncethe binary file is recovered, it will be identical to the original payload file.
Since we are working with binary data files for the payload, the type can be any file that will fit. As longas it is small enough, you can encode other images into this image, in addition to encoding text files.
2.1 Starting Files
For this project, we are providing five files that will be used as the starting point for this project:
http://cs.gmu.edu/~zduric/cs262/Homeworks/image.c
This provides all of the functions to read and write to PGM images.
http://cs.gmu.edu/~zduric/cs262/Homeworks/image.h
Header for image.c with all of the macros and prototypes you need.
http://cs.gmu.edu/~zduric/cs262/Homeworks/Stego.c // Starter File for Part One
http://cs.gmu.edu/~zduric/cs262/Homeworks/StegoExtract.c // Starter File for Part Two
http://cs.gmu.edu/~zduric/cs262/Homeworks/half.pgm
http://cs.gmu.edu/~zduric/cs262/Homeworks/cs262.pgm
image.cContains several functions for reading and writing PGM and PPM images and binary files. Severalmacros, and examples of their use, have also been provided in this file to simplify image byte manipulation.
GetGray(i)
This macro will get and return byte i from the source image as an unsigned char.
SetGray(i,g)
This macro will set byte i in the destination image with the unsigned char g.
GetByte(i)
This macro will get and return byte i from the input binary data file.
SetByte(i,val)
This macro will set byte i in the output binary file to unsigned char val.
Look at the definition of the macros provided in image.h to see how they affect the files in Stego.c
Stego.cStarting point for Part One of your project. This will be the main source file for your program toembed a binary data file within a PGM image. Comments have been added to direct your solution.
2.2 Stego.c Design
There are three parts to embedding data within the cover image.
1. You will use the first 32-bytes of the cover image to embed the 32-bits of b.size (the size field for thebinary data file (payload)). You have access to this in Stego.c.
2. The second 32-bytes of the cover image will be used to embed the 8 digits of your G# in numericalform (using 4-bits per digit).
3. After this, you will embed each byte of your data into the LSBs of the cover image. This means youwill be using 8 bytes of the image to hold each 1 byte of your information. This will use 8 * b.sizebytes in total to embed the data (b.data)
The skeleton program (Stego.c) can be used to understand the operation of the macros and functions.
You will be adding the main code for you solution to the following areas:
// embed four s i z e bytes f o r the Buffer ’ s s i z e f i e l d
<- Write your code here to embed the b . s i z e f i e l d
. . .
// embed the eight d i g i t s of your G# using 4 b i t s per d i g i t
<- Write your code here to embed your G# with the given format
. . .
// here you embed information into the image one byte at the time
// note that you should change only the l e a s t s i g n i f i c a n t b i t s of the image
<- Write your code here to use your g e t l s b s or s e t l s b s functions to perform the ops .
2.3 StegoExtract.c Design
For your extraction in Part Two, you will create a new source file, StegoExtract.c, that will use thesame general techniques as Stego.c, but will undo the operations you performed in Part One. Basically,this will extract the LSBs from the first 32-bytes to form a 32-bit integer (b.size). You will then need tomalloc memory for this many bytes and assign that pointer to b.data. This will hold your extracted payload.
You will then extract the LSBs from the next 32-bytes to extract your G-Number. After this, you willextract the LSBs each subsequent 8-bytes to form each 1-byte of your data, b.data. At the end, you willneed to make a call to WriteBinaryFile(argv[2],b); to output b as the extracted payload data to youruser input output filename.
Unless you get really ambitious and want to play with color images (PPM format images), you only needto use the macros: GetGray and SetByte.
2.4 Compiling
To compile the programs, you will use a Makefile. The compiling process will create static objects that youcan compile into each of your two programs. The following command references will help you build yourmakefile.
Compile the image code into an object (image.o) This will be used by Parts One and Two
gcc -c image.c
Compile your Part One file into an object (Stego.o)
gcc -c Stego.c
Compile your Part Two file into an object (Extract.o)
gcc -c Extract.c
Compile and Link to Create your Stego Executable
gcc -o StegoStego.oimage.o
Compile and Link to Create your StegoExtract Executable
gcc -o StegoExtractStegoExtract.oimage.o
3 Example Execution
In this sample, we are going to embed payload.dat into half.pgm to output half_stego.pgm.
./ Stego half .pgm half stego .pgmimportant .txt
. . . Reading input f i l e ’ half .pgm’
. . . Reading binary f i l e ’ important . txt ’
. . . Writing f i l e ’ half stego .pgm’
In this sample, we are going to extract important.txt’s data from half_stego.pgm.
./ StegoExtract half stego .pgmrecovered .txt
. . . Reading input f i l e ’ half stego .pgm’
. . . Writing binary f i l e ’ recovered . txt ’
4 Internals
You should create two functions to perform the setting and extracting of bits from the LSBs of your coverimage: setlsbs and getlsbs
4.1 setlsbs
Your first function will have the following prototype:
void setlsbs ( unsigned char ∗p , unsigned char b0 );
This function will take an array of 8 unsigned chars called p and a single byte b0. The function will replacethe Least Significant Bits (LSBs) of each byte of p by the bits of b0.
In other words, if the binary representation of b0 is:
b7 b6 b5 b4 b3 b2 b1 b0
You should replace the LSB of p[0] by bit b0, and the LSB of p[1] by bit b1, and so forth. Note that thismapping is in reverse. The first element of p will get its LSB replaced by bit b0 of the byte b0.
4.2 getlsbs
Your second function will have the following prototype:
unsigned char getlsbs ( unsigned char ∗p );
This function will take an array of 8 unsigned chars called p and return a single byte b0. The function willget the Least Significant Bits (LSBs) of each byte of p.
In other words, your function should combine the LSB bi of p[i] to return a byte with this representation:
b7 b6 b5 b4 b3 b2 b1 b0
4.3 Testing
Before using this in your Stego.c and StegoExtract.c programs, you should test these two functions first.
Come up with a series of tests where you are able to use setlsbs to embed an unsigned char (byte) intoan array of 8 randomly generated unsigned chars, then use getlsbs to extract an unsigned char from thatsame array.
Check the values of the array after setlsbs to ensure the LSB of each value has been changed with theproper bit and compare the initial byte with the output of getlsbs to ensure you got the same data back.
For example, if b contained the value 0x42 and p contained the values:
[0x20, 0x42, 0x31, 0x04, 0x02, 0x75, 0x9C, and 0xF0], then you would expect the following:
• Before setlsbs(p, b0); // p contains [0x20, 0x42, 0x31, 0x04, 0x02, 0x75, 0x9C, and 0xF0]
• After setlsbs(p, b0); // p contains [0x20, 0x43, 0x30, 0x05, 0x02, 0x75, 0x9C, and 0xF0]
• unsigned char check = getlsbs(p); // check should be equal to 0x42
Macros for Testing You can use the following macros to print out the bits for testing. (Call PRINTBIN(x);)
#d e f i n e BYTETOBINARYPATTERN “%d%d%d%d%d%d%d%d”
#d e f i n e BYTETOBINARY( byte ) n
( byte & 0x80 ? 1 : 0) , n
( byte & 0x40 ? 1 : 0) , n
( byte & 0x20 ? 1 : 0) , n
( byte & 0x10 ? 1 : 0) , n
( byte & 0x08 ? 1 : 0) , n
( byte & 0x04 ? 1 : 0) , n
( byte & 0x02 ? 1 : 0) , n
( byte & 0x01 ? 1 : 0)
#d e f i n e PRINTBIN( x ) p r i n t f (BYTETOBINARYPATTERN, BYTETOBINARY( x ) ) ;
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*
* StegoExtract.c: A program for manipulating images *
*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
#include
#include “image.h”
int main(intargc, char *argv[])
{
int i, j, k, cover_bits, bits;
struct Buffer b = {NULL, 0, 0};
struct Image img = {0, NULL, NULL, NULL, NULL, 0, 0};
byte b0;
if (argc != 3)
{
printf(“\n%s \n”, argv[0]);
exit(1);
}
ReadImage(argv[1],&img); // read image file into the image buffer img
// the image is an array of unsigned chars (bytes) of NofR rows
// NofC columns, it should be accessed using provided macros
// hidden information
// first four bytes is the size of the hidden file
// next 4 bytes is the G number (4 bits per digit)
if (!GetColor)
cover_bits = img.NofC*img.NofR;
else
cover_bits = 3*img.NofC*img.NofR;
b.size = 0;
// extract four size bytes for the Buffer’s size field
// Set this to b.size
b.data = malloc(b.size); // Allocates room for the output data file
// extract the eight digits of your G# using 4 bits per digit
for (i=0; i
{
// here you extract information from the image one byte at the time
// note that you should extract only the least significant bits of the image
}
WriteBinaryFile(argv[2],b); // output payload file
}
Solution
Stego.c
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*
* Stego.c: A program for manipulating images *
*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
#include
#include “image.h”
#define G (0x00632B4D) /* 00994377 in decimal */
voidsetlsbs(unsigned char *p, unsigned char b0);
int main(intargc, char *argv[])
{
int i, j, k, cover_bits, bits;
struct Buffer b = {NULL, 0, 0};
struct Image img = {0, NULL, NULL, NULL, NULL, 0, 0};
byte b0;
if (argc != 4)
{
printf(“\n%s \n”, argv[0]);
exit(1);
}
ReadImage(argv[1],&img); // read image file into the image buffer img
// the image is an array of unsigned chars (bytes) of NofR rows
// NofC columns, it should be accessed using provided macros
ReadBinaryFile(argv[3],&b); // Read binary data
// hidden information
// first four bytes is the size of the hidden file
// next 4 bytes is the G number (4 bits per digit)
if (!GetColor)
cover_bits = img.NofC*img.NofR;
else
cover_bits = 3*img.NofC*img.NofR;
bits = (8 + b.size)*8;
if (bits >cover_bits)
{
printf(“Cover file is not large enough %d (bits) > %d (cover_bits)\n”,bits,cover_bits);
exit(1);
}
// embed four size bytes for the Buffer’s size field
//
//
for (j = 0; j < 4; j++)
{
SetGray(j, (b.size>> j*8) & 0xFF);
}
// embed the eight digits of your G# using 4 bits per digit
//
//
//
for (j = 0; j < 4; j++)
SetGray(4 + j, (G >> j*8) & 0xFF);
for (i=0; i
{
// here you embed information into the image one byte at the time
// note that you should change only the least significant bits of the image
b0 = GetByte( i );
setlsbs(&GetGray( 8*(1 + i) ), b0);
}
WriteImage(argv[2],img); // output stego file (cover_file + file_to_hide)
}
voidsetlsbs(unsigned char *p, unsigned char b0)
{
unsigned char mask[] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};
unsigned char i;
for (i = 0; i < 8; i++)
if (!!(p[i] & 0x01) != !!(b0 & mask[i]))
p[i] ^= 1u;
}
StegoExtract.c
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*
* StegoExtract.c: A program for manipulating images *
*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
#include
#include “image.h”
unsigned char getlsbs(unsigned char *p);
int main(intargc, char *argv[])
{
int i, j, k, cover_bits, bits;
struct Buffer b = {NULL, 0, 0};
struct Image img = {0, NULL, NULL, NULL, NULL, 0, 0};
byte b0;
if (argc != 3)
{
printf(“\n%s \n”, argv[0]);
exit(1);
}
ReadImage(argv[1],&img); // read image file into the image buffer img
// the image is an array of unsigned chars (bytes) of NofR rows
// NofC columns, it should be accessed using provided macros
// hidden information
// first four bytes is the size of the hidden file
// next 4 bytes is the G number (4 bits per digit)
if (!GetColor)
cover_bits = img.NofC*img.NofR;
else
cover_bits = 3*img.NofC*img.NofR;
b.size = 0;
// extract four size bytes for the Buffer’s size field
// Set this to b.size
for (j = 0; j < 4; j++)
{
b.size |= GetGray(j) << j*8;
}
b.data = malloc(b.size); // Allocates room for the output data file
// extract the eight digits of your G# using 4 bits per digit
for (i=0; i
{
// here you extract information from the image one byte at the time
// note that you should extract only the least significant bits of the image
b0 = getlsbs(&GetGray( 8*(1 + i) ) );
SetByte(i, b0);
}
WriteBinaryFile(argv[2],b); // output payload file
}
unsigned char getlsbs(unsigned char *p)
{
unsigned char b0 = 0;
unsigned char i;
for (i = 0; i < 8; i++)
b0 |= !!(p[i] & 0x01) <
return b0;
}
image.c
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*
* image.c: A program for manipulating images *
* Author: ZoranDuric, 10-7-2010. *
*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
#include
#include
#include
#include “image.h”
voidPGMtoPPM(struct Image *Img)
{
int i;
if (Img->iscolor) return;
Img->iscolor = 1;
if (Img->red == NULL) Img->red = (byte *)malloc(sizeof(byte)*Img->NofC*Img->NofR);
if (Img->green == NULL) Img->green = (byte *)malloc(sizeof(byte)*Img->NofC*Img->NofR);
if (Img->blue == NULL) Img->blue = (byte *)malloc(sizeof(byte)*Img->NofC*Img->NofR);
for (i=0; iNofR*Img->NofC; i++)
{
Img->red[i] = Img->gray[i];
Img->green[i] = Img->gray[i];
Img->blue[i] = Img->gray[i];
}
free(Img->gray);
}
// Read PPM & PGM files (P2, P3, P5, P6)
voidReadImage(char *name, struct Image *Img)
{
inti,j,pixel,k;
FILE *fp;
char s[256];
intfiletype, nlevels;
byte *in;
printf(“\nReading input file ‘%s’\n”,name);
if ((fp = fopen(name, “rb”)) == NULL) {
fprintf(stderr,”Can’t open input file %s\n”,name);
exit(1);
}
label1 :
if (fgets(s, 80, fp)==NULL)
{
fprintf(stderr,” character found in input file %s\n”, name);
exit(1);
}
if (strcmp(“P2\n”,s)==0) filetype = 2;
else if (strcmp(“P3\n”,s)==0) filetype = 3;
else if (strcmp(“P5\n”,s)==0) filetype = 5;
else if (strcmp(“P6\n”,s)==0) filetype = 6;
else {
if (s[0] == ‘#’) goto label1;
fprintf(stderr,”Input file %s is of wrong type %s\n”,name,s);
exit(1);
}
label2:
if (fgets(s, 80, fp)==NULL) {
fprintf(stderr,”Size line does not exist in input file %s\n”, name);
exit(1);
}
if (s[0] == ‘#’) goto label2;
sscanf(s,”%d %d”,&(Img->NofC),&(Img->NofR));
label3:
if (fgets(s, 80, fp)==NULL) {
fprintf(stderr,” character found in input file %s\n”, name);
exit(1);
}
if (s[0] == ‘#’) goto label3;
sscanf(s,”%d”,&nlevels);
switch (filetype) { // read pixels
case 2: // P2 – ascii file, gray level image
Img->iscolor = 0;
if (Img->gray == NULL) Img->gray = (byte *)malloc(sizeof(byte)*Img->NofC*Img->NofR);
k = 0;
for (i=0; iNofR; i++) for (j=0; jNofC; j++) {
fscanf(fp,”%d”,&pixel);
Img->gray[k++] = pixel;
}
break;
case 3: // P3 – ascii file, color image
Img->iscolor = 1;
if (Img->red == NULL) Img->red = (byte *)malloc(sizeof(byte)*Img->NofC*Img->NofR);
if (Img->green == NULL) Img->green = (byte *)malloc(sizeof(byte)*Img->NofC*Img->NofR);
if (Img->blue == NULL) Img->blue = (byte *)malloc(sizeof(byte)*Img->NofC*Img->NofR);
k = 0;
for (i=0; iNofR; i++) for (j=0; jNofC; j++) {
fscanf(fp,”%d”,&pixel); Img->red[k] = pixel;
fscanf(fp,”%d”,&pixel); Img->green[k] = pixel;
fscanf(fp,”%d”,&pixel); Img->blue[k++] = pixel;
}
break;
case 5: // P5 – raw file, gray level image
Img->iscolor = 0;
if (Img->gray == NULL) Img->gray = (byte *)malloc(sizeof(byte)*Img->NofC*Img->NofR);
k = 0;
in = (byte *)malloc(sizeof(byte)*Img->NofC);
for (i=0; iNofR; i++) {
j = fread((void *)in,sizeof(byte),Img->NofC,fp);
if (j NofC)
{ fprintf(stderr,
“Error: input file %s contains only %1d objects (< %1d)\n”,
name,i*Img->NofC+j,Img->NofC*Img->NofR);
exit(1);
}
for (j=0; jNofC; j++) Img->gray[k++] = in[j];
}
free(in);
break;
case 6: // P6 – raw file, color image
Img->iscolor = 1;
if (Img->red == NULL) Img->red = (byte *)malloc(sizeof(byte)*Img->NofC*Img->NofR);
if (Img->green == NULL) Img->green = (byte *)malloc(sizeof(byte)*Img->NofC*Img->NofR);
if (Img->blue == NULL) Img->blue = (byte *)malloc(sizeof(byte)*Img->NofC*Img->NofR);
k = 0;
in = (byte *)malloc(sizeof(byte)*Img->NofC*3);;
for (i=0; iNofR; i++) {
j = fread((void *)in,sizeof(byte),Img->NofC*3,fp);
if (j < (Img->NofC*3))
{ fprintf(stderr,
“Error: input file %s contains only %1d objects (< %1d)\n”,
name,i*Img->NofC+(j+1)/3,Img->NofC*Img->NofR);
exit(1);
}
for (j=0; jNofC; j++) {
Img->red[k] = in[3*j];
Img->green[k] = in[3*j+1];
Img->blue[k++] = in[3*j+2];
}
}
free(in);
break;
otherwise: break;
}
fclose(fp);
}
// Read binary file
voidReadBinaryFile(char *name, struct Buffer *b)
{
inti,j,k;
FILE *fp;
byte *in, *aux;
printf(“\nReading binary file ‘%s’\n”,name);
if ((fp = fopen(name, “rb”)) == NULL) {
fprintf(stderr,”Can’t open input file %s\n”,name);
exit(1);
}
// make space for data
b->data = (byte *)malloc(sizeof(byte)*MIN_BUFFER_SIZE);
b->size = 0;
b->max_size = MIN_BUFFER_SIZE;
in = (byte *)malloc(sizeof(byte)*MIN_BUFFER_SIZE);
while ((j = fread((void *)in,sizeof(byte),MIN_BUFFER_SIZE,fp)) > 0)
{
if ((j + b->size) > b->max_size)
{
aux = (byte *)malloc(sizeof(byte)*b->max_size*2);
b->max_size *=2;
for (i=0; isize; i++) aux[i] = b->data[i];
free(b->data);
b->data = aux;
}
for (i=0; idata[b->size+i] = in[i];
b->size += j;
if (j < MIN_BUFFER_SIZE) break;
}
free(in);
fclose(fp);
}
// Write gray or color images
voidWriteImage(char *file, struct Image Img)
{
int i, j, k;
FILE *fo;
byte *out;
printf(“\nWriting file ‘%s’\n”,file);
if ((fo = fopen(file, “wb”)) == NULL) {
fprintf(stderr,”Can’t create output file \’%s\’\n”,file);
exit(1);
}
if (!Img.iscolor) { // write P5 – raw, gray image
out = (byte *)malloc(sizeof(byte)*Img.NofC);;
fprintf(fo,”P5\n %3d %3d\n 255\n”,Img.NofC,Img.NofR);
for (i=0; i
k = Img.NofC*i;
for (j=0; j
fwrite(out, sizeof(byte), Img.NofC, fo);
}
free(out);
} // !iscolor
else { // P6 – raw, color image
out = (byte *)malloc(sizeof(byte)*Img.NofC*3);;
fprintf(fo,”P6\n %3d %3d\n 255\n”,Img.NofC,Img.NofR);
for (i=0; i
k = Img.NofC*i;
for (j=0; j
out[3*j] = Img.red[k];
out[3*j+1] = Img.green[k];
out[3*j+2] = Img.blue[k++];
}
fwrite(out, sizeof(byte), Img.NofC*3, fo);
}
free(out);
} // iscolor
fclose(fo);
}
// Write gray or color images
voidWriteBinaryFile(char *name, struct Buffer b)
{
FILE *fo;
printf(“\nWriting file ‘%s’\n”,name);
if ((fo = fopen(name, “wb”)) == NULL) {
fprintf(stderr,”Can’t create output file \’%s\’\n”,name);
exit(1);
}
fwrite(b.data, sizeof(byte), b.size, fo);
fclose(fo);
}
image.h
// image.h, Author: ZoranDuric, 10-7-2010
#ifndef COLORIMAGE_H
#define COLORIMAGE_H
#include
#include
#define byte unsigned char
#define MIN_BUFFER_SIZE 4096
#defineGetColor (img.iscolor)
#define GetGray(i) (img.gray[i])
#define GetRed(i) (img.red[i])
#define GetGreen(i) (img.green[i])
#define GetBlue(i) (img.blue[i])
#define SetGray(i,g) (img.gray[i] = g)
#define SetRed(i,r) (img.red[i] = r)
#define SetGreen(i,g) (img.green[i] = g)
#define SetBlue(i,b) (img.blue[i] = b)
#define GetByte(i) (b.data[i])
#define SetByte(i,val) (b.data[i] = val)
struct Image
{
byteiscolor; // color = 1 for a color image
byte *gray, *red, *green, *blue;
intNofR, NofC;
};
struct Buffer
{
byte *data; // data pointer
int size, max_size; // true and available size
};
voidReadImage(char *name, struct Image *Img);
voidWriteImage(char *name, struct Image Img);
voidPGMtoPPM(struct Image *Img);
voidReadBinaryFile(char *name, struct Buffer *b);
voidWriteBinaryFile(char *name, struct Buffer b);
#endif