Skip to content

Instantly share code, notes, and snippets.

@Nevon
Created March 26, 2013 07:51

Revisions

  1. Nevon created this gist Mar 26, 2013.
    2,722 changes: 2,722 additions & 0 deletions Stegger.class.inc.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,2722 @@
    <?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(){

    $return = '';

    // 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);

    $c = intval($c);

    // If our split point exists
    if (strlen(substr($Lock, $c, 1)) > 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(){
    $return = '';
    // 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;
    }
    }

    ?>