2.12.15

Fun With Steganography

I first learned about steganography a long time ago but I never actually played with it in any way until a few days ago when I realized how nice it would be if I could watermark my photographs.

Steganography is defined as "the practice of concealing messages or information within other nonsecret text or data" and in this blog post I intend to show how to hide data in an image at the pixel level, and retrieve it afterwards.

If I want to hide a piece of text in an image I can convert each character of the text into an integer from 0 to however many characters there are in the chosen alphabet, as long as the alphabet consists of less than 255 characters.


For example, the English alphabet has 26 characters and if each character is represented by a digit from 0 to 25 where a is represented by 0, b is represented by 1 and so on, and space is represented by 26, then the phrase "hello world" becomes the array of integers 7, 4, 11, 11, 14, 26, 22, 14, 17, 11, 3


Then I can replace just one channel of particular pixels chosen through a secret key with each integer in the array thus concealing a retrievable text message in a digital format within plain sight!

The pseudo code for the en-steganograph and de-steganograph or ensteg and desteg are as follows:



Ensteg Function
  1. Grab image data
  2. Loop through each pixel until a certain pixel at a certain location is reached according to a given key that specifies the location in a consecutive manner (key must expand and contract and must be smaller than image height/2 and image width/2)
  3. Replace the r channel value in this designated location with the first integer of the message
  4. Point to next integer of the message array
  5. Add a certain specified amount to key 
  6. Repeat 3-5 until the rest of the image
Desteg Function
  1. Loop through each pixel until a certain location is reached according to a decoding key
  2. Grab r value of that pixel and add it to an array
  3. Add a certain specified amount to key
  4. Repeat 1 through 3 until the end of the image
  5. Return array

Hiding and retrieving hidden data from an image at the pixel level is not as easy as it may sound because many image formats use lossy compression systems, which means that the values of the pixel's channels do not remain the same.


For example, the most widely used compression system for JPEG is lossy, and it was "designed to give results acceptably accurate to the human eye whilst providing many-to-one compression"[p.626, S. G. Hoggard, Mathematics of Digital Images]


PHP's GD function imagecreate() uses many-to-one compression that significantly alters the RGB values of each pixel even if the quality option is set to 100. According to S.G. Hoggard, JPEG does include "a system of lossless compression". However, a quick search through stackoverflow suggests that none of the other PHP image libraries include jpeg creation function with lossless compression.


Therefore, jpegs are not the best choice for what I'd like to do. I could still accept any image format and grab the image data from it but upon piecing it all together I must use a format that preserves pixel values. I choose png


Below are the ensteg/desteg functions in PHP.



/* -------------------------------------------------  
 This content is released under the GNU License  
 http://www.gnu.org/copyleft/gpl.html 
 Author: Marina Ibrishimova 
 Version: 1.0
 Purpose: Hide a secret message in an image  
 ---------------------------------------------------- */ 

function ensteg($path, $message, $key, $amount) 
 { 
  if(file_exists($path_to)_img))
  { 
   $im = $path_to_img;
  }
  else{
   return "Image does not exist at specified location";
  }
  $type = strtolower(substr(strrchr($path_to_img,"."),1));
    if($type == 'jpeg') $type = 'jpg';
    switch($type){
     
       case 'gif': $im = imagecreatefromgif($path_to_img); break;
       case 'jpg': $im = imagecreatefromjpeg($path_to_img); break;
       case 'png': $im = imagecreatefrompng($path_to_img); break;
       default : return "Unsupported imagetype!";
    }
            $width = imagesx($im);  
            $height = imagesy($im);
            $i = 0;
            $counter = count($message);
            //loop through each pixel
            for($x = 0; $x < $width; $x++)   
            {  
                 for($y = 0; $y < $height; $y++)   
                 {  
                       $rgb = imagecolorat($im, $x, $y);  
                       $r = ($rgb >> 16) & 0xFF;  
                       $g = ($rgb >> 8) & 0xFF;  
                       $b = $rgb & 0xFF;  
                       $alpha = ($rgb & 0xFF000000) >> 24;  
                        //pixel is of certain height and width according to secret key
                       if($x === ($width - $key) && $y === ($height - $key))
                       {
                        //reset and start repeating msg
                        if($i == $counter){$i = 0;}
                        //add portion of message to r value and move on
                        $r = $message[$i];
                        $i = $i + 1;
                        $key = $key - $amount; 
                        imagesetpixel($im, $x, $y, imagecolorallocate($im, $r, $g, $b));
                       }    
                 }  
             } 
   //lossless even though compression is set to 9
   imagepng($im, 'new_path_to_new_file.png', 9); 
   imagedestroy($im);
   
   $img_url = "new_path_to_new_file.png";    
  
   return $img_url;
   
 }




Original image sans secret message


And the image below has a hidden, retrievable text message in a digital format repeated throughout the image but this is not obvious to the naked human eye.



Image with secret message


To retrieve the hidden data from the message, the following de-steganograph function is required, along with key = 127 and amount = 3


/* -------------------------------------------------  
 This content is released under the GNU License  
 http://www.gnu.org/copyleft/gpl.html 
 Author: Marina Ibrishimova 
 Version: 1.0
 Purpose: Retrieve a secret message hidden with ensteg() 
 ---------------------------------------------------- */ 
function desteg($path, $key, $amount) //bright and sunny
 {
  //path to image generated with ensteg
  if(file_exists($path))
  { 
   $im = "new_path_to_new_file.png";

  }
  else{
   return "Image does not exist at specified location";
  }
  $im = imagecreatefrompng($im);
  $thelong = array();
  
  $width = imagesx($im);  
            $height = imagesy($im);  
        
            //loop through each pixel  
            for($x = 0; $x < $width; $x++)   
            {  
                 for($y = 0; $y < $height; $y++)   
                 {  
                       $rgb = imagecolorat($im, $x, $y);  
                       $r = ($rgb >> 16) & 0xFF;  
                       $g = ($rgb >> 8) & 0xFF;  
                       $b = $rgb & 0xFF;  
                       $alpha = ($rgb & 0xFF000000) >> 24;  
                        //pixel is of certain height and width according to secret key
                       if($x === ($width - $key) && $y === ($height - $key))
                       {
                        $thelong[] = $r;
                        
                        $key = $key - $amount;
                       } 
                 }  
               }
   imagedestroy($im); 
  //returns an array with the message repeated within specified location
  return $thelong; 
 }