<?php

/**
* PHP Trivial Encryption Classes
* Copyright (C) 2007 Toby Inkster
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @author Toby Inkster
* @copyright Copyright (C) 2007 Toby Inkster
* @package TrivialEncoder
* @version 0.2
* @link http://tobyinkster.co.uk/tag/trivial-encoder/
* @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public Licence
*/

/* Disable notices because they cause problems when MCrypt is not available. */
// error_reporting(E_ALL ^ E_NOTICE);

class TE_Init_Exception extends Exception { }
class
TE_Crypto_Exception extends Exception { }
class
TE_Manager_Exception extends Exception { }

define('ASCII_SAFE_NEVER', 0);
define('ASCII_SAFE_PASSTHRU', 0.5);
define('ASCII_SAFE_ALWAYS', 1);

define('PARAMS_NONE', 0);
define('PARAMS_NUMBER', 1);
define('PARAMS_KEYWORD', 2);
define('PARAMS_UCASE', 4);
define('PARAMS_MISC', 1024);

define('STRENGTH_TRIVIAL',  1.1);
define('STRENGTH_WEAK',     1.5);
define('STRENGTH_MEDIUM',   2.0);
define('STRENGTH_STRONG',   4.0);
define('STRENGTH_UNKNOWN',  0.0);

/**
* Deprecated Trivial Encoder Manager.
*
* Use TE_Machine instead.
*/
class TrivialEncoderManager
{
    private
$encoders = array();
    
    public function
__construct ()
    {
        
$classes = get_declared_classes();
        foreach (
$classes as $c)
            if (
is_subclass_of($c, 'TrivialEncoder'))
                
$this->encoders[ eval("return $c::CODE;") ] = $c;
        foreach (
$this->encoders as $k=>$v)
            if (
substr($k,0,1)=='!')
                unset(
$this->encoders[$k]);
        
ksort($this->encoders);
    }
    
    public function
encode ($method, $text)
    {
        
$steps = explode(';', $method);
        foreach (
$steps as $s)
        {
            
$options = explode(' ', $s);
            
$code  = strtolower(trim(array_shift($options)));
            
$class = $this->encoders[$code];
            
            if (!
strlen($class))
                throw new
TE_Manager_Exception("Encoder '$code' not found.");
            
            
$encoder = new $class($options);
            
$text = $encoder->encode($text);
        }
        return
$text;
    }

    public function
decode ($method, $text)
    {
        
$steps = array_reverse(explode(';', $method));
        foreach (
$steps as $s)
        {
            
$options = explode(' ', $s);
            
$code  = strtolower(trim(array_shift($options)));
            
$class = $this->encoders[$code];
            
            if (!
strlen($class))
                throw new
TE_Manager_Exception("Encoder '$code' not found.");
            
            
$encoder = new $class($options);
            
$text = $encoder->decode($text);
        }
        return
$text;
    }
}

abstract class
TrivialEncoder
{
    const
NAME           = 'Abstract';
    const
CODE           = '!';
    const
LINK           = 'http://tobyinkster.co.uk/blog/';
    const
ASCII_SAFENESS = ASCII_SAFE_NEVER;
    const
REQUIRE_MCRYPT = FALSE;
    const
PARAMS         = PARAMS_NONE;
    const
STRENGTH       = STRENGTH_UNKNOWN;
    
    abstract public function
__construct ($options);
    abstract public function
encode ($text);
    abstract public function
decode ($code);
    public function
explain () { return "No description is available."; }
}

class
TrivialEncoder_Rot extends TrivialEncoder
{
    const
NAME           = 'Alphabetic Rotation';
    const
CODE           = 'rot';
    const
LINK           = 'http://en.wikipedia.org/wiki/Caesar_cipher';
    const
ASCII_SAFENESS = ASCII_SAFE_PASSTHRU;
    const
PARAMS         = PARAMS_NUMBER;
    const
STRENGTH       = STRENGTH_TRIVIAL;
    
    protected
$n = 0;
    protected static
$alphabet = 'abcdefghijklmnopqrstuvwxyz';
    
    public function
__construct ($options)
    {
        if (isset(
$options[0]))
            
$this->n = (int)$options[0] % 26;
    }
    
    public function
encode ($text)
    {
        
$rotAlpha = substr(self::$alphabet, $this->n)
            .
substr(self::$alphabet, 0, $this->n);
        
$from = self::$alphabet.strtoupper(self::$alphabet);
        
$to   = $rotAlpha.strtoupper($rotAlpha);
        
        
$code = strtr($text, $from, $to);
        return
$code;
    }
    
    public function
decode ($code)
    {
        
$this->n = (26 - $this->n) % 26;
        
$text = $this->encode($code);
        
$this->n = (26 - $this->n) % 26;
        return
$text;
    }
    
    public function
explain ()
    {
        return
wordwrap("'rot' is a group of substitution cyphers in which each letter in the plaintext is replaced by a letter some fixed number of positions further down the alphabet. For example, with a shift of 3, 'A' would be replaced by 'D', 'B' would become 'E', and so on.\n\n"
                
."Note that this only changes letters in the message.\n\n"
                
."Examples:\n\n"
                
."'rot 5'\n"
                
."Will rotate each letter five places.\n\n"
                
."'rot 25'\n"
                
."'rot -1'\n"
                
."These are exactly equivalent."
                
);
    }
}

class
TrivialEncoder_Rot13 extends TrivialEncoder_Rot
{
    const
NAME     = 'ROT-13';
    const
CODE     = 'rot13';
    const
LINK     = 'http://en.wikipedia.org/wiki/Rot-13';
    const
PARAMS   = PARAMS_NONE;
    const
STRENGTH = STRENGTH_TRIVIAL;

    protected
$n = 13;
    public function
__construct ($options) { /* no options */ }

    public function
explain ()
    {
        return
parent::explain()."\n\n".wordwrap("'rot13' shifts the alphabet by 13 places, half the length of the alphabet, thus being its own inverse.");
    }
}

class
TrivialEncoder_Caesar extends TrivialEncoder_Rot
{
    const
NAME     = 'Caesar\'s Cypher';
    const
CODE     = 'caesar';
    const
LINK     = 'http://en.wikipedia.org/wiki/Caesar_cipher';
    const
PARAMS   = PARAMS_NONE;
    const
STRENGTH = STRENGTH_TRIVIAL;
    
    protected
$n = 3;
    public function
__construct ($options) { /* no options */ }

    public function
explain ()
    {
        return
parent::explain()."\n\n".wordwrap("'caesar' is the cypher used by Julius Caesar to communicate with his generals. It uses a shift of three places.");
    }
}

class
TrivialEncoder_Rot47 extends TrivialEncoder
{
    const
NAME           = 'ROT-47';
    const
CODE           = 'rot47';
    const
LINK           = 'http://en.wikipedia.org/wiki/Rot-13#Variants';
    const
ASCII_SAFENESS = ASCII_SAFE_PASSTHRU;
    const
PARAMS         = PARAMS_NUMBER;
    const
STRENGTH       = STRENGTH_TRIVIAL;
    
    protected
$n = 47;
    protected static
$alphabet = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~';
    
    public function
__construct ($options)
    {
        if (isset(
$options[0]))
            
$this->n = (int)$options[0] % 94;
    }
    
    public function
encode ($text)
    {            
        
$rotAlpha = substr(self::$alphabet, $this->n)
            .
substr(self::$alphabet, 0, $this->n);
        
        
$code = strtr($text, self::$alphabet, $rotAlpha);
        return
$code;
    }
    
    public function
decode ($code)
    {
        
$this->n = (94 - $this->n) % 94;
        
$text = $this->encode($code);
        
$this->n = (94 - $this->n) % 94;
        return
$text;
    }

    public function
explain ()
    {
        return
wordwrap("This is a family of cyphers that operate similarly to the alphabetic rotation cyphers. However, instead of just operating on the alphabet, they operate on the 94 most common printable ASCII characters. This provides a slightly stronger encryption, especially for text that includes significant numeric information.\n\n"
                
."Although the default is to rotate text 47 characters (half of 94, and thus its own inverse), you may pass a numeric parameter to this function to rotate it a different number of places."
                
);
    }
}

class
TrivialEncoder_XOR extends TrivialEncoder
{
    const
NAME     = 'Simple XOR Encryption';
    const
CODE     = 'xor';
    const
PARAMS   = PARAMS_NUMBER;
    const
STRENGTH = STRENGTH_WEAK;

    protected
$n = 255;
    
    public function
__construct ($options)
    {
        if (isset(
$options[0]))
            
$this->n = (int)$options[0] % 256;
    }
    
    public function
encode ($text)
    {
        
$code = '';
        
        while (
strlen($text))
        {
            
$char = substr($text, 0, 1);
            
$text = substr($text, 1);
            
            
$code .= chr($this->n ^ ord($char));
        }
        
        return
$code;
    }
    
    public function
decode ($code)
    {
        return
$this->encode($code);
    }
    
    public function
explain ()
    {
        return
wordwrap("'xor N' performs a bitwise XOR operation with a given number N on each character of the message. Although this is a pretty simple encoding scheme, it's a little better than the 'rot' family of encodings, and works well on binary data. Default N is 255.\n\n"
                
."Examples:\n\n"
                
."'xor 13'\n"
                
."Will XOR each byte with the binary 00001101. Will encode 'ABCDE' to 'LONIH'.\n\n"
                
."'xor 0'\n"
                
."Does nothing."
                
."'xor 19; xor 19'\n"
                
."XOR encodings are their own inverse, so this does nothing too."
                
);
    }
}

class
TrivialEncoder_Memfrob extends TrivialEncoder_XOR
{
    const
NAME     = 'Memfrob Frobnication';
    const
CODE     = 'memfrob';
    const
LINK     = 'http://www.gnu.org/software/libc/manual/html_node/Trivial-Encryption.html';
    const
PARAMS   = PARAMS_NONE;
    const
STRENGTH = STRENGTH_TRIVIAL;

    protected
$n = 42;

    public function
__construct ($options) { /* no options */ }
    
    public function
explain ()
    {
        return
parent::explain()."\n\n".wordwrap("'memfrob' is the equivalent of 'xor 42'. (According to Douglas Adams, 42 is the answer to life, the universe and everything.)");
    }
}

class
TrivialEncoder_Keyword extends TrivialEncoder
{
    const
NAME           = 'Keyword Cypher';
    const
CODE           = 'key';
    const
LINK           = 'http://en.wikipedia.org/wiki/Keyword_cipher';
    const
ASCII_SAFENESS = ASCII_SAFE_PASSTHRU;
    const
PARAMS         = PARAMS_KEYWORD;
    const
STRENGTH       = STRENGTH_WEAK;
    
    protected
$key = 'kryptos';
    protected static
$alphabet = 'abcdefghijklmnopqrstuvwxyz';

    public function
__construct ($options)
    {
        if (isset(
$options[0]))
        {
            
$gotit = array();
            
$key = strtolower($options[0]);
            
$key = preg_replace('/[^a-z]/', '', $key);
            if (
strlen($key))
                
$this->key = '';
            while (
strlen($key))
            {
                
$char = substr($key, 0, 1);
                
$key  = substr($key, 1);
                if (isset(
$gotit[$char]))
                    continue;
                
$gotit[$char] = 1;
                
$this->key .= $char;
            }
        }
    }
    
    public function
encode ($text)
    {
        
$to   = $this->make_keystring();
        
$from = self::$alphabet;
        
$to   = $to   . strtoupper($to);
        
$from = $from . strtoupper($from);
        return
strtr($text, $from, $to);
    }
    
    public function
decode ($code)
    {
        
$to   = $this->make_keystring();
        
$from = self::$alphabet;
        
$to   = $to   . strtoupper($to);
        
$from = $from . strtoupper($from);
        return
strtr($code, $to,  $from);
    }
    
    protected function
make_keystring ()
    {
        return
$this->key.preg_replace('/['.$this->key.']/i','', self::$alphabet);
    }
}

class
TrivialEncoder_OneTimePad_Xor extends TrivialEncoder
{
    const
NAME     = 'One Time Pad (Xor)';
    const
CODE     = 'otpx';
    const
LINK     = 'http://en.wikipedia.org/wiki/One-time_pad';
    const
STRENGTH = STRENGTH_WEAK;
    
    public function
__construct () { /* NOOP */ }
    
    public function
encode ($text)
    {
        
$sheet = self::get_sheet(strlen($text));
        return
$sheet . self::use_sheet($text, $sheet);
    }
    
    public function
decode ($code)
    {
        if (
strlen($code)%2 != 0)
            throw new
TE_Crypto_Exception("Input not valid. Wrong length: ".strlen($code).".");
    
        
$sheet = substr($code, 0, strlen($code)/2);
        
$code  = substr($code, strlen($code)/2);

        return
self::use_sheet($code, $sheet);
    }
    
    public function
explain ()
    {
        return
wordwrap("The one time pad is a very old, but incredibly effective form of encryption. The only problem is that it's symmetric, with a very big key. To implement it in a library such as this, the key needs to be included as part of the result, which makes this algorithm as secure as a locked door where they key has been left in the lock.\n\n"
                
."This algorithm combines the plaintext with the pad sheet using the XOR operation. See also 'otp'.\n\n"
                
."For a one time pad algorithm with very good security, see 'multix'."
                
);
    }

    public static function
get_sheet ($len)
    {
        
$retval = '';
        for (
$i=0 ; $i<$len ; $i++ )
            
$retval .= chr(rand(0,255));
        return
$retval;
    }
    
    public static function
use_sheet ($a, $b)
    {
        if (
strlen($a)!=strlen($b))
            throw new
TE_Crypto_Exception("Input not valid. Pad sheet and crypted text differ in length.");

        
$retval = '';
        while (
strlen($a) && strlen($b))
        {
            
$A = substr($a, 0, 1); $a = substr($a, 1);
            
$B = substr($b, 0, 1); $b = substr($b, 1);
            
            
$retval .= $A ^ $B;
        }
        
        return
$retval;
    }
}

class
TrivialEncoder_OneTimePad_Mod extends TrivialEncoder_OneTimePad_Xor
{
    const
NAME     = 'One Time Pad (Modulo)';
    const
CODE     = 'otp';
    const
STRENGTH = STRENGTH_WEAK;

    public function
encode ($text)
    {
        
$sheet = self::get_sheet(strlen($text));
        return
$sheet . self::use_sheet($text, $sheet);
    }
    
    public function
decode ($code)
    {
        if (
strlen($code)%2 != 0)
            throw new
TE_Crypto_Exception("Input not valid. Wrong length: ".strlen($code).".");
    
        
$sheet = substr($code, 0, strlen($code)/2);
        
$code  = substr($code, strlen($code)/2);

        return
self::use_sheet($code, $sheet, TRUE);
    }

    public function
explain ()
    {
        return
wordwrap("The one time pad is a very old, but incredibly effective form of encryption. The only problem is that it's symmetric, with a very big key. To implement it in a library such as this, the key needs to be included as part of the result, which makes this algorithm as secure as a locked door where they key has been left in the lock.\n\n"
                
."This algorithm combines the plaintext with the pad sheet using modulo 256 addition. See also 'otpx'.\n\n"
                
."For a one time pad algorithm with very good security, see 'multi'."
                
);
    }
    
    public static function
use_sheet ($a, $b, $subtract=FALSE)
    {
        if (
strlen($a)!=strlen($b))
            throw new
TE_Crypto_Exception("Input not valid. Pad sheet and crypted text differ in length.");

        
$retval = '';
        while (
strlen($a) && strlen($b))
        {
            
$A = substr($a, 0, 1); $a = substr($a, 1);
            
$B = substr($b, 0, 1); $b = substr($b, 1);
            
            if (
$subtract)
                
$retval .= chr((ord($A) - ord($B)) % 256);
            else
                
$retval .= chr((ord($A) + ord($B)) % 256);
        }
        
        return
$retval;
    }
}

class
TrivialEncoder_Multi_Mod extends TrivialEncoder
{
    const
NAME     = 'Multi-OTP (Modulo)';
    const
CODE     = 'multi';
    const
LINK     = 'http://en.wikipedia.org/wiki/Superencryption';
    const
PARAMS   = PARAMS_MISC;
    const
STRENGTH = STRENGTH_MEDIUM;

    protected
$methods = array();
    protected
$otp = 'TrivialEncoder_OneTimePad_Mod';
    
    public function
__construct ($options)
    {
        if (!
class_exists('TE_Machine'))
            throw new
TE_Init_Exception("This encoding method can only be used in conjunction with TE_Machine, not TrivialEncoderManager.");
    
        foreach (
$options as $i=>$x)
        {
            if (
$x instanceof TE_Ast_Script)
                
$this->methods[] = $x;
            else
                throw new
TE_Init_Exception("Wrong parameter type: parameter $i should be of type TE_Ast_Script.");
        }
    }
    
    public function
encode ($text)
    {
        
$count = count($this->methods);
        
$textlen = strlen($text);
        
        
$encrypted = $text;
        
$pads = array();

        
/* For each method */
        
for ($i=0; $i<$count; $i++)
        {
            
/* Get a one time pad sheet. */
            
$pads[$i]   = call_user_func(array($this->otp, 'get_sheet'), $textlen);
            
            
/* Encrypt the data using the sheet. */
            
$encrypted  = call_user_func(array($this->otp, 'use_sheet'), $encrypted, $pads[$i]);

            
/* Encrypt that sheet using the method. */
            
$machine = TE_Machine_Encoder;
            
$machine->set_buffer($pads[$i]);
            
$this->methods[$i]->evaluate($machine);
            
$cpads[$i]  = $machine->get_buffer();

            
/* Store the length of the sheet. */
            
$lcpads[$i] = strlen($cpads[$i]);
        }
        
        
/* Return concatenated data. */
        
return '['.join(' ', $lcpads).']'.join('',$cpads).$encrypted;
    }
    
    public function
decode ($code)
    {
        if (!
preg_match('/\[([0-9 ]+)\]/', $code, $matches))
            throw new
TE_Crypto_Exception('No part length codes included.');
        
$code = substr($code, strlen($matches[0]));
        
$lcpads = explode(' ', $matches[1]);

        for (
$i=0; isset($lcpads[$i]); $i++)
        {
            
$length    = $lcpads[$i];
            
$cpads[$i] = substr($code, 0, $length);
            
$code      = substr($code, $length);

            
/* Encrypt the sheet using the method. */
            
$machine = new TE_Machine_Decoder;
            
$machine->set_buffer($cpads[$i]);
            
$this->methods[$i]->evaluate($machine);
            
$pads[$i]  = $machine->get_buffer();
        }
        foreach (
$pads as $p)
            
$code = call_user_func(array($this->otp, 'use_sheet'), $code, $p, TRUE);
        return
$code;
    }

    public function
calc_strength ($base)
    {
        
$retval = $base;
        foreach (
$this->methods as $method)
        {
            
$machine = new TE_Machine_StrengthCalculator;
            
$machine->set_buffer(1);
            
$method->evaluate($machine);
            
$retval *= $machine->get_buffer();
        }
        return
$retval;
    }
    
    public function
explain ()
    {
        return
wordwrap("This algorithm allows you to \"fork\" the encryption in two different directions. While normally cryptographic algorithms are pipelined with each other, this uses an algorithm described in Bruce Schneier's _Applied Cryptography, Second Edition: Protocols, Algorithms, and Source Code in C_ to apply two or more algorithms to the message simultaneously.\n\n"
                
."Two or more one time pad sheets are applied to the message. The sheets are then encrypted using different cryptographic algorithms, which must be specified in parentheses, and then the encrypted pads and encrypted message are all concatenated together. A small header is prepended to the result to record the lengths of the encrypted keys to aid with decryption.\n\n"
                
."Examples:\n\n"
                
."'multi (twofish), (morse; memfrob); base64'\n"
                
."Will apply two one time pad sheets to the message. The first sheet will be encrypted with Twofish; the second sheet will be encrypted by morse code and then memfrob. The two encrypted sheets will be concatenated with the encrypted message. The result will be encoded with Base64.\n\n"
                
."'multi (twofish \"Password1\"), (rijndael512 \"Password2\"), (cast256 \"Password3\");'\n"
                
."You really don't want this message to be decrypted. Short of cracking three world-class encryption schemes, the only way this code can be broken is by guessing your three passwords.\n\n"
                
."(This information applies equally to 'multi' and 'multix'.)"
                
);
    }
}

class
TrivialEncoder_Multi_Xor extends TrivialEncoder_Multi_Mod
{
    const
NAME     = 'Multi-OTP (Xor)';
    const
CODE     = 'multix';
    const
PARAMS   = PARAMS_MISC;
    const
STRENGTH = STRENGTH_MEDIUM;

    protected
$methods = array();
    protected
$otp = 'TrivialEncoder_OneTimePad_Xor';
}

class
TrivialEncoder_Hex extends TrivialEncoder
{
    const
NAME           = 'Hexadecimal';
    const
CODE           = 'hex';
    const
LINK           = 'http://en.wikipedia.org/wiki/Hexadecimal';
    const
ASCII_SAFENESS = ASCII_SAFE_ALWAYS;
    const
STRENGTH       = STRENGTH_TRIVIAL;
    
    protected
$fstr      = '%02x';
    protected
$dstr      = '%x';
    protected
$chunksize = 2;
    
    public function
__construct ($options) { /* no options */ }

    public function
encode ($text)
    {
        
$code = '';
        while (
strlen($text))
        {
            
$char = substr($text, 0, 1);
            
$text = substr($text, 1);
            
            
$code .= sprintf($this->fstr, ord($char));
        }
        
        return
$code;
    }
    
    public function
decode ($code)
    {
        
$text = '';
        while (
strlen($code))
        {
            
$char = substr($code, 0, $this->chunksize);
            
$code = substr($code, $this->chunksize);
            
            list(
$dechar) = sscanf($char, $this->dstr);
            
$text .= chr($dechar);
        }
        
        return
$text;
    }

    public function
explain ()
    {
        return
wordwrap("Converts input from ASCII (base 256) to hexadecimal (base 16). Doubles message length. This is not a strong encoding, but can be used to force the message to be ASCII-safe, as all output will be alphanumeric.");
    }
}

class
TrivialEncoder_Decimal extends TrivialEncoder_Hex
{
    const
NAME     = 'Decimal';
    const
CODE     = 'dec';
    const
LINK     = 'http://en.wikipedia.org/wiki/Decimal';
    const
STRENGTH = STRENGTH_TRIVIAL;
    
    protected
$fstr      = '%03d';
    protected
$dstr      = '%d';
    protected
$chunksize = 3;

    public function
explain ()
    {
        return
wordwrap("Converts input from ASCII (base 256) to decimal (base 10). Triples message length. This is not a strong encoding, but can be used to force the message to be ASCII-safe, as all output will be numeric.");
    }
}

class
TrivialEncoder_Octal extends TrivialEncoder_Hex
{
    const
NAME     = 'Octal';
    const
CODE     = 'oct';
    const
LINK     = 'http://en.wikipedia.org/wiki/Octal';
    const
STRENGTH = STRENGTH_TRIVIAL;
    
    protected
$fstr      = '%03o';
    protected
$dstr      = '%o';
    protected
$chunksize = 3;

    public function
explain ()
    {
        return
wordwrap("Converts input from ASCII (base 256) to octal (base 8). Triples message length. This is not a strong encoding, but can be used to force the message to be ASCII-safe, as all output will be numeric.");
    }
}

class
TrivialEncoder_Binary extends TrivialEncoder_Hex
{
    const
NAME     = 'Binary';
    const
CODE     = 'bin';
    const
LINK     = 'http://en.wikipedia.org/wiki/Binary_numeral_system';
    const
STRENGTH = STRENGTH_TRIVIAL;
    
    protected
$fstr      = '%08b';
    protected
$dstr      = '%b';
    protected
$chunksize = 8;
    
    
/* sscanf($text, '%b') doesn't seem to work */
    
public function decode ($code)
    {    
        
$text = ''