Login   Register  
PHP Classes
elePHPant
Icontem

File: Stegger.class.inc.php

Recommend this page to a friend!
Stumble It! Stumble It! Bookmark in del.icio.us Bookmark in del.icio.us
  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: 6 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;
    }
 }

?>