<?php
/**
 * AES Cipher Library
 * Based on Federal Information Processing Standards Publication 197 - 26th November 2001
 * @author Marcin F. Wiœniowski <marcin.wisniowski@mfw.pl>
 * @version 1.0.5
 * @license http://opensource.org/licenses/lgpl-3.0.html The GNU Lesser General Public License
 * @package AES
 */
 
/**
 * AES Encryption Class. 
 * This class supports three lenghts of key (128, 192, 256)
 * @author Marcin F. Wiœniowski <marcin.wisniowski@mfw.pl>
 * @version 1.0.2
 * @license http://opensource.org/licenses/lgpl-3.0.html The GNU Lesser General Public License
 */
class AES {
/** @var int Number of rounds in AES algorithm (Nr)  */
	private $number_of_rounds;
/** @var int Number of columns (32-bit words) comprising the State. (Nb) */
	private $block_size;
/** @var int Number of 32-bit words comprising the Cipher Key  (Nk) */
	private $key_size;
/** @var array S-Box: Non-linear substitution table used in several byte substitution transformations */
	private $s_box;
/** @var array Inverted S-Box */
	private $s_box_inverted;
/** @var array Araay of [pow(2,x),{00},{00},{00}] values used as round constant value */
	private $round_constants;
/** @var array Logaritmic array used in Galois filed multiplication proccess */
	private $log;
/** @var array Expotencial array used in Galois filed multiplication proccess */
	private $exp;
/** @var array Array of collumn (words) */
	private $state;
	
	const AES128		= 0;
	const AES192		= 1;
	const AES256		= 2;
	
	const WORD_LENGTH 	= 4;
	const BYTE_LENGTH	= 8;
/**
 * Class constructor default construkts AES128 Objeckt
 * @param int Key strength default values is AES128. Also supports AES192 and AES256 values.
 */
	public function __construct($strength=self::AES128) {
		switch($strength) {
			case self::AES256:
				$this->key_size = 8;
                $this->block_size = 4;
				$this->number_of_rounds = 14;
				break;
			case self::AES192:
				$this->key_size = 6;
				$this->block_size = 4;
				$this->number_of_rounds = 12;
				break;
			case self::AES128:
			default:
				$this->key_size = 4;
				$this->block_size = 4;
				$this->number_of_rounds = 10;
				break;
		}
		$this->createSBox();
		$this->createInvertedSBox();
		$this->createRoundConstants();
		$this->createLogarithmicArray();
		$this->createExpotecialArray();
		$this->state = array();
	}
/**
 * Class destructor
 */ 
	public function __destruct() {}
/**
 * Definition of method which takes undefined methods of this Class
 */ 
	public function __call($name, $params) {
		throw new Exception("Undefined method call of AES class. Method name: ".$name);
	}
/**
 * Creates S-Box Table
 */
	private function createSBox() {
		$this->s_box = array(
		0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
		0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
		0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
		0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
		0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
		0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
		0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
		0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
		0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
		0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
		0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
		0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
		0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
		0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
		0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
		0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16);
	}
/**
 * Creates invertet S-Box Table
 */
	private function createInvertedSBox() {
		$this->s_box_inverted = array(
		0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,
		0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB,
		0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E,
		0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25,
		0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92,
		0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84,
		0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06,
		0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B,
		0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73,
		0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E,
		0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B,
		0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4,
		0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F,
		0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF,
		0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61,
		0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D);
	}
/**
 * Creates Round Contstants Table: Array of [pow(2,x),{00},{00},{00}] values used as round constant value 
 */
	private function createRoundConstants() {
		$this->round_constants = array(
		0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000, 0x20000000, 0x40000000, 0x80000000, 
		0x1B000000, 0x36000000, 0x6C000000, 0xD8000000, 0xAB000000, 0x4D000000, 0x9A000000, 0x2F000000,
		0x5E000000, 0xBC000000, 0x63000000, 0xC6000000, 0x97000000, 0x35000000, 0x6A000000, 0xD4000000,
		0xB3000000, 0x7D000000, 0xFA000000, 0xEF000000, 0xC5000000, 0x91000000);
	}
/**
 * Creates Logarithmic array with generator number 0xe5
 */
	private function createLogarithmicArray() {
		$this->log = array(
		0x00, 0xFF, 0xC8, 0x08, 0x91, 0x10, 0xD0, 0x36, 0x5A, 0x3E, 0xD8, 0x43, 0x99, 0x77, 0xFE, 0x18, 
		0x23, 0x20, 0x07, 0x70, 0xA1, 0x6C, 0x0C, 0x7F, 0x62, 0x8B, 0x40, 0x46, 0xC7, 0x4B, 0xE0, 0x0E, 
		0xEB, 0x16, 0xE8, 0xAD, 0xCF, 0xCD, 0x39, 0x53, 0x6A, 0x27, 0x35, 0x93, 0xD4, 0x4E, 0x48, 0xC3, 
		0x2B, 0x79, 0x54, 0x28, 0x09, 0x78, 0x0F, 0x21, 0x90, 0x87, 0x14, 0x2A, 0xA9, 0x9C, 0xD6, 0x74, 
		0xB4, 0x7C, 0xDE, 0xED, 0xB1, 0x86, 0x76, 0xA4, 0x98, 0xE2, 0x96, 0x8F, 0x02, 0x32, 0x1C, 0xC1, 
		0x33, 0xEE, 0xEF, 0x81, 0xFD, 0x30, 0x5C, 0x13, 0x9D, 0x29, 0x17, 0xC4, 0x11, 0x44, 0x8C, 0x80, 
		0xF3, 0x73, 0x42, 0x1E, 0x1D, 0xB5, 0xF0, 0x12, 0xD1, 0x5B, 0x41, 0xA2, 0xD7, 0x2C, 0xE9, 0xD5, 
		0x59, 0xCB, 0x50, 0xA8, 0xDC, 0xFC, 0xF2, 0x56, 0x72, 0xA6, 0x65, 0x2F, 0x9F, 0x9B, 0x3D, 0xBA, 
		0x7D, 0xC2, 0x45, 0x82, 0xA7, 0x57, 0xB6, 0xA3, 0x7A, 0x75, 0x4F, 0xAE, 0x3F, 0x37, 0x6D, 0x47, 
		0x61, 0xBE, 0xAB, 0xD3, 0x5F, 0xB0, 0x58, 0xAF, 0xCA, 0x5E, 0xFA, 0x85, 0xE4, 0x4D, 0x8A, 0x05, 
		0xFB, 0x60, 0xB7, 0x7B, 0xB8, 0x26, 0x4A, 0x67, 0xC6, 0x1A, 0xF8, 0x69, 0x25, 0xB3, 0xDB, 0xBD, 
		0x66, 0xDD, 0xF1, 0xD2, 0xDF, 0x03, 0x8D, 0x34, 0xD9, 0x92, 0x0D, 0x63, 0x55, 0xAA, 0x49, 0xEC, 
		0xBC, 0x95, 0x3C, 0x84, 0x0B, 0xF5, 0xE6, 0xE7, 0xE5, 0xAC, 0x7E, 0x6E, 0xB9, 0xF9, 0xDA, 0x8E, 
		0x9A, 0xC9, 0x24, 0xE1, 0x0A, 0x15, 0x6B, 0x3A, 0xA0, 0x51, 0xF4, 0xEA, 0xB2, 0x97, 0x9E, 0x5D, 
		0x22, 0x88, 0x94, 0xCE, 0x19, 0x01, 0x71, 0x4C, 0xA5, 0xE3, 0xC5, 0x31, 0xBB, 0xCC, 0x1F, 0x2D, 
		0x3B, 0x52, 0x6F, 0xF6, 0x2E, 0x89, 0xF7, 0xC0, 0x68, 0x1B, 0x64, 0x04, 0x06, 0xBF, 0x83, 0x38);
	}
/**
 * Creates Expotencial array with generator number 0xe5
 */	
	private function createExpotecialArray() {
		$this->exp = array(
		0x01, 0xe5, 0x4c, 0xb5, 0xfb, 0x9f, 0xfc, 0x12, 0x03, 0x34, 0xd4, 0xc4, 0x16, 0xba, 0x1f, 0x36, 
		0x05, 0x5c, 0x67, 0x57, 0x3a, 0xd5, 0x21, 0x5a, 0x0f, 0xe4, 0xa9, 0xf9, 0x4e, 0x64, 0x63, 0xee, 
		0x11, 0x37, 0xe0, 0x10, 0xd2, 0xac, 0xa5, 0x29, 0x33, 0x59, 0x3b, 0x30, 0x6d, 0xef, 0xf4, 0x7b, 
		0x55, 0xeb, 0x4d, 0x50, 0xb7, 0x2a, 0x07, 0x8d, 0xff, 0x26, 0xd7, 0xf0, 0xc2, 0x7e, 0x09, 0x8c, 
		0x1a, 0x6a, 0x62, 0x0b, 0x5d, 0x82, 0x1b, 0x8f, 0x2e, 0xbe, 0xa6, 0x1d, 0xe7, 0x9d, 0x2d, 0x8a, 
		0x72, 0xd9, 0xf1, 0x27, 0x32, 0xbc, 0x77, 0x85, 0x96, 0x70, 0x08, 0x69, 0x56, 0xdf, 0x99, 0x94, 
		0xa1, 0x90, 0x18, 0xbb, 0xfa, 0x7a, 0xb0, 0xa7, 0xf8, 0xab, 0x28, 0xd6, 0x15, 0x8e, 0xcb, 0xf2, 
		0x13, 0xe6, 0x78, 0x61, 0x3f, 0x89, 0x46, 0x0d, 0x35, 0x31, 0x88, 0xa3, 0x41, 0x80, 0xca, 0x17, 
		0x5f, 0x53, 0x83, 0xfe, 0xc3, 0x9b, 0x45, 0x39, 0xe1, 0xf5, 0x9e, 0x19, 0x5e, 0xb6, 0xcf, 0x4b, 
		0x38, 0x04, 0xb9, 0x2b, 0xe2, 0xc1, 0x4a, 0xdd, 0x48, 0x0c, 0xd0, 0x7d, 0x3d, 0x58, 0xde, 0x7c, 
		0xd8, 0x14, 0x6b, 0x87, 0x47, 0xe8, 0x79, 0x84, 0x73, 0x3c, 0xbd, 0x92, 0xc9, 0x23, 0x8b, 0x97, 
		0x95, 0x44, 0xdc, 0xad, 0x40, 0x65, 0x86, 0xa2, 0xa4, 0xcc, 0x7f, 0xec, 0xc0, 0xaf, 0x91, 0xfd, 
		0xf7, 0x4f, 0x81, 0x2f, 0x5b, 0xea, 0xa8, 0x1c, 0x02, 0xd1, 0x98, 0x71, 0xed, 0x25, 0xe3, 0x24, 
		0x06, 0x68, 0xb3, 0x93, 0x2c, 0x6f, 0x3e, 0x6c, 0x0a, 0xb8, 0xce, 0xae, 0x74, 0xb1, 0x42, 0xb4, 
		0x1e, 0xd3, 0x49, 0xe9, 0x9c, 0xc8, 0xc6, 0xc7, 0x22, 0x6e, 0xdb, 0x20, 0xbf, 0x43, 0x51, 0x52, 
		0x66, 0xb2, 0x76, 0x60, 0xda, 0xc5, 0xf3, 0xf6, 0xaa, 0xcd, 0x9a, 0xa0, 0x75, 0x54, 0x0e, 0x01);
	}
/**
 * Encrypts 16 bytes of data using AES algoritm
 * @param string Decrypted Content
 * @return string Encrypted Content
 */	
	public function encrypt($content, $key) {
		$expand_key = $this->keyExpansion($this->convertToWords($key));
		$this->state = $this->convertToWords($content);
		$this->addRoundKey($this->createRoundKey(0, $expand_key));
		for ($round = 1; $round < $this->number_of_rounds; $round++) {
			$this->subBytes();
			$this->shiftRows();
			$this->mixColumns();
			$this->addRoundKey($this->createRoundKey($round, $expand_key));
		}
		$this->subBytes();
		$this->shiftRows();
		$this->addRoundKey($this->createRoundKey($this->number_of_rounds, $expand_key));
		return $this->convertToHexString();
	}
/**
 * Decrypts encrypteta 16 bytes of data using AES algoritm
 * @param string Encrypted Content
 * @return string Decrypted Content
 */
	public function decrypt($content, $key) {
		$expand_key = $this->keyExpansion($this->convertToWords($key));
		$this->state = $this->convertToWords($content);
		$this->addRoundKey($this->createRoundKey($this->number_of_rounds, $expand_key));
		for ($round = $this->number_of_rounds - 1; $round > 0; $round--) {
			$this->invShiftRows();
			$this->invSubBytes();
			$this->addRoundKey($this->createRoundKey($round, $expand_key));
			$this->invMixColumns();
		}
		$this->invShiftRows();
		$this->invSubBytes();
		$this->addRoundKey($this->createRoundKey(0, $expand_key));
		return $this->convertToHexString();
	}
/**
 * Processes the State table using a nonlinear byte substitution table (S-box).
 */
	private function subBytes() {
		for ($i=0; $i < $this->block_size; $i++) {
			$this->state[$i] = $this->subWord($this->state[$i]);
		}
	}
/**
 * Inversion of subBytes() method
 * @see subBytes()
 */
	private function invSubBytes() {
		for ($i=0; $i < $this->block_size; $i++) {
			$this->state[$i] = $this->subWord($this->state[$i], true);
		}
	}
/**
 * Processes the State by cyclically shifting the last three rows of the State by different offsets.
 */
	private function shiftRows() {
		for ($i=1; $i < self::WORD_LENGTH; $i++) {
			$buffer = array();
			for ($j=$this->block_size-1; $j >= 0; $j--) {
				$pos = ($i + $j) % $this->block_size;
				$buffer[$j] = $this->getByteFromWord($this->state[$j], $i);
				$byte = isset($buffer[$pos]) ? $buffer[$pos] : $this->getByteFromWord($this->state[$pos], $i);
				$this->state[$j] = $this->putByteIntoWord( $byte, $this->state[$j], $i);
			}
		}
	}
/**
 * Inversion of shiftRows() method.
 * @see shiftRows()
 */
	private function invShiftRows() {
		for ($i=1; $i < self::WORD_LENGTH; $i++) {
			$buffer = array();
			for ($j=0; $j < $this->block_size; $j++) {
				$pos = ($i + $j) % $this->block_size;
				$buffer[$pos] = $this->getByteFromWord($this->state[$pos], $i);
				$byte = isset($buffer[$j]) ? $buffer[$j] : $this->getByteFromWord($this->state[$j], $i);
				$this->state[$pos] = $this->putByteIntoWord( $byte, $this->state[$pos], $i);
			}
		}
	}
/**
 * Takes all of the columns of theState and mixes their data to produce new columns.
 */
	private function mixColumns() {
		$mul1 = array();
		$mul2 = array();
		$mul3 = array();
		for ($i=0; $i < $this->block_size; $i++) {
			for ($j = 0; $j < self::WORD_LENGTH; $j++) {
				$mul1[$j] = $this->getByteFromWord($this->state[$i], $j);
				$mul2[$j] = $this->galoisFieldMultiplication($mul1[$j], 0x02);
				$mul3[$j] = $mul2[$j] ^ $mul1[$j];
			}
			for ($j = 0; $j < self::WORD_LENGTH; $j++) {
				$byte = $mul2[$j] ^ $mul1[($j+3) % $this->block_size] ^ $mul1[($j+2) % $this->block_size] ^ $mul3[($j+1) % $this->block_size];
				$this->state[$i] = $this->putByteIntoWord( $byte, $this->state[$i], $j);
			}
		}
	}
/**
 * Inversion of mixColumns()
 * @see mixColumns()
 */
	private function invMixColumns() {
		$mulE = array();
		$mulD = array();
		$mulB = array();
		$mul9 = array();
		for ($i=0; $i < $this->block_size; $i++) {
			for ($j = 0; $j < self::WORD_LENGTH; $j++) {
				$temp = $this->getByteFromWord($this->state[$i], $j);
				$mulE[$j] = $this->galoisFieldMultiplication($temp, 0x0E);
				$mulD[$j] = $this->galoisFieldMultiplication($temp, 0x0D);
				$mulB[$j] = $this->galoisFieldMultiplication($temp, 0x0B);
				$mul9[$j] = $this->galoisFieldMultiplication($temp, 0x09);
			}
			for ($j = 0; $j < self::WORD_LENGTH; $j++) {
				$byte = $mulE[$j] ^ $mul9[($j+3) % $this->block_size] ^ $mulD[($j+2) % $this->block_size] ^ $mulB[($j+1) % $this->block_size];
				$this->state[$i] = $this->putByteIntoWord( $byte, $this->state[$i], $j);
			}
		}
	}
/**
 * Adds Round Key to the State using an XOR operation.
 */
	private function addRoundKey($key) {
		for ($i=0; $i < $this->block_size; $i++) {
			$this->state[$i] ^= $key[$i];
		}
	}
/**
 * Generates Key schedule from Cipher Key
 * @return array Expanded key array
 */
	private function keyExpansion($key) {
		$expanded_key = array();
		for ($i=0; $i<$this->key_size; $i++) {
			$expanded_key[$i] = $key[$i];
		}
		for ($i=$this->key_size; $i<$this->block_size * ($this->number_of_rounds + 1); $i++) {
			$temp = $expanded_key[$i - 1];
			if ($i % $this->key_size == 0)
				$temp = $this->subWord($this->rotWord($temp)) ^ $this->round_constants[$i / $this->key_size -1];
			else if ($this->key_size > 6 && $i % $this->key_size == 4)
				$temp = $this->subWord($temp);
			$expanded_key[$i] = $expanded_key[$i - $this->key_size] ^ $temp;
		}
		return $expanded_key;
	}
/**
 * Creates array of 4 words from expanded key depend of round
 * @param int Number of round
 * @param array Expanded key array
 * @return array Round key.
 */
	private function createRoundKey($round, $key) {
		return array($key[self::WORD_LENGTH * $round], $key[self::WORD_LENGTH * $round + 1], $key[self::WORD_LENGTH * $round + 2], $key[self::WORD_LENGTH * $round + 3]);
	}
/**
 * Galios field multiplication function
 * @param int First byte
 * @param int Second byte
 * @return int Multiplication result
 */
	private function galoisFieldMultiplication($number_a, $number_b) {
		$temp = $this->exp[($this->log[$number_a] + $this->log[$number_b]) % 0xFF];
		$result = $number_a ? ($number_b ? $temp : 0) : 0;		
		return $result;
		/* // Old Implementanion
		$result = 0; 
		for ($i=0; $i<self::BYTE_LENGTH; $i++) {
			if ($number_b & 1) $result ^= $number_a;
			$number_a = ($number_a << 1) ^ (($number_a & 0x80) ? 0x1B : 0);
			$number_b >>= 1;
		}
		return $result; */
	}
/**
 * Changing value of each byte in word using SBOX table
 * @param word Input word
 * @param bool Inversion flag. Default value: false
 * @return word Result word of transformation
 */
	private function subWord($word, $invers=false) {
		if (!$invers)
			return 	$this->s_box[(( $word >> 24 ) & 0x000000FF)] << 24 |
					$this->s_box[(($word & 0x00FF0000) >> 16)] << 16 |
					$this->s_box[(($word & 0x0000FF00) >> 8 )] << 8 |
					$this->s_box[($word & 0x000000FF)];
		else
			return 	$this->s_box_inverted[(( $word >> 24 ) & 0x000000FF)] << 24 |
					$this->s_box_inverted[(($word & 0x00FF0000) >> 16)] << 16 |
					$this->s_box_inverted[(($word & 0x0000FF00) >> 8 )] << 8 |
					$this->s_box_inverted[($word & 0x000000FF)];
		
	}
/**
 * Clock-wise rotation of bytes in word [aa bb cc dd] -> [bb cc dd aa]
 * @param word Input word
 * @return word Result of transformation
 */
	private function rotWord($word) {
		return (( $word << 8 ) & 0xFFFFFFFF ) | (( $word >> 24 ) & 0x000000FF);
	}
/**
 * Get specyfic byte from word
 * @param word Input word
 * @param int Position of byte in word
 * @return byte Selected byte
 */
	private function getByteFromWord($word, $position) {
		switch ($position) {
			case 0:
				return ( $word >> 24 ) & 0x000000FF;
			case 1:
				return ($word & 0x00FF0000) >> 16;
			case 2:
				return ($word & 0x0000FF00) >> 8;
			case 3:
				return $word & 0x000000FF;
		}
	}
/**
 * Puts byte into word 
 * @param byte Byte
 * @param word Word
 * @param int Position of insertion
 * @return word Modified word
 */
	private function putByteIntoWord($byte, $word, $position) {
		switch ($position) {
			case 0:
				return (($word & 0x00FFFFFF) | (($byte << 24) & 0xFF000000));
			case 1:
				return (($word & 0xFF00FFFF) | (($byte << 16) & 0x00FF0000));
			case 2:
				return (($word & 0xFFFF00FF) | (($byte << 8) & 0x0000FF00));
			case 3:
				return (($word & 0xFFFFFF00) | $byte);
		}
	}
/**
 * Creates words array from hexadecimal string
 * @param string hexadecimal string
 * @return array words reprezentatnion of content
 */
	private function convertToWords($content) {
		$words = array();
		for ($i = 0; $i < strlen($content); $i+=2*$this->block_size){
			$words[$i/(2*$this->block_size)] = hexdec(substr($content, $i, 2*$this->block_size));
		}
		return $words;
	}
/**
 * Converts state array into hexadecimal string
 * @return string 16 bytes string representation of state array
 */
	private function convertToHexString() {
		$string = "";
		for ($i = 0; $i < $this->block_size; $i++) {
			$string .= $this->addZeros(dechex($this->state[$i]));
		}
		return $string;
	}
/**
 * Converts Strings to HEX representation
 * @param string Input string
 * @return string Hex representation
 */
	public function stringToHex($str) {
		$hex="";
		$zeros = "";
		$len = 2 * self::WORD_LENGTH * $this->block_size;
		for ($i = 0; $i < strlen($str); $i++){
			$val = dechex(ord($str{$i}));	    
			if( strlen($val)< 2 ) $val="0".$val;
			$hex.=$val;
		}
		for ($i = 0; $i < $len - strlen($hex); $i++){
			$zeros .= '0';
		}
		return $hex.$zeros;
	}
/**
 * Converts HEX values into strings
 * @param string HEX value in string repreentation
 * @return string 
 */ 
	public function hexToString($hex) {
		$str="";
		for($i=0; $i<strlen($hex); $i=$i+2 ) {
			$temp = hexdec(substr($hex, $i, 2));
			if (!$temp) continue;
			$str .= chr($temp);
		}
		return $str;
	}
/**
 * Add zeros in front od Hex string representation of single word
 * @return string Zeros string
 */
	private function addZeros($word) {
		$len = 2*self::WORD_LENGTH - strlen($word);
		$zeros = "";
		for ($i=0; $i < $len; $i++)
			$zeros .= '0';
		return $zeros.$word;
	}
/**
 * Self testing method
 */
	public function selfTest() {
		$content = '00112233445566778899aabbccddeeff';
		$password = 'Test password';
		$key = md5($password);
		print('Challenge content: 0x'.$content.'<br />');
		print('Password: "'.$password.'", key: 0x'.$key.'<br />');
		
		print('<br />Encryption process<br />');
		$start = microtime(true);
		$this->state = $this->convertToWords($content);
		$time1 = microtime(true);
		$expand_key = $this->keyExpansion($this->convertToWords($key));
		$time2 = microtime(true);
		print('Key expansion time: '.(($time2 - $time1)*1000).' ms<br />');
		$this->addRoundKey($this->createRoundKey(0, $expand_key));
		
		$time1 = microtime(true);
		$this->subBytes();
		$time2 = microtime(true);
		print('Sub bytes time: '.(($time2 - $time1)*1000).' ms<br />');
		
		$time1 = microtime(true);
		$this->shiftRows();
		$time2 = microtime(true);
		print('Shift rows time: '.(($time2 - $time1)*1000).' ms<br />');
		
		$time1 = microtime(true);
		$this->mixColumns();
		$time2 = microtime(true);
		print('Mix columns time: '.(($time2 - $time1)*1000).' ms<br />');
		
		$time1 = microtime(true);
		$this->addRoundKey($this->createRoundKey(1, $expand_key));
		$time2 = microtime(true);
		print('Add round key time: '.(($time2 - $time1)*1000).' ms<br />');
		for ($round = 2; $round < $this->number_of_rounds; $round++) {
			$this->subBytes();
			$this->shiftRows();
			$this->mixColumns();
			$this->addRoundKey($this->createRoundKey($round, $expand_key));
		}
		$this->subBytes();
		$this->shiftRows();
		$this->addRoundKey($this->createRoundKey($this->number_of_rounds, $expand_key));
		$end = microtime(true);
		$content = $this->convertToHexString();
		print('Encryption time: '.(($end-$start)*1000).' ms<br />Encrypted content: 0x'.$content.'<br />');
		
		print('<br />Decryption process<br />');
		
		$start = microtime(true);
		$this->state = $this->convertToWords($content);
		$time1 = microtime(true);
		$expand_key = $this->keyExpansion($this->convertToWords($key));
		$time2 = microtime(true);
		print('Key expansion time: '.(($time2 - $time1)*1000).' ms<br />');
		$this->addRoundKey($this->createRoundKey($this->number_of_rounds, $expand_key));
		
		$time1 = microtime(true);
		$this->invShiftRows();
		$time2 = microtime(true);
		print('Invers shift rows time: '.(($time2 - $time1)*1000).' ms<br />');
		
		$time1 = microtime(true);
		$this->invSubBytes();
		$time2 = microtime(true);
		print('Invers sub bytes time: '.(($time2 - $time1)*1000).' ms<br />');
		
		$time1 = microtime(true);
		$this->addRoundKey($this->createRoundKey($this->number_of_rounds -1, $expand_key));
		$time2 = microtime(true);
		print('Add round key time: '.(($time2 - $time1)*1000).' ms<br />');
		
		$time1 = microtime(true);
		$this->invMixColumns();
		$time2 = microtime(true);
		print('Invers mix columns time: '.(($time2 - $time1)*1000).' ms<br />');

		for ($round = $this->number_of_rounds - 2; $round > 0; $round--) {
			$this->invShiftRows();
			$this->invSubBytes();
			$this->addRoundKey($this->createRoundKey($round, $expand_key));
			$this->invMixColumns();
		}
		$this->invShiftRows();
		$this->invSubBytes();
		$this->addRoundKey($this->createRoundKey(0, $expand_key));
		$end = microtime(true);
		$content = $this->convertToHexString();
		print('Decryption time: '.(($end-$start)*1000).' ms<br />Decrypted content: 0x'.$content.'<br />');
		
	}
}
?>