Simple Steganography Using Processing


Lolcat that has a secret message hidden in it. In this case, it's Edgar Allan Poe's The Raven.

The lolcat image above has Edgar Allan Poe’s poem, The Raven, hidden inside the color values of the pixels. The rest of this post will describe how that was done.

##Prerequisite - Understanding How Images are Stored as Pixels

Writing image filters with students is an excellent way to discuss how images are stored in memory, and provide a nice example of using nested for loops and arrays. When I show my students basic image filtering in Processing, I loosely follow Daniel Shiffman’s examples from his Learning Processing book. The general order goes something like:

  • set the pixels of the entire canvas to random colors - Example 15-5
  • make a vertically striped image, by turning every even pixel white, and every odd pixel black - Example 15-6
  • demonstrate how to examine the pixel array of a specific image - Example 15-7
  • create a flashlight-like image filter (set pixels black if they are a certain distance away from the mouse location) - Example 15-9
  • create a threshold image filter (if the brightness of a pixel is sufficiently high, set it to white, else set it to black) - Example 15-10
  • students create their own filters, then show them to each other

##Algorithm Overview

Now that the students have a grasp of how image pixels are stored as an array, as well as how to access and change the RGB values of each pixel, you are all set for hiding messages! The basic algorithm I’m going to use is:

  • set all red component values of pixels to even numbers, by subtracting by 1 if necessary. This is simply removing the least significant bit from the red color component (you could, of course, do this with green and blue as well).
  • convert the message to be hidden into binary
  • loop through the pixels of the image, continuing until message bits have been applied, by:
    • leaving red component as is (if the bit is a 0)
    • adding 1 to the red component (if the bit is a 1)

Since we can convert any english character into an ASCII value, we will use up 8 bits per character when encoding. We can therefore hide one character in every 8 pixels of the image (if you were to include the green and blue color components as well, you could put 3 characters into every 8 pixels).

##Set All Red Components of Pixel Color Values to Even Numbers

Since each color component (RGB) is an integer from 0 to 255, a person will not notice the difference in the image if we change some of the pixels red component values by 1. Knowing this, we can set all of the red values to even numbers to establish a baseline to work from. This is as simple as checking if someNum % 2 == 0. If there is no remainder when dividing by two, it is already even, so we do not change anything. If there is a remainder, we subtract the red value by one.

PImage initalizedImage(PImage source) {
  //take in the source image and remove the least important bit from the red color component

  //return version of image where all red color components are even numbers (subtracted by 1 if req'd)

  
  PImage dest = createImage(source.width, source.height, RGB);
  source.loadPixels();
  dest.loadPixels();

  for (int i = 0; i < source.pixels.length; i++) {
    dest.pixels[i] = initializeLastBit(source.pixels[i]);
  }

  dest.updatePixels();
  return dest;
}

color initializeLastBit(color thisColor) {
  //reset last bit to 0 for the red color component


  float red = red(thisColor);
  float green = green(thisColor);
  float blue = blue(thisColor);

  if (red % 2 != 0) { //if it's odd, the last bit is on. Turn off by subtracting 1.

    red = red - 1;
  }

  color lastBitRemoved = color(red, green, blue);
  return lastBitRemoved;
}

##Converting Text to Binary

Before converting the secret message into binary, we should confirm that it will actually fit inside the image. Since each character of the message will require 8 pixels of storage, we need to confirm that (message.length() * 8) < (theImage.width * theImage.height). Assuming that this check is passed, Processing has a built-in binary function that gives us an easy way to convert any character into it’s binary representation. We can just loop through the secret message one character at a time, converting each of the characters into binary as we go. This will produce a large string filled with 1’s and 0’s, representing the secret message.

String encodeMessageToBinary(String secretMessage) {
  //convert message into it's binary representation

  
  String encodedMessage = "";

  for (int i=0; i<secretMessage.length(); i++) {
    char thisChar = secretMessage.charAt(i);
    encodedMessage = encodedMessage + binary(thisChar, 8);  //use 8 bits for every character (ASCII-like)

  }

  return encodedMessage;
}

##Applying the Message to the Image

To save the message (now stored as that string of 0’s and 1’s) into the image, all we need to do is loop through the string. Every time the current character is a 0, just leave the related image pixel as is (an even number). If the current character is an odd number, however, we should add one to the red value (to make the red component an odd number, representing a binary 1).

PImage attachMessageToImage(PImage source, String message) {
  //create a new image, and hide the message in the red color component values of the new image

  //assumes the source image has already been "cleaned up" (all red color component values set to even numbers)


  //start the destination image as an exact copy of the "cleaned up" source

  PImage dest = createImage(source.width, source.height, RGB);
  dest.copy(source, 0, 0, source.width, source.height, 0, 0, source.width, source.height);

  source.loadPixels();
  dest.loadPixels();

  //go through the message one character at a time (mapping this loop to a pixel loop), 

  //adding one to the current pixel's red component value if the binary value is a 1

  
  for (int i = 0; i < message.length(); i++ ) {
    if (message.charAt(i) == '1') {
      float redAmount = red(source.pixels[i]) + 1;
      float greenAmount = green(source.pixels[i]);
      float blueAmount = blue(source.pixels[i]);

      dest.pixels[i] = color(redAmount, greenAmount, blueAmount);
    }
  }

  dest.updatePixels();
  return dest;
}

##Decoding the Image Decoding is just the opposite of what we did above. Loop through the pixels in an image, checking each to see if it has an odd or even red component value. If it is even, save a 0 into the binary string representing the decoded message. If it is odd, save a 1 instead. Once the entire image has been converted into binary (you can just stop after encountering a bunch of 0s in a row), loop through the binary string, examining 8 binary values at a time and converting those 8 values back into an ASCII character. In Processing, this can be done by creating a string containing the 8 binary values, String currentChar = encodedMessage.substring(i, i+8);, then appending the ASCII value of that to the decoded message, decodedMessage = decodedMessage + char(unbinary(currentChar)); .

##Complete Program You can find the complete programs for both encoding and decoding on GitHub.