PHP Classes

File: Stegger.class.inc.php

Recommend this page to a friend!
  Classes of Warren Smith   Stegger   Stegger.class.inc.php   Download  
File: Stegger.class.inc.php
Role: Class source
Content type: text/plain
Description: The main class
Class: Stegger
Hide encrypted data in images using steganography
Author: By
Last change: Various bug fixes and performance enhancements
Date: 15 years ago
Size: 87,129 bytes
 

Contents

Class file image Download
<?php //+----------------------------------------------------------------------+ //| Stegger v0.6 | //+----------------------------------------------------------------------+ //| Copyright (c) 2006 Warren Smith ( smythinc 'at' gmail 'dot' com ) | //+----------------------------------------------------------------------+ //| This library is free software; you can redistribute it and/or modify | //| it under the terms of the GNU Lesser General Public License as | //| published by the Free Software Foundation; either version 2.1 of the | //| License, or (at your option) any later version. | //| | //| This library is distributed in the hope that it will be useful, but | //| WITHOUT ANY WARRANTY; without even the implied warranty of | //| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | //| Lesser General Public License for more details. | //| | //| You should have received a copy of the GNU Lesser General Public | //| License along with this library; if not, write to the Free Software | //| Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 | //| USA | //+----------------------------------------------------------------------+ //| Simple is good. | //+----------------------------------------------------------------------+ // /* +----------------------------------------------------------------------+ | Package: Stegger v0.6 | | Class : Stegger | | Created: 03/08/2006 | | Updated: 13/05/2008 | +----------------------------------------------------------------------+ */ /*-------------*/ /* C O N F I G */ /*-------------*/ // This is the public key (the one you give out) to encrypt or decrypt data with define('STEGGER_PUB_KEY', 'Where will the children play?'); /*---------------*/ /* D E F I N E S */ /*---------------*/ // /*-----------*/ /* C L A S S */ /*-----------*/ class Stegger { /*-------------------*/ /* V A R I A B L E S */ /*-------------------*/ // Public Properties /** * boolean * * A flag to determine if we should be verbose with output or not */ var $Verbose = TRUE; // Private Properties /** * boolean * * A flag to determine if we are using a command line interface or not */ var $CLI = FALSE; // Private Properties /** * object * * This is an object representing the image */ var $Image; /** * object * * This is an object representing the main bit stream */ var $BitStream; /** * string * * This is a unique boundry made up of 1's and 0's */ var $BitBoundry; /** * array * * This is the secret data we are going to encode or have decoded */ var $RawData = array(); /*-------------------*/ /* F U N C T I O N S */ /*-------------------*/ /* +------------------------------------------------------------------+ | Constructor | | | | @return void | +------------------------------------------------------------------+ */ function Stegger(){ // Run forever set_time_limit(0); // Setup the environment $this->SetEnvironment(); // Create the bit stream object $this->BitStream = new BitStream(); } // Public API Methods /* +------------------------------------------------------------------+ | Encodes the $secretData into an $imageFile and encrypt with $key | | | | @return void | +------------------------------------------------------------------+ */ function Put($secretData, $imageFile, $key = '', $outputFile = ''){ // Get the start time $StartTime = microtime(TRUE); // Flush any previous bit streams $this->BitStream->FlushStream(); // Tell the user we are loading the image $this->Info('Loading image..'); // Attempt to load the image $this->Image = new Image($imageFile); // If we don't have an image if ($this->Image->EOF()){ // Tell the user the problem $this->FatalError('Could not load the supplied image'); } else { // Tell the user what we are doing $this->Info('Loading data..'); // If we can't load the data they provided if (!$this->Input($secretData)){ // Meh, I hate all this usability stuff $this->FatalError('Could not load the supplied data'); } else { // Tell the user what we are doing $this->Info('Encrypting data..'); // If we can't turn the data into an encrypted string if (!$this->RawToString($key)){ // Tell the user we couldn't encrypt the data $this->FatalError('Could not encrypt the loaded data'); } else { // Tell the user what we are doing $this->Info('Encoding data..'); // If we can't encode the data if (!$this->StringToStream()){ // Tell the user about the error $this->FatalError('Could not encode the loaded data'); } else { // Tell the user what the next step is $this->Info('Encoding image..'); // If we can't encode the image if (!$this->StreamToPixels()){ // Tell the user there was a problem encoding the image $this->FatalError('Could not encode the image'); } else { // Tell the user what we are doing now $this->Info('Saving image..'); // Output the image $this->Image->Output($outputFile); // As the kids say, wewt $this->Success('Done in '.round(microtime(TRUE) - $StartTime).' seconds'); } } } } } } /* +------------------------------------------------------------------+ | This will decode data from an image | | | | @return void | +------------------------------------------------------------------+ */ function Get($imageFile, $key = '', $outputPath = ''){ // Get the start time $StartTime = microtime(TRUE); // Flush any previous bit streams $this->BitStream->FlushStream(); // Tell the user we are loading the image $this->Info('Loading image..'); // Attempt to load the image $this->Image = new Image($imageFile); // If we don't have an image if ($this->Image->EOF()){ // Tell the user the problem $this->FatalError('Could not load the supplied image'); } else { // Tell the user we are about to read the image $this->Info('Reading image..'); // Read the pixels into a bit stream $this->PixelsToStream(); // If we don't have a bit stream if ($this->BitStream->EOF()){ // Tell the user about the problems $this->FatalError('No hidden data found in the image'); } else { // Tell the user we are decoding the data $this->Info('Decoding data..'); // If we can't decode the bit stream into a string if (!$this->StreamToString()){ // Tell the user where it all went wrong $this->FatalError('Could not decode the data'); } else { // Tell the user that the next step is to decrypt and decompress $this->Info('Decrypting data..'); // If we can't decrypt and/or decompress if (!$this->StringToRaw($key)){ // Tell the user about the problem $this->FatalError('Could not decrypt data'); } else { // If we have a problem outputting data if (!$this->Output($outputPath)){ // Fatal Error $this->FatalError('Too many errors to continue'); } else { // We are done $this->Success('Done in '.round(microtime(TRUE) - $StartTime).' seconds'); } } } } } } // Input / Output Methods /* +------------------------------------------------------------------+ | This will load the data to encode into the image | | | | @return boolean | +------------------------------------------------------------------+ */ function Input($data){ // If the data looks like an array but NOT an uploaded file if (is_array($data) && !isset($data['tmp_name'])){ // Loop through each element in the array foreach ($data as $Element){ // Call ourselves again with the element $this->Input($Element); } } else { // Read the data into the raw data array $this->ReadToRaw($data); } // If we have elements in our raw data array if (is_array($this->RawData) && count($this->RawData > 0)){ // Success return TRUE; } else { // Failure return FALSE; } } /* +------------------------------------------------------------------+ | This will set properties relating to our run time environment | | | | @return boolean | +------------------------------------------------------------------+ */ function Output($path = ''){ // If we have raw data to extract if (is_array($this->RawData) && count($this->RawData)){ // If we have a path set if (strlen($path)){ // If the path is not a directory if (!is_dir($path)){ // Error $this->Error('The specified output path is not a directory'); // Failure return FALSE; } // If the path is not writable if (!is_writable($path)){ // Error $this->Error('The specified output path is not writable'); // Failure return FALSE; } // While we have items in the raw data array while (count($this->RawData) > 0){ // If we can't write from the raw data if (!$this->WriteFromRaw($path)){ // Error $this->Error('Problem extracting files'); // Failure return FALSE; } } // If we got here we were probably successfull return TRUE; } else { // If we are in command line mode if ($this->CommandLineInterface()){ // Then tell the user we're gonna need an output path $this->Error('You must specify an output path when using this tool from the command line'); // Failure return FALSE; } else { // Ok browser boy, since you aren't leet enough for a shell you only get one file or message $Data = $this->WriteFromRaw('', TRUE); // Handle each type of data differently switch ($Data['type']){ // Message case 'message': // Send the appropriate mime type header('Content-type: text/plain'); // Attempt to set a file name and get the browser to download header('Content-Disposition: attachment; filename=message.txt'); // Output the message echo $Data['message']; // We should exit now so we don'taccidently send other stuff exit(); // Yeah, I know, redundant break; // File case 'file': // Set the file name and get the browser to download header('Content-Disposition: attachment; filename='.$Data['filename']); // Output the file contents echo $Data['file']; // Don't execute anything below this exit(); // Blah break; } // If we get here we failed return FALSE; } } } else { // Tell the user we had nothing to extract $this->Error('No hidden data to extract from image'); // Failure return FALSE; } } /* +------------------------------------------------------------------+ | Reads a local or remote file or a message into the raw array | | | | @return boolean | +------------------------------------------------------------------+ */ function ReadToRaw($data){ // Figure out what kind of data we are dealing with here switch ($this->GetArgumentType($data)){ // A message case 'message': // If we actually have a message if (strlen($data) > 0){ // Add the message to the final array array_push($this->RawData, array('type' => 'message', 'message' => base64_encode(gzdeflate($data)))); // Success return TRUE; } break; // An uploaded file case 'uploaded': // Attempt to read the temporary file into a variable $Contents = file_get_contents($data['tmp_name']); // If we actually have contents if (strlen($Contents) > 0){ // Add the data to the raw data array array_push($this->RawData, array('type' => 'file', 'file' => base64_encode(gzdeflate($Contents)), 'filename' => $data['name'])); // Success return TRUE; } break; // A glob style string case 'glob': // Loop through all of the glob matches foreach (glob($data) as $File){ // Attempt to read the file into memory $Contents = file_get_contents($File); // If we have contents if (strlen($Contents) > 0){ // Add the data to the raw data array array_push($this->RawData, array('type' => 'file', 'file' => base64_encode(gzdeflate($Contents)), 'filename' => $File)); } } // Were probably successfull return TRUE; break; // A path or url to a file case 'file': // Attempt to read the file into memory $Contents = file_get_contents($data); // If we have contents if (strlen($Contents) > 0){ // Add the data to the raw data array array_push($this->RawData, array('type' => 'file', 'file' => base64_encode(gzdeflate($Contents)), 'filename' => $data)); // We were probably successfull return TRUE; } break; } // If we got here we failed return FALSE; } /* +------------------------------------------------------------------+ | This will pop another item off the raw data stack to output | | | | @return boolean | +------------------------------------------------------------------+ */ function WriteFromRaw($path = '', $return = FALSE){ // If we actually have shit to extract if (is_array($this->RawData) && count($this->RawData) > 0){ // Pop another item off the stack $Data = array_pop($this->RawData); // Handle different data types differently switch ($Data['type']){ // Message case 'message': // If we aren't supposed to return if ($return == FALSE){ // We don't write messages, we output them $this->Info('The following message was embedded in the image'); $this->Info("\t".gzinflate(base64_decode($Data['message']))); // Success return TRUE; } else { // Decompress the message $Data['message'] = gzinflate(base64_decode($Data['message'])); // Return the data type return $Data; } // I don't know why I do this break; // File case 'file': // If we aren't returning if ($return == FALSE){ // If we do not have a path if (!strlen($path)){ // Then this was a waste of our time return FALSE; } else { // Get some ifnormation about the file $Info = pathinfo($Data['filename']); // Get some information about our path $Path = pathinfo($path); // Attempt to open a file pointer to the output path $Pointer = fopen($Path['dirname'].'/'.$Path['basename'].'/'.$Info['basename'], 'w+'); // If we have a pointer if (is_resource($Pointer)){ // Write to the file fwrite($Pointer, gzinflate(base64_decode($Data['file']))); // Close the file fclose($Pointer); // I'm guessing everything went OK return TRUE; } else { // Failure return FALSE; } } } else { // Just decompress and decode the file contents $Data['file'] = gzinflate(base64_decode($Data['file'])); // And return it return $Data; } // break; } } else { // Meh return FALSE; } } /* +------------------------------------------------------------------+ | This will encode and compress a raw data array | | | | @return boolean | +------------------------------------------------------------------+ */ function RawToString($key = ''){ // If we actually have a data array if (is_array($this->RawData) && count($this->RawData) > 0){ // Serialize our data array $this->DataString = serialize($this->RawData); // Instantiate the Secrypt object $Secrypt = new Secrypt(); // If we can encrypt the data if ($Secrypt->Encrypt($this->DataString, $key)){ // Then update the data string $this->DataString = $Secrypt->Data; // We are done with the raw data and encryption class $this->RawData = array(); unset($Secrypt); // Loop untill we have a valid bit boundry while (strstr($this->DataString, $Boundry) || strlen($Boundry) <= 0){ // Generate a new 24 bit boundry $Boundry = chr(rand(33, 127)).chr(rand(33, 127)).chr(rand(33, 127)); } // Reset the bit boundry $this->BitBoundry = ''; // Loop through each character in the new boundry for ($i = 0; $i < 3; $i++){ // Add this to the bit boundry $this->BitBoundry .= str_pad(decbin(ord($Boundry[$i])), 8, '0', STR_PAD_LEFT); } // Success return TRUE; } else { // We have no data string $this->DataString = ''; // Failure return FALSE; } } else { // Nothing to do return FALSE; } } /* +------------------------------------------------------------------+ | This will decompress and decode a string into a raw data array | | | | @return boolean | +------------------------------------------------------------------+ */ function StringToRaw($key = ''){ // If we actually have an encoded data string if (is_string($this->DataString) && strlen($this->DataString) > 0){ // Create a new instance of the Secrypt object $Secrypt = new Secrypt(); // If we can decrypt the string if ($Secrypt->Decrypt($this->DataString, $key)){ // Then unserialize the data array $this->RawData = unserialize($Secrypt->Data); // If we have a raw data array if (is_array($this->RawData) && count($this->RawData) > 0){ // Then we did it return TRUE; } else { // Failure return FALSE; } } else { // Failure return FALSE; } } else { // Nothing to do return FALSE; } } /* +------------------------------------------------------------------+ | This will turn a bit stream into a data string | | | | @return boolean | +------------------------------------------------------------------+ */ function StreamToString(){ // Make sure we have an empty data string $this->DataString = ''; // Loop untill the end of the bit stream while (!$this->BitStream->EOF()){ // Add the character representation for the next 8 bits to our data string $this->DataString .= chr(bindec($this->BitStream->Read(8))); } // If we have a data string if (strlen($this->DataString) > 0){ // Trim any spare spaces off the string $this->DataString = trim($this->DataString, ' '); // Success return TRUE; } else { // Summn went wrong return FALSE; } } /* +------------------------------------------------------------------+ | This will turn an encoded data string into a bit sequence | | | | @return boolean | +------------------------------------------------------------------+ */ function StringToStream(){ // Flush the bit stream $this->BitStream->FlushStream(); // If we have a data string that will fit in the image if ((strlen($this->DataString) * 8) < (($this->Image->CountPixels() - 6) * 3)){ // While the length of the string is not cleanly divisible by 3 while (strlen($this->DataString) % 3 > 0){ // Add a white space character to the data string $this->DataString .= ' '; } // While we still have a data string while (strlen($this->DataString) > 0){ // Write the next chunk of characters to the bit stream $this->BitStream->Write(substr($this->DataString, 0, 1)); // Remove the first character from the data string $this->DataString = substr($this->DataString, 1); } // Success return TRUE; } else { // Work out how many bytes this image can hold $Capacity = round(($this->Image->CountPixels() * 3) / 8); // If we have less than a kilobyte if ($Capacity < 1024){ // Make the capacity human readable $Capacity = $Capacity.' bytes'; // If the capacity is smaller than a megabyte } elseif ($Capacity < 1048576){ // Make the capacity human readable $Capacity = round($Capacity / 1024, 2).' KB'; // The capacity is 1 megabyte or over } else { // Make the capacity human readable $Capacity = round(($Capacity / 1024) / 1024, 2).' MB'; } // Tell the user why the problem occurred $this->Error('That image is not large enough to store that much data'); // Now go over the top and tell the user how they can fix it $this->Error('The image you supplied can only hold '.$Capacity.' of data'); // Failure return FALSE; } } /* +------------------------------------------------------------------+ | This will read pixels to obtain a bit stream | | | | @return void | +------------------------------------------------------------------+ */ function PixelsToStream(){ // Make a new bit stream for the image $BitStream = new BitStream($this->Image->GetBoundry()); // Move to the start pixel $this->Image->StartPixel(); // While we have bits and pixels while (!$this->Image->EOF() && !$BitStream->EOF()){ // Get the current pixels RGB value $Pixel = $this->Image->GetPixel(); // Write the pixel data to the bit stream $BitStream->Write($Pixel); // Move to the next pixel $this->Image->NextPixel(); } // If we got to the end of the image if ($this->Image->EOF()){ // Then we never found our secret data $BitStream->Stream = ''; } // Overwrite the main bit stream with our new one $this->BitStream = $BitStream; } /* +------------------------------------------------------------------+ | This will write a bit stream to pixels | | | | @return void | +------------------------------------------------------------------+ */ function StreamToPixels(){ // Move to the start pixel $this->Image->StartPixel(); // While we have bits and pixels while (!$this->Image->EOF() && !$this->BitStream->EOF()){ // Read the next 3 bits from the bit stream $Bits = $this->BitStream->Read(3); // Write those 3 bits to the current pixel $this->Image->SetPixel($Bits); // Move to the next pixel $this->Image->NextPixel(); } // Set the end bit boundry $this->Image->SetBoundry($this->BitBoundry); // Move to the first pixel $this->Image->FirstPixel(); // Set the first bit boundry $this->Image->SetBoundry($this->BitBoundry); // If we got here we probably succeeded return TRUE; } // Enviromental Methods /* +------------------------------------------------------------------+ | This will set properties relating to our run time environment | | | | @return void | +------------------------------------------------------------------+ */ function SetEnvironment(){ // If we have a REQUEST_METHOD if ($_SERVER['REQUEST_METHOD']){ // Then we are probably being called from the web $this->CLI = FALSE; // Turn verbose output off $this->Verbose = FALSE; } else { // We are being run as a command line (or possibly compiled) app $this->CLI = TRUE; // Turn verbose output on $this->Verbose = TRUE; // Make sure we have implicit flush set to on ob_implicit_flush(1); } } /* +------------------------------------------------------------------+ | This will determine if we are using a command line interface | | | | @return boolean | +------------------------------------------------------------------+ */ function CommandLineInterface(){ // If the command line interface flag is set if ($this->CLI){ // Then we are probably using a command line interface return TRUE; } else { // Not a command line interface return FALSE; } } /* +------------------------------------------------------------------+ | This will attempt to figure out what an argument represents | | | | @return string | +------------------------------------------------------------------+ */ function GetArgumentType($argument){ // If this is looks like an uploaded file if (is_array($argument) && isset($argument['tmp_name'])){ // Then it probably is one return 'uploaded'; // If this looks like a local file } elseif (file_exists($argument)){ // Handle as a file return 'file'; // If this looks like an external resource (TODO: Do this properly) } elseif (strstr($argument, '://')){ // Handle as a file return 'file'; // If the argument contains an asterix (TODO: Check the validity of the path) } elseif (strstr($argument, '*') && ($argument[0] == '.' || $argument[0] == '/')){ // Then I'm guessing it is a glob style string return 'glob'; // Everything else } else { // Treat it as a normal message return 'message'; } } // Message Methods /* +------------------------------------------------------------------+ | Print out an error message to the user and exit | | | | @return void | +------------------------------------------------------------------+ */ function FatalError($msg){ // First we show the error message to the user $this->Error('Fatal Error: '.$msg); // Now we exit exit(-1); } /* +------------------------------------------------------------------+ | Print out an error message to the user | | | | @return void | +------------------------------------------------------------------+ */ function Error($msg){ // If we are running as a command line application if ($this->CommandLineInterface()){ // Just show the message a little formatted for the command line echo '[-] '.$msg.".\n"; } else { // Show the error formatted for the web echo '<strong>Error:</strong> '.htmlspecialchars($msg).'<br />'; } } /* +------------------------------------------------------------------+ | Print out a success message to the user | | | | @return void | +------------------------------------------------------------------+ */ function Success($msg){ // If we are in verbose mode if ($this->Verbose){ // If we are running as a command line application if ($this->CommandLineInterface()){ // Just show the message a little formatted for the command line echo '[+] '.$msg.".\n"; } else { // Show the message formatted for the web echo '<strong>Success:</strong> '.htmlspecialchars($msg).'<br />'; } } } /* +------------------------------------------------------------------+ | Print out an informative message to the user | | | | @return void | +------------------------------------------------------------------+ */ function Info($msg){ // If we are in verbose mode if ($this->Verbose){ // If we are running as a command line application if ($this->CommandLineInterface()){ // Just show the message a little formatted for the command line echo '[i] '.$msg.".\n"; } else { // Show the message formatted for the web echo '<strong>Info:</strong> '.htmlspecialchars($msg).'<br />'; } } } } /* +----------------------------------------------------------------------+ | Package: Stegger v0.5 | | Class : BitStream | | Created: 03/08/2006 | +----------------------------------------------------------------------+ */ class BitStream { /*-------------------*/ /* V A R I A B L E S */ /*-------------------*/ /** * string * * This is a string of 1's and 0's representing binary data */ var $Stream = ''; /** * string * * This is a string of 1's and 0's representing the bit boundry */ var $Boundry = ''; /** * boolean * * This is a flag to determine if the class is still new or not */ var $Fresh = TRUE; /*-------------------*/ /* F U N C T I O N S */ /*-------------------*/ /* +------------------------------------------------------------------+ | Constructor | | | | @return void | +------------------------------------------------------------------+ */ function BitStream($bitBoundry = ''){ // If we have a bit boundry, use it if ($bitBoundry) $this->Boundry = $bitBoundry; } /* +------------------------------------------------------------------+ | This will read $number bits from the bit stream | | | | @return boolean | +------------------------------------------------------------------+ */ function Read($number = 8){ // If we are not on the end of the bit stream if (strlen($this->Stream) > 0){ // Grab the chunk of bits from the bit stream $return = substr($this->Stream, 0, $number); // Remove the chunk of bits from the bit stream $this->Stream = substr($this->Stream, $number); // Return the chunk of bits return $return; } else { // Nothing to return return FALSE; } } /* +------------------------------------------------------------------+ | This will write data to the bit stream | | | | @return void | +------------------------------------------------------------------+ */ function Write($data, $binary = FALSE){ // If we have binary data if ($binary){ // Then just add it raw $this->Stream .= $data; } else { // Handle different data types differently switch (gettype($data)){ // String case 'string': // Loop through each character in the string for ($i = 0; $i < strlen($data); $i++){ // Add the bit representation for this character to the bit stream $this->Stream .= str_pad(decbin(ord($data[$i])), 8, '0', STR_PAD_LEFT); } break; // Integer case 'integer': // Add the bit representation for this character to the bit stream $this->Stream .= str_pad(decbin($data), 8, '0', STR_PAD_LEFT); break; // Boolean case 'boolean': // If the boolean is true if ($data == TRUE){ // Then add a 1 to the bit stream $this->Stream .= '1'; } else { // Add a 0 to the bit stream $this->Stream .= '0'; } break; // Array of RGB values case 'array': // Loop through each primary colour in this RGB array foreach ($data as $PrimaryColour){ // Add the bit value of this integer $this->Stream .= (int) $PrimaryColour % 2; } break; } } } /* +------------------------------------------------------------------+ | This will determine if we have hit the end of the bit stream | | | | @return void | +------------------------------------------------------------------+ */ function EOF(){ // If we have not ran any methods yet if ($this->Fresh){ // We are no longer fresh $this->Fresh = FALSE; // But we are not at the end of the file either return FALSE; } // If we have a bit of stream left if (strlen($this->Stream) > 0){ // If we have a bit boundry if (strlen($this->Boundry)){ // If we have found our bit boundry if (substr($this->Stream, -24) == $this->Boundry){ // Then we remove the boundry from the bit stream $this->Stream = substr($this->Stream, 0, -24); // We hit the end of the stream return TRUE; } else { // We are not at the end of the stream return FALSE; } } else { // Not at the end return FALSE; } } else { // Yeah, we're at the end return TRUE; } } /* +------------------------------------------------------------------+ | This will flush out the bit stream (reset it) | | | | @return void | +------------------------------------------------------------------+ */ function FlushStream(){ // Reset the stream $this->Stream = ''; } } /* +----------------------------------------------------------------------+ | Package: Stegger v0.5 | | Class : Image | | Created: 03/08/2006 | +----------------------------------------------------------------------+ */ class Image { /*-------------------*/ /* V A R I A B L E S */ /*-------------------*/ /** * resource * * This is the main image canvas we are reading from or writing too */ var $Canvas; /** * string * * The name of the image we are encoding to or decoding from */ var $Name = ''; /** * integer * * The main image canvas' width */ var $Width = 0; /** * integer * * The main image canvas' height */ var $Height = 0; /** * array * * This is an array containing the x and y co-ordinate's of the current pixel */ var $PixelPointer = array('x' => 0, 'y' => 0); /** * boolean * * Determines if we are at the end of the image or not */ var $EOF = TRUE; /*-------------------*/ /* F U N C T I O N S */ /*-------------------*/ /* +------------------------------------------------------------------+ | Constructor | | | | @return boolean | +------------------------------------------------------------------+ */ function Image($image){ // If we have an image if ($image){ // Load it $this->Load($image); } else { // Failure return FALSE; } } /* +------------------------------------------------------------------+ | This will load an image as a resource | | | | @return boolean | +------------------------------------------------------------------+ */ function Load($image){ // If the image looks like it was uploaded if (is_array($image) && isset($image['tmp_name'])){ // Set the default output image name using the original file name $this->SetName($image['name']); // Create a canvas for this image $this->CreateCanvas($image['tmp_name'], $image['name']); } else { // Set the default output image name using the path or url to the image $this->SetName($image); // Create a canvas for this image $this->CreateCanvas($image); } // If we actually have a canvas at this point if (is_resource($this->Canvas)){ // We are not at the end of the file $this->EOF = FALSE; // Clear the canvas $this->ClearCanvas(); // Move to the first pixel $this->FirstPixel(); // Success return TRUE; } else { // Failure return FALSE; } } /* +------------------------------------------------------------------+ | Creates an image canvas resource from an image url or path | | | | @return void | +------------------------------------------------------------------+ */ function CreateCanvas($image, $name = ''){ // If we don't have the original name, then the image contains the name if (!$name) $name = $image; // Handle each image type differently switch ($this->GetImageType($name)){ // JPG case 'JPG': // Create a canvas from the JPG $this->Canvas = imagecreatefromjpeg($image); break; // PNG case 'PNG': // Create a canvas from the PNG $this->Canvas = imagecreatefrompng($image); break; // GIF case 'GIF': // Create a canvas from the GIF $this->Canvas = imagecreatefromgif($image); break; // Not Supported default: // Nothing else we can do return; } // If we have an image canvas if (is_resource($this->Canvas)){ // Get the images width $this->Width = imagesx($this->Canvas); // Get the images height $this->Height = imagesy($this->Canvas); // We are not at the end of the file $this->EOF = FALSE; } else { // We are at the end of the file $this->EOF = TRUE; } } /* +------------------------------------------------------------------+ | This will copy the image on to a fresh canvas | | | | @return void | +------------------------------------------------------------------+ */ function ClearCanvas(){ // Create a new true colour canvas based on our images dimensions $Canvas = imagecreatetruecolor($this->Width, $this->Height); // If we have a canvas and an image if (is_resource($Canvas) && is_resource($this->Canvas)){ // Make sure alpha blending is off imagealphablending($Canvas, FALSE); // Copy the contents of the original canvas to the new one imagecopy($Canvas, $this->Canvas, 0, 0, 0, 0, $this->Width, $this->Height); // Overwrite the old canvas with the newly prepaired one $this->Canvas = $Canvas; } } /* +------------------------------------------------------------------+ | This will output the current image to a file or the browser | | | | @return void | +------------------------------------------------------------------+ */ function Output($outputFile = ''){ // If we have an output file specified if ($outputFile){ // Set the output image name $this->SetName($outputFile); } // If we are serving to a browser if ($_SERVER['REQUEST_METHOD']){ // Make sure the browser knows this is a PNG image header('Content-type: image/png'); // Try get the browser to download the image as our name header('Content-Disposition: attachment; filename='.$this->Name); // Output the image to the browser imagepng($this->Canvas); } else { // Get some information about the output path $Info = pathinfo($outputFile); // Write the image to the file name specified imagepng($this->Canvas, $Info['dirname'].'/'.$this->Name); } // Destroy the canvas imagedestroy($this->Canvas); } /* +------------------------------------------------------------------+ | This will get the image type from a URL or path to an image | | | | @return string | +------------------------------------------------------------------+ */ function GetImageType($image){ // Get some information about the path, URL or image name $Info = pathinfo($image); // Handle each extension type differently switch (strtolower($Info['extension'])){ // JPEG case 'jpg': case 'jpeg': // We are dealing with a JPG return 'JPG'; // GIF case 'gif': // We are dealing with a GIF return 'GIF'; // PNG case 'png': // We are dealing with a PNG return 'PNG'; // * default: // No idea what the hell this is return ''; } } /* +------------------------------------------------------------------+ | This will get the RGB value of the current pixel | | | | @return array | +------------------------------------------------------------------+ */ function GetPixel(){ // Get the (32 bit) RGB value from the current image $RGB = imagecolorat($this->Canvas, $this->PixelPointer['x'], $this->PixelPointer['y']); // Obtain the individual values for each primary colour $R = ($RGB >> 16) & 0xFF; $G = ($RGB >> 8) & 0xFF; $B = ($RGB >> 0) & 0xFF; // Return the individual RGB values in an array return array($R, $G, $B); } /* +------------------------------------------------------------------+ | This will set the RGB value of the current array | | | | @return void | +------------------------------------------------------------------+ */ function SetPixel($rgb){ // If this looks like a couple of bits if (is_string($rgb) && strlen($rgb) == 3){ // Get the RGB value of the current pixel $RGB = $this->GetPixel(); // Loop through each primary colour in this pixel for ($i = 0; $i < 3; $i++){ // If the current char of our binary string is a 1 if ($rgb[$i] == '1'){ // If the current colour value isn't odd if ($RGB[$i] % 2 != 1){ // Increment it $RGB[$i]++; } } else { // If the current colour valie isn't even if ($RGB[$i] % 2 != 0){ // Decrease it $RGB[$i]--; } } } // Call ourselves again with the RGB array $this->SetPixel($RGB); // And thats all there is to it return TRUE; } // If we have a full RGB array if (is_array($rgb) && count($rgb) == 3){ // Allocate the colour to the image $Colour = imagecolorallocate($this->Canvas, $rgb[0], $rgb[1], $rgb[2]); // Assign the colour to the current pixel imagesetpixel($this->Canvas, $this->PixelPointer['x'], $this->PixelPointer['y'], $Colour); // We're done here return TRUE; } // If we get here we failed return FALSE; } /* +------------------------------------------------------------------+ | This will count the total number of pixels on the canvas | | | | @return integer | +------------------------------------------------------------------+ */ function CountPixels(){ // Return the width multiplied by the height return round($this->Height * $this->Width); } /* +------------------------------------------------------------------+ | This will move the pixel position to the first pixel | | | | @return void | +------------------------------------------------------------------+ */ function FirstPixel(){ // Reset the pixel pointer $this->PixelPointer['x'] = ($this->Width - 1); $this->PixelPointer['y'] = ($this->Height - 1); } /* +------------------------------------------------------------------+ | This will move the pixel pointer to the start of the data | | | | @return void | +------------------------------------------------------------------+ */ function StartPixel(){ // The data starts 24 bits in (3 bytes) $this->PixelPointer['x'] = ($this->Width - 1) - 8; $this->PixelPointer['y'] = ($this->Height - 1); } /* +------------------------------------------------------------------+ | This will move to the next pixel | | | | @return void | +------------------------------------------------------------------+ */ function NextPixel(){ // If we are on the last column if ($this->PixelPointer['x'] <= 0){ // If we are on the last row of pixels if ($this->PixelPointer['y'] <= 0){ // We are at the end of the file $this->EOF = TRUE; // So we can't go any further return $this->EOF; } else { // Move to the next row $this->PixelPointer['y']--; // Move to the first column of the new row $this->PixelPointer['x'] = ($this->Width - 1); } } else { // Move to the next column $this->PixelPointer['x']--; } } /* +------------------------------------------------------------------+ | This will move to the previous pixel | | | | @return void | +------------------------------------------------------------------+ */ function PrevPixel(){ // If we are on the first column if ($this->PixelPointer['x'] >= ($this->Width - 1)){ // If we are on the first row of pixels if ($this->PixelPointer['y'] >= ($this->Height - 1)){ // Then we can't go back any further return; } else { // Move to the previous row $this->PixelPointer['y']++; // Move to the last column of the current row $this->PixelPointer['x'] = 0; } } else { // Move to the previous column $this->PixelPointer['x']++; } } /* +------------------------------------------------------------------+ | This will get the boundry pattern for the bit stream | | | | @return string | +------------------------------------------------------------------+ */ function GetBoundry(){ // Backup the current pixel pointer $PixelPointer = $this->PixelPointer; // Move to the first pixel $this->FirstPixel(); // Go through the first 8 pixels (24 bits) for ($i = 0; $i < 8; $i++){ // Get this pixels RGB value $Pixel = $this->GetPixel(); // Loop through each primary colour in this foreach ($Pixel as $PrimaryColour){ // Add the bit value of this number to the final string $return .= (int) $PrimaryColour % 2; } // Move to the next pixel $this->NextPixel(); } // Move the pixel pointer back where it was $this->PixelPointer = $PixelPointer; // Return the final value return $return; } /* +------------------------------------------------------------------+ | This sets the bit boundry from the current pixel position | | | | @return void | +------------------------------------------------------------------+ */ function SetBoundry($boundry){ // If we have at least 3 bytes of data (24 bits) if (strlen($boundry) >= 24){ // Initiate the bit counter $b = 0; // Loop through 8 pixels from our current position for ($i = 0; $i < 8; $i++){ // Get the RGB value of the current value $RGB = $this->GetPixel(); // Loop through each primary colour in the RGB array for ($j = 0; $j < 3; $j++){ // Get the next bit from the binary string $Bit = $boundry[$b]; // Figure out which kind of bit this is switch ($Bit){ // 1 case '1': // If this colour is not an odd number if ($RGB[$j] % 2 != 1){ // Then increase it $RGB[$j]++; } break; // 0 case '0': // If this colour is not represented by an even number if ($RGB[$j] % 2 != 0){ // Decrease it $RGB[$j]--; } break; } // Increment the bit counter $b++; } // Set the pixel to our new RGB array $this->SetPixel($RGB); // Move to the next pixel $this->NextPixel(); } } } /* +------------------------------------------------------------------+ | This will set the name of the image we are going to output | | | | @return void | +------------------------------------------------------------------+ */ function SetName($image){ // Get some information about the image filename, path or url $Info = pathinfo($image); // If we have an extension if (strlen($Info['extension']) > 0){ // If the extension is not a PNG if (strtolower($Info['extension']) != 'png'){ // Change the extension to a PNG $Info['basename'] = str_replace('.'.$Info['extension'], '.png', $Info['basename']); } } else { // If we have a basename if (strlen($Info['basename']) > 0){ // Then append our extension to it $Info['basename'] .= '.png'; } else { // This guy isn't giving us much choice $Info['basename'] = 'encoded.png'; } } // Set the image name to the base name $this->Name = $Info['basename']; } /* +------------------------------------------------------------------+ | This will test for the end of the file (image) | | | | @return boolean | +------------------------------------------------------------------+ */ function EOF(){ // Return the end of file property return $this->EOF; } } /* +----------------------------------------------------------------------+ | Package: Stegger v0.5 | | Class : Secrypt | | Created: 23/07/2006 | +----------------------------------------------------------------------+ */ class Secrypt { /*-------------------*/ /* V A R I A B L E S */ /*-------------------*/ // Public Properties /** * array * * This is the array of keys we use to encrypt or decrypt data */ var $Keys = array('public' => '', 'private' => '', 'xfactor' => '', 'yfactor' => '', 'zfactor' => ''); /** * string * * This holds the data after it has been successfully encrypted or decrypted */ var $Data = ''; /** * boolean * * Determines if we can zip the contents or not */ var $Zip = TRUE; /** * array * * All the error messages in an array */ var $Errors = array(); // Private Properties /** * array * * An array that holds each of our base64 compatible charsets */ var $Locks = array(); /*-------------------*/ /* F U N C T I O N S */ /*-------------------*/ /* +------------------------------------------------------------------+ | Constructor | | | | @return void | +------------------------------------------------------------------+ */ function Secrypt(){ // If we can't zip if (!function_exists('gzdeflate')){ // Then we don't zip $this->Zip = FALSE; } // Run forever set_time_limit(0); // Reset the lock $this->ResetLock(); } // Public API Methods /* +------------------------------------------------------------------+ | This will encrypt $data against the $privateKey and $publicKey | | | | @return string | +------------------------------------------------------------------+ */ function Encrypt($data, $privateKey = '', $publicKey = STEGGER_PUB_KEY){ // Insert the keys $this->InsertKeys($privateKey, $publicKey); // Turn all the keys $this->TurnKey(); // Locketh the data return $this->Lock($data); } /* +------------------------------------------------------------------+ | This will decrypt $data against the $privateKey and $publicKey | | | | @return string | +------------------------------------------------------------------+ */ function Decrypt($data, $privateKey = '', $publicKey = STEGGER_PUB_KEY){ // Insert the keys $this->InsertKeys($privateKey, $publicKey); // Turn all the keys $this->TurnKey(); // Unlock the data and return the results return $this->Unlock($data); } // Key Methods /* +------------------------------------------------------------------+ | This gets a reference to the key that fits in $lockType | | | | @return reference | +------------------------------------------------------------------+ */ function &GetKey($lockType){ // Return the appropriate key return $this->Keys[$lockType]; } /* +------------------------------------------------------------------+ | This will set all the keys in the key array at once | | | | @return void | +------------------------------------------------------------------+ */ function InsertKeys($private, $public){ // Remove all keys $this->RemoveKey(); // Reset all the locks $this->ResetLock(); // Loop through all the keys foreach ($this->Keys as $KeyType => $Key){ // If this is a factor key if (strstr($KeyType, 'factor')){ // Set the key to the md5 hash of the keys array thus far $Key = md5(serialize($this->Keys)); } else { // Set the key to the key we were passed $Key = $$KeyType; } // Insert the key we have in the end $this->InsertKey($Key, $KeyType); } } /* +------------------------------------------------------------------+ | This will set a $key for $lockType | | | | @return void | +------------------------------------------------------------------+ */ function InsertKey($key, $lockType){ // If we have a key if (strlen($key) > 0){ // Set the key $this->Keys[$lockType] = $key; } } /* +------------------------------------------------------------------+ | This will turn a lock based on a keys contents | | | | @return void | +------------------------------------------------------------------+ */ function TurnKey($lockType = ''){ // If we don't have a lock type if (!$lockType){ // Loop through all the locks foreach ($this->Locks as $LockType => $Lock){ // Call ourselves with this lock type $this->TurnKey($LockType); } // Don't pass this bit return; } // Get a reference to the desired key $Key =& $this->GetKey($lockType); // Loop through each character of the key for ($i = 0; $i < strlen($Key); $i++){ // Work out how many steps to turn the lock $Steps = ord($Key[$i]) / ($i + 1); // If the decimal value of the current character is odd if (ord($Key[$i]) % 2 != 0){ // Turn the lock left $this->TurnLock($lockType, $Steps, 'left'); } else { // Turn the lock right $this->TurnLock($lockType, $Steps, 'right'); } } } /* +------------------------------------------------------------------+ | This will clear a keys contents, all keys if no $lockType is set | | | | @return void | +------------------------------------------------------------------+ */ function RemoveKey($lockType = ''){ // Loop through each of the keys foreach($this->Keys as $KeyName => $Key){ // If this is our desired key or we don't have a desired key if ($lockType == $KeyName || strlen($lockType) == 0){ // Reset this key $this->Keys[$KeyName] = ''; } } } // Lock Methods /* +------------------------------------------------------------------+ | This gets a reference to the character set a key manipulates | | | | @return reference | +------------------------------------------------------------------+ */ function &GetLock($lockType){ // Return a reference to the lock return $this->Locks[$lockType]; } /* +------------------------------------------------------------------+ | This will lock the data according to the current character index | | | | @return string | +------------------------------------------------------------------+ */ function Lock($data){ // Reset the data $this->Data = ''; // If we are supposed to be zipping if ($this->Zip == TRUE){ // If we can't compress the data if (FALSE === ($data = @gzdeflate($data))){ // Add the error incase the user wants to know why we failed $this->Error('There was a problem compressing the data'); // Huston, we have a problem return FALSE; } } // If we can compress the character if (FALSE !== ($data = base64_encode($data))){ // Loop through each character in the data for ($i = 0; $i < strlen($data); $i++){ // Convert this character to its encrypted equivilent $data[$i] = $this->GetChar($data[$i], TRUE); } // Looks like we have ourselves some data $this->Data = $data; // And thats all folks return $this->Data; } else { // Add the error to let the user know why we failed $this->Error('There was a problem encoding the data'); // Failure return FALSE; } } /* +------------------------------------------------------------------+ | This unlocks the data according to the current character index | | | | @return string | +------------------------------------------------------------------+ */ function Unlock($data){ // Reset the data $this->Data = ''; // Loop through each character in the data for ($i = 0; $i < strlen($data); $i++){ // Convert this character to its decrypted equivilent $data[$i] = $this->GetChar($data[$i], FALSE); } // If we can base64 decode the data if (FALSE !== ($data = base64_decode($data))){ // If we can decompress data if (FALSE !== ($data = @gzinflate($data))){ // Looks like we have ourselves some data $this->Data = $data; // Thats all folks return $this->Data; } else { // Tell the user why we failed $this->Error('There was a problem decompressing the data'); // Failure return FALSE; } } else { // Add the error ro the error stack $this->Error('There was a problem decoding the data'); // Failure return FALSE; } } /* +------------------------------------------------------------------+ | This will turn a lock (character set) $steps steps in $direction | | | | @return void | +------------------------------------------------------------------+ */ function TurnLock($lockType, $steps = 5, $direction = 'right'){ // Loop through the required number of steps for ($i = 0; $i < $steps; $i++){ // Get a reference to the lock $Lock =& $this->GetLock($lockType); // If we are not going right, reverse the string if ($direction != 'right') $Lock = strrev($Lock); // Make a copy of the counter $c = $i; // If we are rotating a character passed the end of the character set if ($c >= strlen($Lock)){ // While we still have too little characters to split while ($c >= strlen($Lock)){ // Minus the lock length from the counter $c = $c - strlen($Lock); } } // Isolate the first character in the charset $Char = substr($Lock, 0, 1); $Lock = substr($Lock, 1); // If our split point exists if (strlen($Lock[$c]) > 0){ // Split the string at the desired position $Chunks = explode($Lock[$c], $Lock); // If we have some chunks if (is_array($Chunks)){ // Then piece together the string $Lock = $Chunks[0].$Lock[$c].$Char.$Chunks[1]; } } else { // Put the lock back to the way it was $Lock = $Char.$Lock; } // If we are not going right, reverse the string back if ($direction != 'right') $Lock = strrev($Lock); } } /* +------------------------------------------------------------------+ | This will generate the original charset and character index | | | | @return void | +------------------------------------------------------------------+ */ function ResetLock($lockType = ''){ // Get the base 64 compatible character set $CharSet = $this->GetCharSet(); // Loop through the keys we have foreach ($this->Keys as $LockType => $Key){ // If we were supplied a lock type to reset if ($lockType){ // If this is our lock if ($LockType == $lockType){ // Then reset the lock $this->Locks[$LockType] = $CharSet; // And we're done return; } } else { // Reset this lock $this->Locks[$LockType] = $CharSet; } } } // Character Set Methods /* +------------------------------------------------------------------+ | This will lookup the encrypted/decrypted version of a character | | | | @return string | +------------------------------------------------------------------+ */ function GetChar($char, $encrypt = FALSE){ // If we are not encrypting, flip the locks if (!$encrypt) $this->Locks = array_reverse($this->Locks); // Initate the lock counter $i = 0; // Loop through each lock foreach ($this->Locks as $LockType => $Lock){ // If this is the first lock, set the initial position if ($i == 0){ // Get the initial position $Position = strpos($Lock, $char); } // If the lock counter is odd, or this is the final iteration if ($i % 2 > 0){ // If we are encrypting if ($encrypt){ // Swap position $Position = strpos($Lock, $char); } else { // Swap character $char = $Lock[$Position]; } } else { // If we are encrypting if ($encrypt){ // Swap character $char = $Lock[$Position]; } else { // Swap position $Position = strpos($Lock, $char); } } // Increment the lock counter $i++; } // If we are not encrypting, flip the locks if (!$encrypt) $this->Locks = array_reverse($this->Locks); // Return the character return $char; } /* +------------------------------------------------------------------+ | This will generate and return a base 64 compatible charset | | | | @return string | +------------------------------------------------------------------+ */ function GetCharSet(){ // These are forbidden characters that fall in the range of chars we iterate $ForbiddenChars = array_merge(range(44, 46), range(58, 64), range(91, 96)); // Loop through the base64 compatible range of characters for ($i = 43; $i < 123; $i++){ // If this is not a forbidden character if (!in_array($i, $ForbiddenChars)){ // Then add this to the final character set $return .= chr($i); } } // Return the final character set return $return; } // Error Reporting Methods /* +------------------------------------------------------------------+ | This will add an error message to the error message stack | | | | @return void | +------------------------------------------------------------------+ */ function Error($msg){ // Add the error to the stack $this->Errors[] = $msg; } /* +------------------------------------------------------------------+ | This will display the error messages specific to the current env | | | | @return void | +------------------------------------------------------------------+ */ function ShowErrors($returnVal = FALSE){ // Loop through all the errors foreach ($this->Errors as $Error){ // If we are being called from the web if (strlen($_SERVER['REQUEST_METHOD']) > 0){ // Format the errors for the web $return .= '<strong>Error:</strong> '.$Error.'<br />'; } else { // Format the error message for the command line $return .= '[-] '.$Error."\n"; } } // Now that we are showing the errors, we can clear them too $this->Errors = array(); // If we are supposed to the return the errors if ($returnVal){ // Then return them we shall return $return; } else { // Output the errors directly echo $return; } } // Debug Methods /* +------------------------------------------------------------------+ | This will output a message instantly for debugging purposes | | | | @return void | +------------------------------------------------------------------+ */ function Debug($msg){ // Turn implicit output buffering on incase it is off ob_implicit_flush(1); // If we are being called from the web if (strlen($_SERVER['REQUEST_METHOD'])){ // Then format the message for the web $msg = '<strong>Debug:</strong> '.$msg.'<br />'; } else { // Format the message for a CLI $msg = '[i] '.$msg."\n"; } // Output the message echo $msg; } } ?>