Difference between revisions of "AES PHP Implementation"

From Second Life Wiki
Jump to navigation Jump to search
m (language tags to <source>)
 
(13 intermediate revisions by 2 users not shown)
Line 1: Line 1:
= Description =
= Description =
The following is a port of the [[AES LSL Implementation|LSL AES Engine]] by [[Haravikk Mistral]], allowing you to process Java and LSL compatible AES messages in PHP.
The following is a PHP class compatible with the [[AES LSL Implementation|LSL AES Engine]] by [[Haravikk Mistral]], allowing you to process Java and LSL compatible AES messages in PHP.


The Crypto class provided performs extremely well on most PHP set-ups, but the cost of including the file can vary with a number of factors. For this reason if you are only using AES encryption within a single script, you may consider entering the Crypto class directly into this script, rather than including it.
The Crypto class provided performs extremely well on most PHP set-ups, but the cost of including the file can vary with a number of factors. For this reason if you are only using AES encryption within a single script, you may consider entering the Crypto class directly into this script, rather than including it.
Line 6: Line 6:
'''NOTE''': The PHP implementation includes helper functions for handling strings, which are used in the examples, as the <code>base64_decode()</code> function provided by PHP will attempt to process null-characters produced as a result of the 6-bit to 8-bit conversion process. If you wish to process strings with null-characters at the end, then you will need to use <code>invertPadBase64CipherToString()</code> and call base64_decode yourself on the result.
'''NOTE''': The PHP implementation includes helper functions for handling strings, which are used in the examples, as the <code>base64_decode()</code> function provided by PHP will attempt to process null-characters produced as a result of the 6-bit to 8-bit conversion process. If you wish to process strings with null-characters at the end, then you will need to use <code>invertPadBase64CipherToString()</code> and call base64_decode yourself on the result.


= Class =
= Implementations =
<php><?php
== mcrypt ==
/*******************************************************************************
The following is the preferred PHP class but requires a PHP installation with the mcrypt module. It supports CBC and CFB modes, and the custom wrapper class implements all padding modes supported by the LSL implementation.
* PHP AES Implementation               *
********************************************************************************
* This is an alternate AES implementation for PHP which uses an almost  *
* direct port of the LSL implementation for Second Life which can be    *
*      found here: http://wiki.secondlife.com/wiki/AES_LSL_Implementation    *
*******************************************************************************/


<source lang="php"><?php
define('AES_MODE_CBC', 1);
define('AES_MODE_CBC', 1);
define('AES_MODE_CFB', 2);
define('AES_MODE_CFB', 2);
Line 25: Line 20:
define('AES_PAD_ZEROES', 132);
define('AES_PAD_ZEROES', 132);
define('AES_PAD_RANDOM', 133);
define('AES_PAD_RANDOM', 133);
define('AES_HEX_CHARS', '0123456789abcdef');
define('AES_BASE64_CHARS',
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/');


define('AES_DEFAULT_PAD', 512);
define('AES_DEFAULT_PAD', 512);


class Crypto {
class Crypto {
private static $SBOX = array(
0x637c777b, 0xf26b6fc5, 0x3001672b, 0xfed7ab76, 0xca82c97d,
0xfa5947f0, 0xadd4a2af, 0x9ca472c0, 0xb7fd9326, 0x363ff7cc,
0x34a5e5f1, 0x71d83115, 0x04c723c3, 0x1896059a, 0x071280e2,
0xeb27b275, 0x09832c1a, 0x1b6e5aa0, 0x523bd6b3, 0x29e32f84,
0x53d100ed, 0x20fcb15b, 0x6acbbe39, 0x4a4c58cf, 0xd0efaafb,
0x434d3385, 0x45f9027f, 0x503c9fa8, 0x51a3408f, 0x929d38f5,
0xbcb6da21, 0x10fff3d2, 0xcd0c13ec, 0x5f974417, 0xc4a77e3d,
0x645d1973, 0x60814fdc, 0x222a9088, 0x46eeb814, 0xde5e0bdb,
0xe0323a0a, 0x4906245c, 0xc2d3ac62, 0x9195e479, 0xe7c8376d,
0x8dd54ea9, 0x6c56f4ea, 0x657aae08, 0xba78252e, 0x1ca6b4c6,
0xe8dd741f, 0x4bbd8b8a, 0x703eb566, 0x4803f60e, 0x613557b9,
0x86c11d9e, 0xe1f89811, 0x69d98e94, 0x9b1e87e9, 0xce5528df,
0x8ca1890d, 0xbfe64268, 0x41992d0f, 0xb054bb16
);
private static $RSBOX = array(
0x52096ad5, 0x3036a538, 0xbf40a39e, 0x81f3d7fb, 0x7ce33982,
0x9b2fff87, 0x348e4344, 0xc4dee9cb, 0x547b9432, 0xa6c2233d,
0xee4c950b, 0x42fac34e, 0x082ea166, 0x28d924b2, 0x765ba249,
0x6d8bd125, 0x72f8f664, 0x86689816, 0xd4a45ccc, 0x5d65b692,
0x6c704850, 0xfdedb9da, 0x5e154657, 0xa78d9d84, 0x90d8ab00,
0x8cbcd30a, 0xf7e45805, 0xb8b34506, 0xd02c1e8f, 0xca3f0f02,
0xc1afbd03, 0x01138a6b, 0x3a911141, 0x4f67dcea, 0x97f2cfce,
0xf0b4e673, 0x96ac7422, 0xe7ad3585, 0xe2f937e8, 0x1c75df6e,
0x47f11a71, 0x1d29c589, 0x6fb7620e, 0xaa18be1b, 0xfc563e4b,
0xc6d27920, 0x9adbc0fe, 0x78cd5af4, 0x1fdda833, 0x8807c731,
0xb1121059, 0x2780ec5f, 0x60517fa9, 0x19b54a0d, 0x2de57a9f,
0x93c99cef, 0xa0e03b4d, 0xae2af5b0, 0xc8ebbb3c, 0x83539961,
0x172b047e, 0xba77d626, 0xe1691463, 0x55210c7d
);
private static function getSBoxByte($n) {
$v = self::$SBOX[$n / 4];
return ($v >> ((3 - ($n % 4)) * 8)) & 0xFF;
}
private static function getSBoxInvertedByte($n) {
$v = self::$RSBOX[$n / 4];
return ($v >> ((3 - ($n % 4)) * 8)) & 0xFF;
}
public static $NULL_BLOCK = array(0, 0, 0, 0);
public static $NULL_STATE = array(
array(0, 0, 0, 0),
array(0, 0, 0, 0),
array(0, 0, 0, 0),
array(0, 0, 0, 0)
);
private $mode = AES_MODE_CBC;
private $mode = AES_MODE_CBC;
private $modeHandle = null;
private $padType = AES_PAD_RBT;
private $padType = AES_PAD_RBT;
private $padSize = AES_DEFAULT_PAD;
private $padSize = AES_DEFAULT_PAD;


private $roundKey = array();
private $iv;
private $rounds = 0;
private $key;
 
private $iv = array();
private $module;


public function Crypto($mode=0, $padType=0, $padSize=0) {
public function Crypto($mode=0, $padType=0, $padSize=0) {
Line 99: Line 38:
$this->padType = $padType;
$this->padType = $padType;


if (($mode >= 1) && ($mode <= 2)) {
if (($mode >= 1) || ($mode <= 2)) $this->mode = $mode;
$this->mode = $mode;
 
$mcryptMode = 'cbc';
if ($this->mode == AES_MODE_CBC)  
if ($this->mode == AES_MODE_CFB) $mcryptMode = 'ncfb';
$this->modeHandle = new CryptoAESCBC();
else $this->modeHandle = new CryptoAESCFB();
$this->module = mcrypt_module_open('rijndael-128', '', $mcryptMode, '');
}
}
public function __destruct() {
mcrypt_module_close($this->module);
}
}


public function setHexKey(&$key) {
public function setHexKey(&$key) {
return $this->keyExpansion(self::hexToBytes($key));
$this->key = self::hexToBinary($key);
}
}


public function setBase64Key(&$key) {
public function setBase64Key(&$key) {
return $this->keyExpansion(self::base64ToBytes($key));
$this->key = self::base64ToBinary($key);
}
}


public function setHexIV(&$iv) {
public function setHexIV(&$iv) {
$iv = self::hexToBytes($iv);
$this->iv = self::hexToBinary($iv);
if (is_array($iv) && (count($iv) > 4))
$this->iv = array_slice($iv, 1);
else $this->iv = self::$NULL_BLOCK;
}
}


public function padHexCipherToHex(&$cipher) {
public function padHexCipherToHex(&$cipher) {
return self::bytesToHex(
return self::binaryToHex(
$this->padCipher(self::hexToBytes($cipher)));
$this->padCipher(self::hexToBinary($cipher)));
}
}


public function padHexCipherToBase64(&$cipher) {
public function padHexCipherToBase64(&$cipher) {
return self::bytesToBase64(
return self::binaryToBase64(
$this->padCipher(self::hexToBytes($cipher)));
$this->padCipher(self::hexToBinary($cipher)));
}
}


public function padStringCipherToBase64(&$cipher) {
public function padStringCipherToBase64(&$cipher) {
return self::bytesToBase64(
return self::binaryToBase64($this->padCipher($cipher));
$this->padCipher(
self::base64ToBytes(base64_encode($cipher))));
}
}


public function invertPadBase64CipherToString(&$cipher) {
public function invertPadBase64CipherToString(&$cipher) {
$s = self::bytesToBase64(
return $this->invertPadCipher(self::base64ToBinary($cipher));
$this->invertPadCipher(self::base64ToBytes($cipher)));
 
if ($this->padType != AES_PAD_NULLS_SAFE) {
// Trim null-characters (A's), since php's base64
// functions don't trim them like they should.
$l = $o = mb_strlen($s);
while (($l) >= 0) {
$c = mb_substr($s, $l - 1, 1);
$e = ($c == '=');
if ($e) --$o; // ignore pad characters
 
if (($c != 'A') && !$e) break;
--$l;
}
 
if ($l != $o) {
$s = mb_substr($s, 0, $l);
 
$l %= 4;
if ($l) {
if ($l == 2) $s .= '==';
else if ($l == 1) $s .= '=';
}
}
}
 
return base64_decode($s);
}
}


public function padBase64CipherToHex(&$cipher) {
public function padBase64CipherToHex(&$cipher) {
return self::bytesToHex(
return self::binaryToHex(
$this->padCipher(self::base64ToBytes($cipher)));
$this->padCipher(self::base64ToBinary($cipher)));
}
}


public function padBase64CipherToBase64(&$cipher) {
public function padBase64CipherToBase64(&$cipher) {
return self::bytesToBase64(
return self::binaryToBase64(
$this->padCipher(self::base64ToBytes($cipher)));
$this->padCipher(self::base64ToBinary($cipher)));
}
}


private function padCipher(&$data) {
private function padCipher(&$data) {
$bits = $data[0];
$data = array_slice($data, 1);
$count = count($data);
$padding = $this->padType;
$padding = $this->padType;
if ($padding == AES_PAD_NONE) {
if ($padding == AES_PAD_NONE) {
if ($this->mode == AES_MODE_CFB)  
if ($this->mode == AES_MODE_CFB)  
return array_merge(
return $this->cipher($data);
(array)$bits, $this->cipher($data)
);
$padding = AES_PAD_RBT;
$padding = AES_PAD_RBT;
}
}
Line 197: Line 101:
if ($padding == AES_PAD_RBT) $blockSize = 128;
if ($padding == AES_PAD_RBT) $blockSize = 128;


$bytes  = mb_strlen($data, '8bit');
$bits = $bytes << 3;
$blocks = (integer)($bits / $blockSize);
$blocks = (integer)($bits / $blockSize);
$extra  = $bits % $blockSize;
$extra  = $bits % $blockSize;


if ($padding == AES_PAD_RBT) {
if ($padding == AES_PAD_RBT) {
$final = array();
if ($extra > 0) {
if ($extra > 0) {
$words = $extra >> 5;
$ebytes = $extra >> 3;
if (($words << 5) < $extra) ++$words;
if (($ebytes << 3) < $extra) ++$ebytes;


$pos = $count - $words;
$pos = $bytes - $ebytes;
$t = array_slice($data, $pos, $words);
$t = mb_substr($data, $pos, $ebytes, '8bit');
 
$lb = array();
if ($blocks < 1) {
if ($blocks < 1) {
$data = array();
$data = '';
$lb = $this->cipher($this->cipher($iv));
$lb = $this->cipher($this->cipher($iv));
} else {
} else {
$data = $this->cipher(
$data = $this->cipher(
array_slice($data, 0, $pos)
mb_substr($data, 0, $pos, '8bit')
);
);


$lb = $this->cipher(
$lb = $this->cipher(
array_slice($data, $pos - 4, 4)
mb_substr($data, $pos - 16, 16, '8bit')
);
);
}
}


$l = count($t);
for ($i = 0; $i < $ebytes; ++$i)  
for ($i = 0; $i < $l; ++$i)  
$data .= $t{$i} ^ $lb{$i};
$final[] = $t[$i] ^ $lb[$i];


return array_merge((array)$bits, $data, $final);
return $data;
}
}
return array_merge((array)$bits, $this->cipher($data));
 
return $this->cipher($data);
} else {
} else {
$extra = $blockSize - $extra;
$extra = $blockSize - $extra;
Line 235: Line 139:
if ($padding == AES_PAD_NULLS_SAFE) {
if ($padding == AES_PAD_NULLS_SAFE) {
++$bits;
++$bits;
$words = $bits >> 5;
$data .= "\x80";
$bit  = $bits % 32;
 
if ($words < count($data))
$data[$words] |= (1 << (32 - $bit));
else $data[] = 0x80000000;


if ((--$extra) < 0) $extra += $blockSize;
if ((--$extra) < 0) $extra += $blockSize;
Line 246: Line 145:
}
}


$bytes = $extra >> 3;
$ebytes = $extra >> 3;
if ($bytes <= 0) {
if ($ebytes <= 0) {
if ($padding == AES_PAD_NULLS)  
if ($padding == AES_PAD_NULLS)  
return array_merge(
return $this->cipher($data);
(array)$bits,
$this->cipher($data)
);


$bytes = $blockSize >> 3;
$ebytes = $blockSize >> 3;
$extra += $blockSize;
$extra += $blockSize;
}
}


$bits += $extra;
$bits += $extra;
$words = $bytes >> 2;
$extra = $bytes % 4;


if ($extra > 0) {
if ($ebytes > 0) {
$i = 0;
$final = -1;
$index = count($data) - 1;
if ($padding != AES_PAD_NULLS)  
$v = $data[$index];
$final = $ebytes - 1;
 
if (($extra == $bytes) &&
($padding != AES_PAD_NULLS)) {
$v |= $bytes;
$i = 1;
}


$byte = 0;
$byte = 0;
for (; $i < $extra; ++$i) {
for ($i = 0; $i < $ebytes; ++$i) {
if ($padding == AES_PAD_RANDOM) {
if (($padding != AES_PAD_NULLS) &&
($i == $final))
$byte = $ebytes;
else if ($padding == AES_PAD_RANDOM)  
$byte = mt_rand(0, 255);
$byte = mt_rand(0, 255);
$v |= ($byte << ($i << 3));
$data .= pack('C', $byte);
}
}
 
$data[$index] = $v;
}
 
$w = array();
if ($words > 0) {
$final = -1;
if ($padding != AES_PAD_NULLS)
$final = $words - 1;
 
$word = 0; $byte = 0;
for ($i = 0; $i < $words; ++$i) {
$word = 0;
for ($j = 0; $j < 4; ++$j) {
if (($padding != AES_PAD_NULLS) &&
($i == $final) && !$j)
$byte = $bytes;
else if ($padding == AES_PAD_RANDOM)
$byte = mt_rand(0, 255);
 
$word |= ($byte << ($j << 3));
}
 
$w[] = $word;
}
}
$data = array_merge($data, $w);
}
}


return array_merge((array)$bits, $this->cipher($data));
return $this->cipher($data);
}
}
}
}


public function invertPadHexCipherToHex(&$cipher) {
public function invertPadHexCipherToHex(&$cipher) {
return self::bytesToHex($this->invertPadCipher(self::hexToBytes($cipher)));
return self::binaryToHex($this->invertPadCipher(self::hexToBinary($cipher)));
}
}


public function invertPadHexCipherToBase64(&$cipher) {
public function invertPadHexCipherToBase64(&$cipher) {
return self::bytesToBase64($this->invertPadCipher(self::hexToBytes($cipher)));
return self::binaryToBase64($this->invertPadCipher(self::hexToBinary($cipher)));
}
}


public function invertPadBase64CipherToHex(&$cipher) {
public function invertPadBase64CipherToHex(&$cipher) {
return self::bytesToHex($this->invertPadCipher(self::base64ToBytes($cipher)));
return self::binaryToHex($this->invertPadCipher(self::base64ToBinary($cipher)));
}
}


public function invertPadBase64CipherToBase64(&$cipher) {
public function invertPadBase64CipherToBase64(&$cipher) {
return self::bytesToBase64($this->invertPadCipher(self::base64ToBytes($cipher)));
return self::binaryToBase64($this->invertPadCipher(self::base64ToBinary($cipher)));
}
}


private function invertPadCipher(&$data) {
private function invertPadCipher(&$data) {
$bits = $data[0];
$data = array_slice($data, 1);
$padding = $this->padType;
$padding = $this->padType;
if ($padding == AES_PAD_NONE) {
if ($padding == AES_PAD_NONE) {
if ($this->mode == AES_MODE_CFB)  
if ($this->mode == AES_MODE_CFB)  
return array_merge(
return $this->invertCipher($data);
(array)$bits, $this->invertCipher($data)
);
$padding = AES_PAD_RBT;
$padding = AES_PAD_RBT;
}
}
Line 344: Line 202:
$blockSize = $this->padSize;
$blockSize = $this->padSize;
if ($padding == AES_PAD_RBT) $blockSize = 128;
if ($padding == AES_PAD_RBT) $blockSize = 128;
 
$bytes  = mb_strlen($data, '8bit');
$bits = $bytes << 3;
$blocks = (integer)($bits / $blockSize);
$blocks = (integer)($bits / $blockSize);
$extra = $bits % $blockSize;
$extra = $bits % $blockSize;


if ($padding == AES_PAD_RBT) {
if ($padding == AES_PAD_RBT) {
$final = array();
if ($extra > 0) {
if ($extra > 0) {
$words = $extra >> 5;
$ebytes = $extra >> 3;
if (($words << 5) < $extra) ++$words;
if (($ebytes << 3) < $extra) ++$ebytes;


$pos = $count - $words;
$pos = $bytes - $ebytes;
$t = array_slice($data, $pos, $words);
$t = mb_substr($data, $pos, $ebytes, '8bit');


$lb = array();
if ($blocks < 1)  
if ($blocks < 1)  
$lb = $this->cipher(
$lb = $this->cipher(
Line 363: Line 221:
);
);
else {
else {
$start = $count - (4 + $words);
$start = $bytes - (16 + $ebytes);
$lb = $this->cipher(
$lb = $this->cipher(
array_slice(
mb_substr($data, $start, 16, '8bit')
$data,  
$start,  
4
)
);
);


$data = $this->invertCipher(
$data = $this->invertCipher(
array_slice($data, 0, $pos)
mb_substr($data, 0, $pos, '8bit')
);
);
}
}


$l = count($t);
for ($i = 0; $i < $ebytes; ++$i)  
for ($i = 0; $i < $l; ++$i)  
$data .= $t{$i} ^ $lb{$i};
$final[] = $t[$i] ^ $lb[$i];
return $data;
}
}
 
return array_merge((array)$bits, $data, $final);
return $this->invertCipher($data);
} else {
} else {
if ($extra > 0) {
if ($extra > 0) {
$bits -= $extra;
$bits -= $extra;


$e = $extra >> 5;
$ebytes = $extra >> 3;
if (($e << 5) < $extra) ++$e;
if (($e << 3) < $extra) ++$ebytes;
$extra = $e;
$extra = $ebytes;


if ($extra <= 0) $extra = 1;
if ($extra <= 0) $extra = 1;
$count = count($data);
if ($extra > $bytes) return "\0";


$count = count($data);
$data = mb_substr($data, 0, $bytes - $extra, '8bit');
if ($extra > $count) return (array)0;
 
$data = array_slice($data, 0, $count - $extra);
}
}


$data = $this->invertCipher($data);
$data = $this->invertCipher($data);


$count = count($data);
$ebytes = 0; $excessBits = 0;
$bytes = 0; $words = 0; $excessBits = 0;


if (($padding == AES_PAD_NULLS) ||  
if (($padding == AES_PAD_NULLS) ||  
Line 408: Line 262:
$v = 0; $continue = true;
$v = 0; $continue = true;


while ($continue && ($words < $count)) {
while ($byte = ($data{$index = ($bytes - ($ebytes + 1))}) == "\0")  
$v = $data[$count - ($words + 1)];
++$ebytes;
 
if ($v == 0) {
++$words;
$bytes += 4;
} else {
for ($j = 0; $j < 4; ++$j) {
$byte = ($v >> ($j << 3)) &
0xFF;
 
if ($byte == 0) ++$bytes;
else {
$continue = false;
break;
}
}
}
}


if ($padding == AES_PAD_NULLS_SAFE) {
if ($padding == AES_PAD_NULLS_SAFE) {
Line 432: Line 269:
while ($i < 0xFF) {
while ($i < 0xFF) {
++$excessBits;
++$excessBits;
if ($byte & $i) break;
if ($byte & $i) {
$byte ^= $i;
break;
}
$i <<= 1;
$i <<= 1;
}
}
$data{$index} = $byte;
}
}
} else {
} else {
$bytes = $data[$count - 1] & 0xFF;
$ebytes = $data{$bytes - 1} & 0xFF;
if (($bytes << 3) >= $bits) return (array)0;
if (($ebytes << 3) >= $bits) return "\0";
$words = $bytes >> 2;
}
}


if ($words > 0)  
if ($ebytes > 0)  
$data = array_slice($data, 0, $count - $words);
$data = mb_substr($data, 0, $bytes - $ebytes);


$bits -= ($bytes << 3) + $excessBits;
$bits -= ($ebytes << 3) + $excessBits;


return array_merge((array)$bits, $data);
return $data;
}
}
}
}


public function performInvertCipher(&$state) {
private function invertCipher($data) {
$this->addRoundKey($state, $this->rounds);
if ($data == "") return "";
$j = $this->rounds - 1;
mcrypt_generic_init($this->module, $this->key, $this->iv);
while ($j > 0) {
$decrypted = mdecrypt_generic($this->module, $data);
$this->invertShiftRows($state);
mcrypt_generic_deinit($this->module);
$this->invertSubBytes($state);
return $decrypted;
$this->addRoundKey($state, $j--);
}
$this->invertMixColumns($state);
}


$this->invertShiftRows($state);
private function cipher($data) {
$this->invertSubBytes($state);
if ($data == "") return "";
$this->addRoundKey($state, 0);
mcrypt_generic_init($this->module, $this->key, $this->iv);
$cipherText = mcrypt_generic($this->module, $data);
mcrypt_generic_deinit($this->module);
return $cipherText;
}
}


private function invertCipher($data) {
private static function base64ToBinary(&$base64Data) {
return $this->modeHandle->invertCipher($this, $data);
return base64_decode($base64Data);
}
}


public function performCipher(&$state) {
private static function hexToBinary(&$hexData) {
$this->addRoundKey($state, 0);
return pack('H*', $hexData);
 
}
for ($j = 1; $j < $this->rounds; ++$j) {
$this->subBytes($state);
$this->shiftRows($state);
$this->mixColumns($state);
$this->addRoundKey($state, $j);
}


$this->subBytes($state);
private static function binaryToBase64(&$binary) {
$this->shiftRows($state);
return base64_encode($binary);
$this->addRoundKey($state, $this->rounds);
}
}


private function cipher($data) {
private static function binaryToHex(&$binary) {
return $this->modeHandle->cipher($this, $data);
$unpacked = unpack('H*hexchars', $binary);
return '0x'.implode('', $unpacked['hexchars']);
}
}
}


private function keyExpansion(&$key) {
?></source>
if (!is_array($key)) return false;


$count = count($key);
== phpseclib ==
if (($count <= 4) || ($count > 9)) return false;
The following class follows the same structure as the one above and implements the same padding schemes, but provides an alternative implementation using [http://phpseclib.sourceforge.net/ phpseclib]. This implementation only supports CBC (the currently recommended mode of operation), but phpseclib has the advantage that it can be installed on hosts that do not have mcrypt (though it will support mcrypt when it is available), so it allows you to produce more portable code.


$len = --$count;
Thanks to [[User:John Avium|John Avium]] for recommending the use of this library.
$this->roundKey = array_slice($key, 1);


$this->rounds = $len + 6;
<source lang="php"><?php
$x = ($len * 3) + 28;
include_once 'phpseclib/Crypt/AES.php';


$i = 0;
define('AES_MODE_CBC', 1);
$t = $this->roundKey[$len - 1];


do {
define('AES_PAD_NONE', 128);
if (!($i % $len))  
define('AES_PAD_RBT', 129);
$t = (((self::getSBoxByte(($t >> 16) & 0xFF) ^
define('AES_PAD_NULLS', 130);
  (0x000D8080 >> (7 ^ ($i / $len)))) & 0xFF) << 24) |
define('AES_PAD_NULLS_SAFE', 131);
      (self::getSBoxByte(($t >>  8) & 0xFF) << 16) |
define('AES_PAD_ZEROES', 132);
      (self::getSBoxByte(($t      ) & 0xFF) <<  8) |
define('AES_PAD_RANDOM', 133);
      (self::getSBoxByte(($t >> 24) & 0xFF)      );
else if (($len > 6) && (($i % $len) == 4))
$t = ((self::getSBoxByte(($t >> 24) & 0xFF) << 24) |
      (self::getSBoxByte(($t >> 16) & 0xFF) << 16) |
      (self::getSBoxByte(($t >>  8) & 0xFF) <<  8) |
      (self::getSBoxByte(($t      ) & 0xFF)      ));


$this->roundKey[] = ($t = ($t ^ $this->roundKey[$i]) & 0xFFFFFFFF);
define('AES_DEFAULT_PAD', 512);
} while ((++$i) < $x);


return true;
class Crypto {
}
private $mode = AES_MODE_CBC;
private $padType = AES_PAD_RBT;
private $padSize = AES_DEFAULT_PAD;


private function addRoundKey(&$state, $round) {
private $iv;
$round <<= 2;
private $key;
private $changed = true;
private $module;


for ($i = 0; $i < 4; ++$i, ++$round) {
public function Crypto($mode=0, $padType=0, $padSize=0) {
$state[$i][0] ^= (($this->roundKey[$round] >> 24) & 0xFF);
if ($padSize >= 128) $this->padSize = $padSize;
$state[$i][1] ^= (($this->roundKey[$round] >> 16) & 0xFF);
if (($padType >= 128) && ($padType <= 133))  
$state[$i][2] ^= (($this->roundKey[$round] >>  8) & 0xFF);
$this->padType = $padType;
$state[$i][3] ^= (($this->roundKey[$round]      ) & 0xFF);
 
}
// Re-add this if phpseclib ever adds more modes of operation
//if (($mode >= 1) || ($mode <= 2)) $this->mode = $mode;
$this->module = new Crypt_AES(CRYPT_AES_MODE_CBC);
$this->module->disablePadding();
}
}


private function subBytes(&$state) {
public function setHexKey(&$key) {
for ($i = 0; $i < 4; ++$i)  
$this->key = self::hexToBinary($key);
for ($j = 0; $j < 4; ++$j)
$this->changed = true;
$state[$i][$j] = self::getSBoxByte($state[$i][$j]);
}
}


private function invertSubBytes(&$state) {
public function setBase64Key(&$key) {
for ($i = 0; $i < 4; ++$i)  
$this->key = self::base64ToBinary($key);
for ($j = 0; $j < 4; ++$j)
$this->changed = true;
$state[$i][$j] = self::getSBoxInvertedByte($state[$i][$j]);
}
}


private function shiftRows(&$state) {
public function setHexIV(&$iv) {
$t = $state[0][1];
$this->iv = self::hexToBinary($iv);
$state[0][1] = $state[1][1];
$this->changed = true;
$state[1][1] = $state[2][1];
$state[2][1] = $state[3][1];
$state[3][1] = $t;
 
$t = $state[0][2];
$state[0][2] = $state[2][2];
$state[2][2] = $t;
 
$t = $state[1][2];
$state[1][2] = $state[3][2];
$state[3][2] = $t;
 
$t = $state[0][3];
$state[0][3] = $state[3][3];
$state[3][3] = $state[2][3];
$state[2][3] = $state[1][3];
$state[1][3] = $t;
}
}


private function invertShiftRows(&$state) {
public function padHexCipherToHex(&$cipher) {
$t = $state[3][1];
return self::binaryToHex(
$state[3][1] = $state[2][1];
$this->padCipher(self::hexToBinary($cipher)));
$state[2][1] = $state[1][1];
$state[1][1] = $state[0][1];
$state[0][1] = $t;
 
$t = $state[0][2];
$state[0][2] = $state[2][2];
$state[2][2] = $t;
 
$t = $state[1][2];
$state[1][2] = $state[3][2];
$state[3][2] = $t;
 
$t = $state[0][3];
$state[0][3] = $state[1][3];
$state[1][3] = $state[2][3];
$state[2][3] = $state[3][3];
$state[3][3] = $t;
}
}


 
public function padHexCipherToBase64(&$cipher) {
private function xTimes($x) {
return self::binaryToBase64(
return (($x << 1) ^ ((($x >> 7) & 1) * 0x1b)) & 0xFF;
$this->padCipher(self::hexToBinary($cipher)));
}
}
 
private function multiply($x, $y) {
public function padStringCipherToBase64(&$cipher) {
$xT = $this->xTimes($x);
return self::binaryToBase64($this->padCipher($cipher));
$xT2 = $this->xTimes($xT);
$xT3 = $this->xTimes($xT2);
 
return ((($y & 1) * $x) ^ ((($y >> 1) & 1) * $xT) ^
        ((($y >> 2) & 1) * $xT2) ^ ((($y >> 3) & 1) * $xT3) ^
        ((($y >> 4) & 1) * $this->xTimes($xT3))) & 0xFF;
}
}


private function mixColumns(&$state) {
public function invertPadBase64CipherToString(&$cipher) {
for ($i = 0; $i < 4; ++$i) {
return $this->invertPadCipher(self::base64ToBinary($cipher));
$t = $state[$i][0];
$t1 = $state[$i][0] ^ $state[$i][1] ^ $state[$i][2] ^ $state[$i][3];
 
for ($j = 0; $j < 3; ++$j)
$state[$i][$j] ^=
$this->xTimes($state[$i][$j] ^ $state[$i][$j + 1]) ^ $t1;
 
$state[$i][3] ^= $this->xTimes($state[$i][3] ^ $t) ^ $t1;
}
}
}


private function invertMixColumns(&$state) {
public function padBase64CipherToHex(&$cipher) {
for ($i = 0; $i < 4; ++$i) {
return self::binaryToHex(
$a = $state[$i][0];
$this->padCipher(self::base64ToBinary($cipher)));
$b = $state[$i][1];
$c = $state[$i][2];
$d = $state[$i][3];
 
$state[$i][0] = $this->multiply($a, 0x0e) ^ $this->multiply($b, 0x0b) ^
                $this->multiply($c, 0x0d) ^ $this->multiply($d, 0x09);
$state[$i][1] = $this->multiply($a, 0x09) ^ $this->multiply($b, 0x0e) ^
                $this->multiply($c, 0x0b) ^ $this->multiply($d, 0x0d);
$state[$i][2] = $this->multiply($a, 0x0d) ^ $this->multiply($b, 0x09) ^
                $this->multiply($c, 0x0e) ^ $this->multiply($d, 0x0b);
$state[$i][3] = $this->multiply($a, 0x0b) ^ $this->multiply($b, 0x0d) ^
                $this->multiply($c, 0x09) ^ $this->multiply($d, 0x0e);
}
}
}


private static function base64ToBytes(&$base64Data) {
public function padBase64CipherToBase64(&$cipher) {
$x = mb_strpos($base64Data, '=');
return self::binaryToBase64(
if ($x > 0) $base64Data = mb_substr($base64Data, 0, $x);
$this->padCipher(self::base64ToBinary($cipher)));
return self::stringToBytes($base64Data, 6, AES_BASE64_CHARS);
}
}


private static function hexToBytes(&$hexData) {
private function padCipher(&$data) {
$hexData = mb_strtolower($hexData);
$padding = $this->padType;
if (mb_substr($hexData, 0, 1) == '0x')  
if ($padding == AES_PAD_NONE) {
$hexData = mb_substr($hexData, 2);
if ($this->mode == AES_MODE_CFB)
return self::stringToBytes($hexData, 4, AES_HEX_CHARS);
return $this->cipher($data);
}
$padding = AES_PAD_RBT;
}


private static function stringToBytes(&$string, $width, $alphabet) {
$blockSize = $this->padSize;
$l = mb_strlen($string);
if ($padding == AES_PAD_RBT) $blockSize = 128;


$n = (array)($l * $width);
$bytes  = mb_strlen($data, '8bit');
$bitbuf = 0;
$bits = $bytes << 3;
$adjust = 32;
$blocks = (integer)($bits / $blockSize);
$extra  = $bits % $blockSize;


for ($i = 0; $i < $l; ++$i) {
if ($padding == AES_PAD_RBT) {
$val = mb_strpos($alphabet, mb_substr($string, $i, 1));
if ($extra > 0) {
if ($val === false) return "Invalid character at index $i";
$ebytes = $extra >> 3;
if (($ebytes << 3) < $extra) ++$ebytes;


if (($adjust -= $width) <= 0) {
$pos = $bytes - $ebytes;
$bitbuf = ($bitbuf | ($val >> -$adjust)) & 0xFFFFFFFF;
$t = mb_substr($data, $pos, $ebytes, '8bit');
$n[] = $bitbuf;
if ($blocks < 1) {
$data = '';
$lb = $this->cipher($this->cipher($iv));
} else {
$data = $this->cipher(
mb_substr($data, 0, $pos, '8bit')
);


$adjust += 32;
$lb = $this->cipher(
if ($adjust < 32) $bitbuf = ($val << $adjust) & 0xFFFFFFFF;
mb_substr($data, $pos - 16, 16, '8bit')
else $bitbuf = 0;
);
} else $bitbuf = ($bitbuf | ($val << $adjust)) & 0xFFFFFFFF;
}
}


if ($adjust < 32) $n[] = $bitbuf;
for ($i = 0; $i < $ebytes; ++$i)  
$data .= $t{$i} ^ $lb{$i};


return $n;
return $data;
}
}


private static function bytesToBase64(&$bytes) {
return $this->cipher($data);
$s = self::bytesToString($bytes, 6, AES_BASE64_CHARS);
} else {
$extra = $blockSize - $extra;


$l = mb_strlen($s) % 4;
if ($padding == AES_PAD_NULLS_SAFE) {
if ($l) {
++$bits;
if ($l == 2) $s .= '==';
$data .= "\x80";
else if ($l == 1) $s .= '=';
}
return $s;
}


private static function bytesToHex(&$bytes) {
if ((--$extra) < 0) $extra += $blockSize;
return '0x' . self::bytesToString($bytes, 4, AES_HEX_CHARS);
$padding = AES_PAD_NULLS;
}
}


private static function bytesToString(&$bytes, $width, $alphabet) {
$ebytes = $extra >> 3;
$bits = $bytes[0];
if ($ebytes <= 0) {
if ($padding == AES_PAD_NULLS)
return $this->cipher($data);


$mask = ~(-1 << $width);
$ebytes = $blockSize >> 3;
$shift = 32 - $width;
$extra += $blockSize;
}


$available = $prev = $i = 0;
$bits += $extra;
$s = '';
while (($bits -= 32) > -32) {
$available += 32 + ($bits * (0 > $bits));
$buf = $bytes[++$i];


if ($available >= $width) {
if ($ebytes > 0) {
if ($prev) {
$final = -1;
$s .= mb_substr(
if ($padding != AES_PAD_NULLS)  
$alphabet,
$final = $ebytes - 1;
$value = (
$extra | (
($buf >> ($shift + $prev)) &
~(-1 << ($width - $prev))
)
),
1
);
$buf <<= ($width - $prev);
$available -= $width;
}


while ($available >= $width) {
$byte = 0;
$s .= mb_substr(
for ($i = 0; $i < $ebytes; ++$i) {
$alphabet,
if (($padding != AES_PAD_NULLS) &&
$value = (($buf >> $shift) & $mask),
($i == $final))  
1
$byte = $ebytes;
);
else if ($padding == AES_PAD_RANDOM)  
$buf <<= $width;
$byte = mt_rand(0, 255);
$available -= $width;
$data .= pack('C', $byte);
}
}
}


if ($prev = $available)
return $this->cipher($data);
$extra = ($buf >> $shift) & $mask;
} else break;
}
}
}


if ($available) {
public function invertPadHexCipherToHex(&$cipher) {
$buf &= 0xFFFFFFFF;
return self::binaryToHex($this->invertPadCipher(self::hexToBinary($cipher)));
$mask = -1 << ($width - $prev);
}
$s .= mb_substr(
$alphabet,
$value = (($extra & $mask) |
(
($buf >> ($shift + $prev)) &
((-1 << ($width - $available)) ^ $mask))
),
1
);
}


return $s;
public function invertPadHexCipherToBase64(&$cipher) {
return self::binaryToBase64($this->invertPadCipher(self::hexToBinary($cipher)));
}
}


public function expandIV() {
public function invertPadBase64CipherToHex(&$cipher) {
$iv = array();
return self::binaryToHex($this->invertPadCipher(self::base64ToBinary($cipher)));
for ($i = 0; $i < 4; ++$i) {
$iv[$i][0] = (($this->iv[$i] >> 24) & 0xFF);
$iv[$i][1] = (($this->iv[$i] >> 16) & 0xFF);
$iv[$i][2] = (($this->iv[$i] >>  8) & 0xFF);
$iv[$i][3] = (($this->iv[$i]      ) & 0xFF);
}
return $iv;
}
}
}


class CryptoAESCBC {
public function invertPadBase64CipherToBase64(&$cipher) {
function invertCipher(&$crypto, &$data) {
return self::binaryToBase64($this->invertPadCipher(self::base64ToBinary($cipher)));
$state = Crypto::$NULL_STATE;
}


$next = array();
private function invertPadCipher(&$data) {
$prev = $crypto->expandIV();
$padding = $this->padType;
if ($padding == AES_PAD_NONE) {
if ($this->mode == AES_MODE_CFB)
return $this->invertCipher($data);
$padding = AES_PAD_RBT;
}


$l = count($data);
$blockSize = $this->padSize;
$output = array();
if ($padding == AES_PAD_RBT) $blockSize = 128;
$bytes  = mb_strlen($data, '8bit');
$bits = $bytes << 3;
$blocks = (integer)($bits / $blockSize);
$extra  = $bits % $blockSize;


while ($l > 0) {
if ($padding == AES_PAD_RBT) {
for ($i = 0; $i < 4; ++$i) {
if ($extra > 0) {
$next[$i][0] = $state[$i][0] =
$ebytes = $extra >> 3;
(($data[$i] >> 24) & 0xFF);
if (($ebytes << 3) < $extra) ++$ebytes;
$next[$i][1] = $state[$i][1] =
(($data[$i] >> 16) & 0xFF);
$next[$i][2] = $state[$i][2] =
(($data[$i] >>  8) & 0xFF);
$next[$i][3] = $state[$i][3] =
(($data[$i]      ) & 0xFF);
}


$crypto->performInvertCipher($state);
$pos = $bytes - $ebytes;
$t = mb_substr($data, $pos, $ebytes, '8bit');


for ($i = 0; $i < 4; ++$i)  
if ($blocks < 1)  
$output[] =  
$lb = $this->cipher(
    (($prev[$i][0] ^ $state[$i][0]) << 24) |
$this->cipher($this->iv)
    (($prev[$i][1] ^ $state[$i][1]) << 16) |
);
    (($prev[$i][2] ^ $state[$i][2]) <<  8) |
else {
    (($prev[$i][3] ^ $state[$i][3])     );
$start = $bytes - (16 + $ebytes);
$lb = $this->cipher(
mb_substr($data, $start, 16, '8bit')
);


$prev = $next;
$data = $this->invertCipher(
mb_substr($data, 0, $pos, '8bit')
);
}


$data = array_slice($data, 4);
for ($i = 0; $i < $ebytes; ++$i)
$l -= 4;
$data .= $t{$i} ^ $lb{$i};
}
return $data;
}
return $this->invertCipher($data);
} else {
if ($extra > 0) {
$bits -= $extra;


return $output;
$ebytes = $extra >> 3;
}
if (($e << 3) < $extra) ++$ebytes;
$extra = $ebytes;


function cipher(&$crypto, &$data) {
if ($extra <= 0) $extra = 1;
$state = $crypto->expandIV();
 
$count = count($data);
$l = count($data);
if ($extra > $bytes) return "\0";
$output = array();


while ($l > 0) {
$data = mb_substr($data, 0, $bytes - $extra, '8bit');
for ($i = 0; $i < 4; ++$i) {
$state[$i][0] ^= (($data[$i] >> 24) & 0xFF);
$state[$i][1] ^= (($data[$i] >> 16) & 0xFF);
$state[$i][2] ^= (($data[$i] >>  8) & 0xFF);
$state[$i][3] ^= (($data[$i]      ) & 0xFF);
}
}


$crypto->performCipher($state);
$data = $this->invertCipher($data);


for ($i = 0; $i < 4; ++$i)
$ebytes = 0; $excessBits = 0;
$output[] =  
($state[$i][0] << 24) |
($state[$i][1] << 16) |
($state[$i][2] <<  8) |
($state[$i][3]      );


$data = array_slice($data, 4);
if (($padding == AES_PAD_NULLS) ||
$l -= 4;
    ($padding == AES_PAD_NULLS_SAFE)) {
}
$v = 0; $continue = true;


return $output;
while ($byte = ($data{$index = ($bytes - ($ebytes + 1))}) == "\0")
}
++$ebytes;
}


class CryptoAESCFB {
if ($padding == AES_PAD_NULLS_SAFE) {
function invertCipher(&$crypto, &$data) {
$i = 1;
$prev = $crypto->expandIV();
while ($i < 0xFF) {
++$excessBits;
if ($byte & $i) {
$byte ^= $i;
break;
}
$i <<= 1;
}
$data{$index} = $byte;
}
} else {
$ebytes = $data{$bytes - 1} & 0xFF;
if (($ebytes << 3) >= $bits) return "\0";
}


$l = count($data);
if ($ebytes > 0)  
$output = array();
$data = mb_substr($data, 0, $bytes - $ebytes);


while ($l > 0) {
$bits -= ($ebytes << 3) + $excessBits;
$state = $prev;


$crypto->performCipher($state);
return $data;
}
}


for ($i = 0; $i < 4; ++$i) {
private function invertCipher($data) {
$prev[$i][0] = (($data[$i] >> 24) & 0xFF);
if ($data == "") return "";
$prev[$i][1] = (($data[$i] >> 16) & 0xFF);
$prev[$i][2] = (($data[$i] >> 8) & 0xFF);
if ($this->changed) {
$prev[$i][3] = (($data[$i]      ) & 0xFF);
$this->module->setKey($this->key);
$this->module->setIV($this->iv);
}


$output[] =
return $this->module->decrypt($data);
    (($prev[$i][0] ^ $state[$i][0]) << 24) |
}
    (($prev[$i][1] ^ $state[$i][1]) << 16) |
    (($prev[$i][2] ^ $state[$i][2]) <<  8) |
    (($prev[$i][3] ^ $state[$i][3])      );
}


$data = array_slice($data, 4);
private function cipher($data) {
$l -= 4;
if ($data == "") return "";
if ($this->changed) {
$this->module->setKey($this->key);
$this->module->setIV($this->iv);
}
}
return $this->module->encrypt($data);
}


return $output;
private static function base64ToBinary(&$base64Data) {
return base64_decode($base64Data);
}
}


function cipher(&$crypto, &$data) {
private static function hexToBinary(&$hexData) {
$state = $crypto->expandIV();
return pack('H*', $hexData);
}


$l = count($data);
private static function binaryToBase64(&$binary) {
$output = array();
return base64_encode($binary);
 
}
while ($l > 0) {
$crypto->performCipher($state);
 
for ($i = 0; $i < 4; ++$i) {
$state[$i][0] ^= (($data[$i] >> 24) & 0xFF);
$state[$i][1] ^= (($data[$i] >> 16) & 0xFF);
$state[$i][2] ^= (($data[$i] >>  8) & 0xFF);
$state[$i][3] ^= (($data[$i]      ) & 0xFF);


$output[] =
private static function binaryToHex(&$binary) {
    ($state[$i][0] << 24) | ($state[$i][1] << 16) |
$unpacked = unpack('H*hexchars', $binary);
    ($state[$i][2] <<  8) | ($state[$i][3]      );
return '0x'.implode('', $unpacked['hexchars']);
}
 
$data = array_slice($data, 4);
$l -= 4;
}
 
return $output;
}
}
}
}
 
?></php>
?></source>


= Examples =
= Examples =
The following are simple examples, and do not demonstrate real-world usage. They do not handle reading the message from Second Life, of which note, you will need to remember that PHP replaces any '+' characters (part of the base64 character-set) with spaces, unless you first replace them using URI encoding in your LSL script. The easiest solution is to use <code>str_replace($base64data, ' ', '+')</code> assuming your data contains only base64 encoded characters.
The following are simple examples, and do not demonstrate real-world usage. They do not handle reading the message from Second Life, of which note, you will need to remember that PHP replaces any '+' characters (part of the base64 character-set) with spaces, unless you first replace them using URI encoding in your LSL script. The easiest solution is to use <code>str_replace(' ', '+', $base64data)</code> assuming your data contains only base64 encoded characters.


Importantly the examples below do not handle input-validation, or key-generation, which are both critical to maintaining security. It is up to the developer to determine how they wish to verify the identify of their in-world objects from malicious attackers, and how they will generate a key, and keep it updated.
Importantly the examples below do not handle input-validation, or key-generation, which are both critical to maintaining security. It is up to the developer to determine how they wish to verify the identify of their in-world objects from malicious attackers, and how they will generate a key, and keep it updated.


== Encryption ==
== Encryption ==
<php><?php
<source lang="php"><?php
include 'aes.php';
include 'aes.php';


Line 911: Line 670:
$myMsg = 'Hello world! I am a lovely message waiting to be encrypted!';
$myMsg = 'Hello world! I am a lovely message waiting to be encrypted!';


$aes = new Crypto(AES_MODE_CFB, AES_PAD_NONE, 512);
$aes = new Crypto(AES_MODE_CBC, AES_PAD_NULLS_SAFE, 512);
$aes->setHexKey($myKey);
$aes->setHexKey($myKey);
$aes->setHexIV ($myIV);
$aes->setHexIV ($myIV);


die ($aes->padStringCipherToBase64($myMsg));
die ($aes->padStringCipherToBase64($myMsg));
?></php>
?></source>


== Decryption ==
== Decryption ==
<php><?php
<source lang="php"><?php
include 'aes.php';
include 'aes.php';


$myKey = '1234567890ABCDEF0123456789ABCDEF';
$myKey = '1234567890ABCDEF0123456789ABCDEF';
$myIV  = '89ABCDEF0123456789ABCDEF01234567';
$myIV  = '89ABCDEF0123456789ABCDEF01234567';
$myMsg = 'Mdn6jGTwRPMOKTYTTdDKGm9KScz26LIz96KVOGAeMw3hpwByPfa07PDRHxRW4TIh5dmu5LlhKpTQChi=';
$myMsg = 'slihkO6t9I/yfvfUpI0Rthagd/z8j1s5qh/PSbKGBg4N3PoQgUFdcCVnqOYku53csPmhn7i+XeHM9lsOO47exg==';


$aes = new Crypto(AES_MODE_CFB, AES_PAD_NONE, 512);
$aes = new Crypto(AES_MODE_CBC, AES_PAD_NULLS_SAFE, 512);
$aes->setHexKey($myKey);
$aes->setHexKey($myKey);
$aes->setHexIV ($myIV);
$aes->setHexIV ($myIV);


die ($aes-> invertPadBase64CipherToString($myMsg));
die ($aes-> invertPadBase64CipherToString($myMsg));
?></php>
?></source>

Latest revision as of 11:16, 25 January 2015

Description

The following is a PHP class compatible with the LSL AES Engine by Haravikk Mistral, allowing you to process Java and LSL compatible AES messages in PHP.

The Crypto class provided performs extremely well on most PHP set-ups, but the cost of including the file can vary with a number of factors. For this reason if you are only using AES encryption within a single script, you may consider entering the Crypto class directly into this script, rather than including it.

NOTE: The PHP implementation includes helper functions for handling strings, which are used in the examples, as the base64_decode() function provided by PHP will attempt to process null-characters produced as a result of the 6-bit to 8-bit conversion process. If you wish to process strings with null-characters at the end, then you will need to use invertPadBase64CipherToString() and call base64_decode yourself on the result.

Implementations

mcrypt

The following is the preferred PHP class but requires a PHP installation with the mcrypt module. It supports CBC and CFB modes, and the custom wrapper class implements all padding modes supported by the LSL implementation.

<?php
define('AES_MODE_CBC', 		1);
define('AES_MODE_CFB', 		2);

define('AES_PAD_NONE', 		128);
define('AES_PAD_RBT', 		129);
define('AES_PAD_NULLS', 	130);
define('AES_PAD_NULLS_SAFE',	131);
define('AES_PAD_ZEROES', 	132);
define('AES_PAD_RANDOM', 	133);

define('AES_DEFAULT_PAD',	512);

class Crypto {
	private $mode		= AES_MODE_CBC;
	private $padType	= AES_PAD_RBT;
	private $padSize	= AES_DEFAULT_PAD;

	private $iv;
	private $key;
	
	private $module;

	public function Crypto($mode=0, $padType=0, $padSize=0) {
		if ($padSize >= 128) $this->padSize = $padSize;
		if (($padType >= 128) && ($padType <= 133)) 
			$this->padType = $padType;

		if (($mode >= 1) || ($mode <= 2)) $this->mode = $mode;
		
		$mcryptMode = 'cbc';
		if ($this->mode == AES_MODE_CFB) $mcryptMode = 'ncfb';
		
		$this->module = mcrypt_module_open('rijndael-128', '', $mcryptMode, '');
	}
	
	public function __destruct() {
		mcrypt_module_close($this->module);
	}

	public function setHexKey(&$key) {
		$this->key = self::hexToBinary($key);
	}

	public function setBase64Key(&$key) {
		$this->key = self::base64ToBinary($key);
	}

	public function setHexIV(&$iv) {
		$this->iv = self::hexToBinary($iv);
	}

	public function padHexCipherToHex(&$cipher) {
		return self::binaryToHex(
			$this->padCipher(self::hexToBinary($cipher)));
	}

	public function padHexCipherToBase64(&$cipher) {
		return self::binaryToBase64(
			$this->padCipher(self::hexToBinary($cipher)));
	}

	public function padStringCipherToBase64(&$cipher) {
		return self::binaryToBase64($this->padCipher($cipher));
	}

	public function invertPadBase64CipherToString(&$cipher) {
		return $this->invertPadCipher(self::base64ToBinary($cipher));
	}

	public function padBase64CipherToHex(&$cipher) {
		return self::binaryToHex(
			$this->padCipher(self::base64ToBinary($cipher)));
	}

	public function padBase64CipherToBase64(&$cipher) {
		return self::binaryToBase64(
			$this->padCipher(self::base64ToBinary($cipher)));
	}

	private function padCipher(&$data) {
		$padding = $this->padType;
		if ($padding == AES_PAD_NONE) {
			if ($this->mode == AES_MODE_CFB) 
				return $this->cipher($data);
			$padding = AES_PAD_RBT;
		}

		$blockSize = $this->padSize;
		if ($padding == AES_PAD_RBT) $blockSize = 128;

		$bytes  = mb_strlen($data, '8bit');
		$bits	= $bytes << 3;
		$blocks = (integer)($bits / $blockSize);
		$extra  = $bits % $blockSize;

		if ($padding == AES_PAD_RBT) {
			if ($extra > 0) {
				$ebytes = $extra >> 3;
				if (($ebytes << 3) < $extra) ++$ebytes;

				$pos = $bytes - $ebytes;
				$t = mb_substr($data, $pos, $ebytes, '8bit');
				
				if ($blocks < 1) {
					$data = '';
					$lb = $this->cipher($this->cipher($iv));
				} else {
					$data = $this->cipher(
						mb_substr($data, 0, $pos, '8bit')
					);

					$lb = $this->cipher(
						mb_substr($data, $pos - 16, 16, '8bit')
					);
				}

				for ($i = 0; $i < $ebytes; ++$i) 
					$data .= $t{$i} ^ $lb{$i};

				return $data;
			}

			return $this->cipher($data);
		} else {
			$extra = $blockSize - $extra;

			if ($padding == AES_PAD_NULLS_SAFE) {
				++$bits;
				$data .= "\x80";

				if ((--$extra) < 0) $extra += $blockSize;
				$padding = AES_PAD_NULLS;
			}

			$ebytes = $extra >> 3;
			if ($ebytes <= 0) {
				if ($padding == AES_PAD_NULLS) 
					return $this->cipher($data);

				$ebytes = $blockSize >> 3;
				$extra += $blockSize;
			}

			$bits += $extra;

			if ($ebytes > 0) {
				$final = -1;
				if ($padding != AES_PAD_NULLS) 
					$final = $ebytes - 1;

				$byte = 0;
				for ($i = 0; $i < $ebytes; ++$i) {
					if (($padding != AES_PAD_NULLS) && 
						($i == $final)) 
						$byte = $ebytes;
					else if ($padding == AES_PAD_RANDOM) 
						$byte = mt_rand(0, 255);
					$data .= pack('C', $byte);
				}
			}

			return $this->cipher($data);
		}
	}

	public function invertPadHexCipherToHex(&$cipher) {
		return self::binaryToHex($this->invertPadCipher(self::hexToBinary($cipher)));
	}

	public function invertPadHexCipherToBase64(&$cipher) {
		return self::binaryToBase64($this->invertPadCipher(self::hexToBinary($cipher)));
	}

	public function invertPadBase64CipherToHex(&$cipher) {
		return self::binaryToHex($this->invertPadCipher(self::base64ToBinary($cipher)));
	}

	public function invertPadBase64CipherToBase64(&$cipher) {
		return self::binaryToBase64($this->invertPadCipher(self::base64ToBinary($cipher)));
	}

	private function invertPadCipher(&$data) {
		$padding = $this->padType;
		if ($padding == AES_PAD_NONE) {
			if ($this->mode == AES_MODE_CFB) 
				return $this->invertCipher($data);
			$padding = AES_PAD_RBT;
		}

		$blockSize = $this->padSize;
		if ($padding == AES_PAD_RBT) $blockSize = 128;
		
		$bytes  = mb_strlen($data, '8bit');
		$bits	= $bytes << 3;
		$blocks = (integer)($bits / $blockSize);
		$extra  = $bits % $blockSize;

		if ($padding == AES_PAD_RBT) {
			if ($extra > 0) {
				$ebytes = $extra >> 3;
				if (($ebytes << 3) < $extra) ++$ebytes;

				$pos = $bytes - $ebytes;
				$t = mb_substr($data, $pos, $ebytes, '8bit');

				if ($blocks < 1) 
					$lb = $this->cipher(
						$this->cipher($this->iv)
					);
				else {
					$start = $bytes - (16 + $ebytes);
					$lb = $this->cipher(
						mb_substr($data, $start, 16, '8bit')
					);

					$data = $this->invertCipher(
						mb_substr($data, 0, $pos, '8bit')
					);
				}

				for ($i = 0; $i < $ebytes; ++$i) 
					$data .= $t{$i} ^ $lb{$i};
					
				return $data;
			}
			
			return $this->invertCipher($data);
		} else {
			if ($extra > 0) {
				$bits -= $extra;

				$ebytes = $extra >> 3;
				if (($e << 3) < $extra) ++$ebytes;
				$extra = $ebytes;

				if ($extra <= 0) $extra = 1;
 
				$count = count($data);
				if ($extra > $bytes) return "\0";

				$data = mb_substr($data, 0, $bytes - $extra, '8bit');
			}

			$data = $this->invertCipher($data);

			$ebytes = 0; $excessBits = 0;

			if (($padding == AES_PAD_NULLS) || 
			    ($padding == AES_PAD_NULLS_SAFE)) {
				$v = 0; $continue = true;

				while ($byte = ($data{$index = ($bytes - ($ebytes + 1))}) == "\0") 
					++$ebytes;

				if ($padding == AES_PAD_NULLS_SAFE) {
					$i = 1;
					while ($i < 0xFF) {
						++$excessBits;
						if ($byte & $i) {
							$byte ^= $i;
							break;
						}
						$i <<= 1;
					}
					$data{$index} = $byte;
				}
			} else {
				$ebytes = $data{$bytes - 1} & 0xFF;
				if (($ebytes << 3) >= $bits) return "\0";
			}

			if ($ebytes > 0) 
				$data = mb_substr($data, 0, $bytes - $ebytes);

			$bits -= ($ebytes << 3) + $excessBits;

			return $data;
		}
	}

	private function invertCipher($data) {
		if ($data == "") return "";
	
		mcrypt_generic_init($this->module, $this->key, $this->iv);
		$decrypted = mdecrypt_generic($this->module, $data);
		mcrypt_generic_deinit($this->module);
		return $decrypted;
	}

	private function cipher($data) {
		if ($data == "") return "";
		
		mcrypt_generic_init($this->module, $this->key, $this->iv);
		$cipherText = mcrypt_generic($this->module, $data);
		mcrypt_generic_deinit($this->module);
		return $cipherText;
	}

	private static function base64ToBinary(&$base64Data) {
		return base64_decode($base64Data);
	}

	private static function hexToBinary(&$hexData) {
		return pack('H*', $hexData);
	}

	private static function binaryToBase64(&$binary) {
		return base64_encode($binary);
	}

	private static function binaryToHex(&$binary) {
		$unpacked = unpack('H*hexchars', $binary);
		return '0x'.implode('', $unpacked['hexchars']);
	}
}

?>

phpseclib

The following class follows the same structure as the one above and implements the same padding schemes, but provides an alternative implementation using phpseclib. This implementation only supports CBC (the currently recommended mode of operation), but phpseclib has the advantage that it can be installed on hosts that do not have mcrypt (though it will support mcrypt when it is available), so it allows you to produce more portable code.

Thanks to John Avium for recommending the use of this library.

<?php
include_once 'phpseclib/Crypt/AES.php';

define('AES_MODE_CBC', 		1);

define('AES_PAD_NONE', 		128);
define('AES_PAD_RBT', 		129);
define('AES_PAD_NULLS', 	130);
define('AES_PAD_NULLS_SAFE',	131);
define('AES_PAD_ZEROES', 	132);
define('AES_PAD_RANDOM', 	133);

define('AES_DEFAULT_PAD',	512);

class Crypto {
	private $mode		= AES_MODE_CBC;
	private $padType	= AES_PAD_RBT;
	private $padSize	= AES_DEFAULT_PAD;

	private $iv;
	private $key;
	private $changed	= true;
	
	private $module;

	public function Crypto($mode=0, $padType=0, $padSize=0) {
		if ($padSize >= 128) $this->padSize = $padSize;
		if (($padType >= 128) && ($padType <= 133)) 
			$this->padType = $padType;

		// Re-add this if phpseclib ever adds more modes of operation
		//if (($mode >= 1) || ($mode <= 2)) $this->mode = $mode;
		
		$this->module = new Crypt_AES(CRYPT_AES_MODE_CBC);
		$this->module->disablePadding();
	}

	public function setHexKey(&$key) {
		$this->key = self::hexToBinary($key);
		$this->changed = true;
	}

	public function setBase64Key(&$key) {
		$this->key = self::base64ToBinary($key);
		$this->changed = true;
	}

	public function setHexIV(&$iv) {
		$this->iv = self::hexToBinary($iv);
		$this->changed = true;
	}

	public function padHexCipherToHex(&$cipher) {
		return self::binaryToHex(
			$this->padCipher(self::hexToBinary($cipher)));
	}

	public function padHexCipherToBase64(&$cipher) {
		return self::binaryToBase64(
			$this->padCipher(self::hexToBinary($cipher)));
	}
	
	public function padStringCipherToBase64(&$cipher) {
		return self::binaryToBase64($this->padCipher($cipher));
	}

	public function invertPadBase64CipherToString(&$cipher) {
		return $this->invertPadCipher(self::base64ToBinary($cipher));
	}

	public function padBase64CipherToHex(&$cipher) {
		return self::binaryToHex(
			$this->padCipher(self::base64ToBinary($cipher)));
	}

	public function padBase64CipherToBase64(&$cipher) {
		return self::binaryToBase64(
			$this->padCipher(self::base64ToBinary($cipher)));
	}

	private function padCipher(&$data) {
		$padding = $this->padType;
		if ($padding == AES_PAD_NONE) {
			if ($this->mode == AES_MODE_CFB) 
				return $this->cipher($data);
			$padding = AES_PAD_RBT;
		}

		$blockSize = $this->padSize;
		if ($padding == AES_PAD_RBT) $blockSize = 128;

		$bytes  = mb_strlen($data, '8bit');
		$bits	= $bytes << 3;
		$blocks = (integer)($bits / $blockSize);
		$extra  = $bits % $blockSize;

		if ($padding == AES_PAD_RBT) {
			if ($extra > 0) {
				$ebytes = $extra >> 3;
				if (($ebytes << 3) < $extra) ++$ebytes;

				$pos = $bytes - $ebytes;
				$t = mb_substr($data, $pos, $ebytes, '8bit');
				
				if ($blocks < 1) {
					$data = '';
					$lb = $this->cipher($this->cipher($iv));
				} else {
					$data = $this->cipher(
						mb_substr($data, 0, $pos, '8bit')
					);

					$lb = $this->cipher(
						mb_substr($data, $pos - 16, 16, '8bit')
					);
				}

				for ($i = 0; $i < $ebytes; ++$i) 
					$data .= $t{$i} ^ $lb{$i};

				return $data;
			}

			return $this->cipher($data);
		} else {
			$extra = $blockSize - $extra;

			if ($padding == AES_PAD_NULLS_SAFE) {
				++$bits;
				$data .= "\x80";

				if ((--$extra) < 0) $extra += $blockSize;
				$padding = AES_PAD_NULLS;
			}

			$ebytes = $extra >> 3;
			if ($ebytes <= 0) {
				if ($padding == AES_PAD_NULLS) 
					return $this->cipher($data);

				$ebytes = $blockSize >> 3;
				$extra += $blockSize;
			}

			$bits += $extra;

			if ($ebytes > 0) {
				$final = -1;
				if ($padding != AES_PAD_NULLS) 
					$final = $ebytes - 1;

				$byte = 0;
				for ($i = 0; $i < $ebytes; ++$i) {
					if (($padding != AES_PAD_NULLS) && 
						($i == $final)) 
						$byte = $ebytes;
					else if ($padding == AES_PAD_RANDOM) 
						$byte = mt_rand(0, 255);
					$data .= pack('C', $byte);
				}
			}

			return $this->cipher($data);
		}
	}

	public function invertPadHexCipherToHex(&$cipher) {
		return self::binaryToHex($this->invertPadCipher(self::hexToBinary($cipher)));
	}

	public function invertPadHexCipherToBase64(&$cipher) {
		return self::binaryToBase64($this->invertPadCipher(self::hexToBinary($cipher)));
	}

	public function invertPadBase64CipherToHex(&$cipher) {
		return self::binaryToHex($this->invertPadCipher(self::base64ToBinary($cipher)));
	}

	public function invertPadBase64CipherToBase64(&$cipher) {
		return self::binaryToBase64($this->invertPadCipher(self::base64ToBinary($cipher)));
	}

	private function invertPadCipher(&$data) {
		$padding = $this->padType;
		if ($padding == AES_PAD_NONE) {
			if ($this->mode == AES_MODE_CFB) 
				return $this->invertCipher($data);
			$padding = AES_PAD_RBT;
		}

		$blockSize = $this->padSize;
		if ($padding == AES_PAD_RBT) $blockSize = 128;
		
		$bytes  = mb_strlen($data, '8bit');
		$bits	= $bytes << 3;
		$blocks = (integer)($bits / $blockSize);
		$extra  = $bits % $blockSize;

		if ($padding == AES_PAD_RBT) {
			if ($extra > 0) {
				$ebytes = $extra >> 3;
				if (($ebytes << 3) < $extra) ++$ebytes;

				$pos = $bytes - $ebytes;
				$t = mb_substr($data, $pos, $ebytes, '8bit');

				if ($blocks < 1) 
					$lb = $this->cipher(
						$this->cipher($this->iv)
					);
				else {
					$start = $bytes - (16 + $ebytes);
					$lb = $this->cipher(
						mb_substr($data, $start, 16, '8bit')
					);

					$data = $this->invertCipher(
						mb_substr($data, 0, $pos, '8bit')
					);
				}

				for ($i = 0; $i < $ebytes; ++$i) 
					$data .= $t{$i} ^ $lb{$i};
					
				return $data;
			}
			
			return $this->invertCipher($data);
		} else {
			if ($extra > 0) {
				$bits -= $extra;

				$ebytes = $extra >> 3;
				if (($e << 3) < $extra) ++$ebytes;
				$extra = $ebytes;

				if ($extra <= 0) $extra = 1;
 
				$count = count($data);
				if ($extra > $bytes) return "\0";

				$data = mb_substr($data, 0, $bytes - $extra, '8bit');
			}

			$data = $this->invertCipher($data);

			$ebytes = 0; $excessBits = 0;

			if (($padding == AES_PAD_NULLS) || 
			    ($padding == AES_PAD_NULLS_SAFE)) {
				$v = 0; $continue = true;

				while ($byte = ($data{$index = ($bytes - ($ebytes + 1))}) == "\0") 
					++$ebytes;

				if ($padding == AES_PAD_NULLS_SAFE) {
					$i = 1;
					while ($i < 0xFF) {
						++$excessBits;
						if ($byte & $i) {
							$byte ^= $i;
							break;
						}
						$i <<= 1;
					}
					$data{$index} = $byte;
				}
			} else {
				$ebytes = $data{$bytes - 1} & 0xFF;
				if (($ebytes << 3) >= $bits) return "\0";
			}

			if ($ebytes > 0) 
				$data = mb_substr($data, 0, $bytes - $ebytes);

			$bits -= ($ebytes << 3) + $excessBits;

			return $data;
		}
	}

	private function invertCipher($data) {
		if ($data == "") return "";
	
		if ($this->changed) {
			$this->module->setKey($this->key);
			$this->module->setIV($this->iv);
		}

		return $this->module->decrypt($data);
	}

	private function cipher($data) {
		if ($data == "") return "";
		
		if ($this->changed) {
			$this->module->setKey($this->key);
			$this->module->setIV($this->iv);
		}
		
		return $this->module->encrypt($data);
	}

	private static function base64ToBinary(&$base64Data) {
		return base64_decode($base64Data);
	}

	private static function hexToBinary(&$hexData) {
		return pack('H*', $hexData);
	}

	private static function binaryToBase64(&$binary) {
		return base64_encode($binary);
	}

	private static function binaryToHex(&$binary) {
		$unpacked = unpack('H*hexchars', $binary);
		return '0x'.implode('', $unpacked['hexchars']);
	}
}
 
?>

Examples

The following are simple examples, and do not demonstrate real-world usage. They do not handle reading the message from Second Life, of which note, you will need to remember that PHP replaces any '+' characters (part of the base64 character-set) with spaces, unless you first replace them using URI encoding in your LSL script. The easiest solution is to use str_replace(' ', '+', $base64data) assuming your data contains only base64 encoded characters.

Importantly the examples below do not handle input-validation, or key-generation, which are both critical to maintaining security. It is up to the developer to determine how they wish to verify the identify of their in-world objects from malicious attackers, and how they will generate a key, and keep it updated.

Encryption

<?php
include 'aes.php';

$myKey = '1234567890ABCDEF0123456789ABCDEF';
$myIV  = '89ABCDEF0123456789ABCDEF01234567';
$myMsg = 'Hello world! I am a lovely message waiting to be encrypted!';

$aes = new Crypto(AES_MODE_CBC, AES_PAD_NULLS_SAFE, 512);
$aes->setHexKey($myKey);
$aes->setHexIV ($myIV);

die ($aes->padStringCipherToBase64($myMsg));
?>

Decryption

<?php
include 'aes.php';

$myKey = '1234567890ABCDEF0123456789ABCDEF';
$myIV  = '89ABCDEF0123456789ABCDEF01234567';
$myMsg = 'slihkO6t9I/yfvfUpI0Rthagd/z8j1s5qh/PSbKGBg4N3PoQgUFdcCVnqOYku53csPmhn7i+XeHM9lsOO47exg==';

$aes = new Crypto(AES_MODE_CBC, AES_PAD_NULLS_SAFE, 512);
$aes->setHexKey($myKey);
$aes->setHexIV ($myIV);

die ($aes-> invertPadBase64CipherToString($myMsg));
?>