Crypt: add OpenSSL support

aside from the addition of OpenSSL support a few other changes have been included:

- setEngine(), as added by petrich, is depricated (not that it was ever in trunk to begin with)
  it has been replaced with isValidEngine() and setPreferredEngine().
- replace _generate_xor() with increment_str()
  _increment_str() had extra functionality that wasn't being used. ie. it could concatenate
  multiple successive string increments to one another automatically. but not only was that
  functionality not used - it also made the function less versatile. _increment_str() can be
  used more easily for iterative brute forcing (for example)
- rename Crypt_Base::_stringShift to Crypt_Base::_string_shift (for consistency)
- more expansive unit test coverage
This commit is contained in:
terrafrost 2014-11-29 07:39:21 -06:00
parent e7708b0d20
commit f6e0c4b506
15 changed files with 1018 additions and 239 deletions

View File

@ -206,7 +206,7 @@ class Crypt_AES extends Crypt_Rijndael
default:
$this->key_size = 32;
}
$this->_setupEngine();
$this->_setEngine();
}
}
}

View File

@ -110,6 +110,10 @@ define('CRYPT_MODE_INTERNAL', 1);
* Base value for the mcrypt implementation $engine switch
*/
define('CRYPT_MODE_MCRYPT', 2);
/**
* Base value for the OpenSSL implementation $engine switch
*/
define('CRYPT_MODE_OPENSSL', 3);
/**#@-*/
/**
@ -118,7 +122,7 @@ define('CRYPT_MODE_MCRYPT', 2);
* @package Crypt_Base
* @author Jim Wigginton <terrafrost@php.net>
* @author Hans-Juergen Petrich <petrich@tronic-media.com>
* @version 1.0.1
* @version 1.0.2
* @access public
*/
class Crypt_Base
@ -326,15 +330,11 @@ class Crypt_Base
* which will be determined automatically on __construct()
*
* Currently available $engines are:
* - CRYPT_MODE_OPENSSL (very fast, php-extension: openssl, extension_loaded('openssl') required)
* - CRYPT_MODE_MCRYPT (fast, php-extension: mcrypt, extension_loaded('mcrypt') required)
* - CRYPT_MODE_INTERNAL (slower, pure php-engine, no php-extension required)
*
* In the pipeline... maybe. But currently not available:
* - CRYPT_MODE_OPENSSL (very fast, php-extension: openssl, extension_loaded('openssl') required)
*
* If possible, CRYPT_MODE_MCRYPT will be used for each cipher.
* Otherwise CRYPT_MODE_INTERNAL
*
* @see Crypt_Base::_setEngine()
* @see Crypt_Base::encrypt()
* @see Crypt_Base::decrypt()
* @var Integer
@ -342,6 +342,16 @@ class Crypt_Base
*/
var $engine;
/**
* Holds the preferred crypt engine
*
* @see Crypt_Base::_setEngine()
* @see Crypt_Base::setPreferredEngine()
* @var Integer
* @access private
*/
var $preferredEngine;
/**
* The mcrypt specific name of the cipher
*
@ -355,6 +365,29 @@ class Crypt_Base
*/
var $cipher_name_mcrypt;
/**
* The openssl specific name of the cipher
*
* Only used if $engine == CRYPT_MODE_OPENSSL
*
* @link http://www.php.net/openssl-get-cipher-methods
* @var String
* @access private
*/
var $cipher_name_openssl;
/**
* The openssl specific name of the cipher in ECB mode
*
* If OpenSSL does not support the mode we're trying to use (CTR)
* it can still be emulated with ECB mode.
*
* @link http://www.php.net/openssl-get-cipher-methods
* @var String
* @access private
*/
var $cipher_name_openssl_ecb;
/**
* The default password key_size used by setPassword()
*
@ -422,6 +455,15 @@ class Crypt_Base
*/
var $use_inline_crypt;
/**
* If OpenSSL can be used in ECB but not in CTR we can emulate CTR
*
* @see Crypt_Base::_openssl_ctr_process()
* @var Boolean
* @access private
*/
var $openssl_emulate_ctr = false;
/**
* Default Constructor.
*
@ -448,20 +490,11 @@ class Crypt_Base
*/
function Crypt_Base($mode = CRYPT_MODE_CBC)
{
$const_crypt_mode = 'CRYPT_' . $this->const_namespace . '_MODE';
// Setup the internal crypt engine
if (defined($const_crypt_mode)) {
$this->engine = constant($const_crypt_mode);
} else {
define($const_crypt_mode, $this->setEngine());
}
// $mode dependent settings
switch ($mode) {
case CRYPT_MODE_ECB:
$this->paddable = true;
$this->mode = $mode;
$this->mode = CRYPT_MODE_ECB;
break;
case CRYPT_MODE_CTR:
case CRYPT_MODE_CFB:
@ -475,6 +508,8 @@ class Crypt_Base
$this->mode = CRYPT_MODE_CBC;
}
$this->_setEngine();
// Determining whether inline crypting can be used by the cipher
if ($this->use_inline_crypt !== false && function_exists('create_function')) {
$this->use_inline_crypt = true;
@ -519,6 +554,7 @@ class Crypt_Base
{
$this->key = $key;
$this->changed = true;
$this->_setEngine();
}
/**
@ -631,12 +667,85 @@ class Crypt_Base
* @see Crypt_Base::decrypt()
* @access public
* @param String $plaintext
* @return String $cipertext
* @return String $ciphertext
* @internal Could, but not must, extend by the child Crypt_* class
*/
function encrypt($plaintext)
{
if ($this->engine == CRYPT_MODE_MCRYPT) {
if ($this->paddable) {
$plaintext = $this->_pad($plaintext);
}
if ($this->engine === CRYPT_MODE_OPENSSL) {
if ($this->changed) {
$this->_clearBuffers();
$this->changed = false;
}
switch ($this->mode) {
case CRYPT_MODE_STREAM:
case CRYPT_MODE_ECB:
return openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
case CRYPT_MODE_CBC:
$ciphertext = openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $this->encryptIV);
if ($this->continuousBuffer) {
$this->encryptIV = substr($ciphertext, -$this->block_size);
}
return $ciphertext;
case CRYPT_MODE_CTR:
return $this->_openssl_ctr_process($plaintext, $this->encryptIV, $this->enbuffer);
case CRYPT_MODE_CFB:
// cfb loosely routines inspired by openssl's:
// {@link http://cvs.openssl.org/fileview?f=openssl/crypto/modes/cfb128.c&v=1.3.2.2.2.1}
if ($this->continuousBuffer) {
$iv = &$this->encryptIV;
$pos = &$this->enbuffer['pos'];
} else {
$iv = &$this->encryptIV;
$pos = 0;
}
$len = strlen($plaintext);
$i = 0;
if ($pos) {
$orig_pos = $pos;
$max = $this->block_size - $pos;
if ($len >= $max) {
$i = $max;
$len-= $max;
$pos = 0;
} else {
$i = $len;
$pos+= $len;
$len = 0;
}
// ie. $i = min($max, $len), $len-= $i, $pos+= $i, $pos%= $blocksize
$ciphertext = substr($iv, $orig_pos) ^ $plaintext;
$iv = substr_replace($iv, $ciphertext, $orig_pos, $i);
$plaintext = substr($plaintext, $i);
}
$overflow = $len % $this->block_size;
if ($overflow) {
$ciphertext.= openssl_encrypt(substr($plaintext, 0, -$overflow) . str_repeat("\0", $this->block_size), $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv);
$iv = $this->_string_pop($ciphertext, $this->block_size);
$size = $len - $overflow;
$block = $iv ^ substr($plaintext, -$overflow);
$iv = substr_replace($iv, $block, 0, $overflow);
$ciphertext.= $block;
$pos = $overflow;
} else if ($len) {
$ciphertext = openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv);
$iv = substr($ciphertext, -$this->block_size);
}
return $ciphertext;
case CRYPT_MODE_OFB:
return $this->_openssl_ofb_process($plaintext, $this->encryptIV, $this->enbuffer);
}
}
if ($this->engine === CRYPT_MODE_MCRYPT) {
if ($this->changed) {
$this->_setupMcrypt();
$this->changed = false;
@ -702,10 +811,6 @@ class Crypt_Base
return $ciphertext;
}
if ($this->paddable) {
$plaintext = $this->_pad($plaintext);
}
$ciphertext = mcrypt_generic($this->enmcrypt, $plaintext);
if (!$this->continuousBuffer) {
@ -723,9 +828,6 @@ class Crypt_Base
$inline = $this->inline_crypt;
return $inline('encrypt', $this, $plaintext);
}
if ($this->paddable) {
$plaintext = $this->_pad($plaintext);
}
$buffer = &$this->enbuffer;
$block_size = $this->block_size;
@ -754,15 +856,17 @@ class Crypt_Base
for ($i = 0; $i < strlen($plaintext); $i+=$block_size) {
$block = substr($plaintext, $i, $block_size);
if (strlen($block) > strlen($buffer['encrypted'])) {
$buffer['encrypted'].= $this->_encryptBlock($this->_generateXor($xor, $block_size));
$buffer['encrypted'].= $this->_encryptBlock($xor);
}
$key = $this->_stringShift($buffer['encrypted'], $block_size);
$this->_increment_str($xor);
$key = $this->_string_shift($buffer['encrypted'], $block_size);
$ciphertext.= $block ^ $key;
}
} else {
for ($i = 0; $i < strlen($plaintext); $i+=$block_size) {
$block = substr($plaintext, $i, $block_size);
$key = $this->_encryptBlock($this->_generateXor($xor, $block_size));
$key = $this->_encryptBlock($xor);
$this->_increment_str($xor);
$ciphertext.= $block ^ $key;
}
}
@ -824,7 +928,7 @@ class Crypt_Base
$xor = $this->_encryptBlock($xor);
$buffer['xor'].= $xor;
}
$key = $this->_stringShift($buffer['xor'], $block_size);
$key = $this->_string_shift($buffer['xor'], $block_size);
$ciphertext.= $block ^ $key;
}
} else {
@ -863,7 +967,82 @@ class Crypt_Base
*/
function decrypt($ciphertext)
{
if ($this->engine == CRYPT_MODE_MCRYPT) {
if ($this->paddable) {
// we pad with chr(0) since that's what mcrypt_generic does [...]
$ciphertext = str_pad($ciphertext, strlen($ciphertext) + ($this->block_size - strlen($ciphertext) % $this->block_size) % $this->block_size, chr(0));
}
if ($this->engine === CRYPT_MODE_OPENSSL) {
if ($this->changed) {
$this->_clearBuffers();
$this->changed = false;
}
switch ($this->mode) {
case CRYPT_MODE_STREAM:
case CRYPT_MODE_ECB:
$plaintext = openssl_decrypt($ciphertext, $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
break;
case CRYPT_MODE_CBC:
$plaintext = openssl_decrypt($ciphertext, $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $this->decryptIV);
if ($this->continuousBuffer) {
$this->decryptIV = substr($ciphertext, -$this->block_size);
}
break;
case CRYPT_MODE_CTR:
$plaintext = $this->_openssl_ctr_process($ciphertext, $this->decryptIV, $this->debuffer);
break;
case CRYPT_MODE_CFB:
// cfb loosely routines inspired by openssl's:
// {@link http://cvs.openssl.org/fileview?f=openssl/crypto/modes/cfb128.c&v=1.3.2.2.2.1}
if ($this->continuousBuffer) {
$iv = &$this->decryptIV;
$pos = &$this->buffer['pos'];
} else {
$iv = $this->decryptIV;
$pos = 0;
}
$len = strlen($ciphertext);
$i = 0;
if ($pos) {
$orig_pos = $pos;
$max = $this->block_size - $pos;
if ($len >= $max) {
$i = $max;
$len-= $max;
$pos = 0;
} else {
$i = $len;
$pos+= $len;
$len = 0;
}
// ie. $i = min($max, $len), $len-= $i, $pos+= $i, $pos%= $this->blocksize
$plaintext = substr($iv, $orig_pos) ^ $ciphertext;
$iv = substr_replace($iv, substr($ciphertext, 0, $i), $orig_pos, $i);
$ciphertext = substr($ciphertext, $i);
}
$overflow = $len % $this->block_size;
if ($overflow) {
$plaintext.= openssl_decrypt(substr($ciphertext, 0, -$overflow), $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv);
if ($len - $overflow) {
$iv = substr($ciphertext, -$overflow - $this->block_size, -$overflow);
}
$iv = openssl_encrypt(str_repeat("\0", $this->block_size), $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv);
$plaintext.= $iv ^ substr($ciphertext, -$overflow);
$iv = substr_replace($iv, substr($ciphertext, -$overflow), 0, $overflow);
$pos = $overflow;
} else if ($len) {
$plaintext.= openssl_decrypt($ciphertext, $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv);
$iv = substr($ciphertext, -$this->block_size);
}
break;
case CRYPT_MODE_OFB:
$plaintext = $this->_openssl_ofb_process($ciphertext, $this->decryptIV, $this->debuffer);
}
return $this->paddable ? $this->_unpad($plaintext) : $plaintext;
}
if ($this->engine === CRYPT_MODE_MCRYPT) {
$block_size = $this->block_size;
if ($this->changed) {
$this->_setupMcrypt();
@ -937,10 +1116,6 @@ class Crypt_Base
}
$block_size = $this->block_size;
if ($this->paddable) {
// we pad with chr(0) since that's what mcrypt_generic does [...]
$ciphertext = str_pad($ciphertext, strlen($ciphertext) + ($block_size - strlen($ciphertext) % $block_size) % $block_size, chr(0));
}
$buffer = &$this->debuffer;
$plaintext = '';
@ -967,15 +1142,17 @@ class Crypt_Base
for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) {
$block = substr($ciphertext, $i, $block_size);
if (strlen($block) > strlen($buffer['ciphertext'])) {
$buffer['ciphertext'].= $this->_encryptBlock($this->_generateXor($xor, $block_size));
$buffer['ciphertext'].= $this->_encryptBlock($xor);
$this->_increment_str($xor);
}
$key = $this->_stringShift($buffer['ciphertext'], $block_size);
$key = $this->_string_shift($buffer['ciphertext'], $block_size);
$plaintext.= $block ^ $key;
}
} else {
for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) {
$block = substr($ciphertext, $i, $block_size);
$key = $this->_encryptBlock($this->_generateXor($xor, $block_size));
$key = $this->_encryptBlock($xor);
$this->_increment_str($xor);
$plaintext.= $block ^ $key;
}
}
@ -1036,7 +1213,7 @@ class Crypt_Base
$xor = $this->_encryptBlock($xor);
$buffer['xor'].= $xor;
}
$key = $this->_stringShift($buffer['xor'], $block_size);
$key = $this->_string_shift($buffer['xor'], $block_size);
$plaintext.= $block ^ $key;
}
} else {
@ -1060,6 +1237,164 @@ class Crypt_Base
return $this->paddable ? $this->_unpad($plaintext) : $plaintext;
}
/**
* OpenSSL CTR Processor
*
* PHP's OpenSSL bindings do not operate in continuous mode so we'll wrap around it. Since the keystream
* for CTR is the same for both encrypting and decrypting this function is re-used by both Crypt_Base::encrypt()
* and Crypt_Base::decrypt(). Also, OpenSSL doesn't implement CTR for all of it's symmetric ciphers so this
* function will emulate CTR with ECB when necesary.
*
* @see Crypt_Base::encrypt()
* @see Crypt_Base::decrypt()
* @param String $plaintext
* @param String $encryptIV
* @param Array $buffer
* @return String
* @access private
*/
function _openssl_ctr_process($plaintext, &$encryptIV, &$buffer)
{
$ciphertext = '';
$block_size = $this->block_size;
$key = $this->key;
if ($this->openssl_emulate_ctr) {
$xor = $encryptIV;
if (strlen($buffer['encrypted'])) {
for ($i = 0; $i < strlen($plaintext); $i+=$block_size) {
$block = substr($plaintext, $i, $block_size);
if (strlen($block) > strlen($buffer['encrypted'])) {
$buffer['encrypted'].= openssl_encrypt($xor, $this->cipher_name_openssl_ecb, $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
}
$this->_increment_str($xor);
$otp = $this->_string_shift($buffer['encrypted'], $block_size);
$ciphertext.= $block ^ $otp;
}
} else {
for ($i = 0; $i < strlen($plaintext); $i+=$block_size) {
$block = substr($plaintext, $i, $block_size);
$otp = openssl_encrypt($xor, $this->cipher_name_openssl_ecb, $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
$this->_increment_str($xor);
$ciphertext.= $block ^ $otp;
}
}
if ($this->continuousBuffer) {
$encryptIV = $xor;
if ($start = strlen($plaintext) % $block_size) {
$buffer['encrypted'] = substr($key, $start) . $buffer['encrypted'];
}
}
return $ciphertext;
}
if (strlen($buffer['encrypted'])) {
$ciphertext = $plaintext ^ $this->_string_shift($buffer['encrypted'], strlen($plaintext));
$plaintext = substr($plaintext, strlen($ciphertext));
if (!strlen($plaintext)) {
return $ciphertext;
}
}
$overflow = strlen($plaintext) % $block_size;
if ($overflow) {
$plaintext2 = $this->_string_pop($plaintext, $overflow); // ie. trim $plaintext to a multiple of $block_size and put rest of $plaintext in $plaintext2
$encrypted = openssl_encrypt($plaintext . str_repeat("\0", $block_size), $this->cipher_name_openssl, $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $encryptIV);
$encryptIV = $this->_string_pop($encrypted, $block_size);
$ciphertext.= $encrypted . ($plaintext2 ^ $encryptIV);
$buffer['encrypted'] = substr($encryptIV, $overflow);
} else if (!strlen($buffer['encrypted'])) {
$ciphertext.= openssl_encrypt($plaintext . str_repeat("\0", $block_size), $this->cipher_name_openssl, $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $encryptIV);
$encryptIV = $this->_string_pop($ciphertext, $block_size);
}
$encryptIV = openssl_decrypt($encryptIV, $this->cipher_name_openssl_ecb, $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
if ($overflow) {
$this->_increment_str($encryptIV);
}
return $ciphertext;
}
/**
* OpenSSL OFB Processor
*
* PHP's OpenSSL bindings do not operate in continuous mode so we'll wrap around it. Since the keystream
* for OFB is the same for both encrypting and decrypting this function is re-used by both Crypt_Base::encrypt()
* and Crypt_Base::decrypt().
*
* @see Crypt_Base::encrypt()
* @see Crypt_Base::decrypt()
* @param String $plaintext
* @param String $encryptIV
* @param Array $buffer
* @return String
* @access private
*/
function _openssl_ofb_process($plaintext, &$encryptIV, &$buffer)
{
if (strlen($buffer['xor'])) {
$ciphertext = $plaintext ^ $buffer['xor'];
$buffer['xor'] = substr($buffer['xor'], strlen($ciphertext));
$plaintext = substr($plaintext, strlen($ciphertext));
} else {
$ciphertext = '';
}
$block_size = $this->block_size;
$len = strlen($plaintext);
$key = $this->key;
$overflow = $len % $block_size;
if (strlen($plaintext)) {
if ($overflow) {
$ciphertext.= openssl_encrypt(substr($plaintext, 0, -$overflow) . str_repeat("\0", $block_size), $this->cipher_name_openssl, $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $encryptIV);
$xor = $this->_string_pop($ciphertext, $block_size);
if ($this->continuousBuffer) {
$encryptIV = $xor;
}
$ciphertext.= $this->_string_shift($xor, $overflow) ^ substr($plaintext, -$overflow);
if ($this->continuousBuffer) {
$buffer['xor'] = $xor;
}
} else {
$ciphertext = openssl_encrypt($plaintext, $this->cipher_name_openssl, $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $encryptIV);
if ($this->continuousBuffer) {
$encryptIV = substr($ciphertext, -$block_size) ^ substr($plaintext, -$block_size);
}
}
}
return $ciphertext;
}
/**
* phpseclib <-> OpenSSL Mode Mapper
*
* May need to be overwritten by classes extending this one in some cases
*
* @return Integer
* @access private
*/
function _openssl_translate_mode()
{
switch ($this->mode) {
case CRYPT_MODE_ECB:
return 'ecb';
case CRYPT_MODE_CBC:
return 'cbc';
case CRYPT_MODE_CTR:
return 'ctr';
case CRYPT_MODE_CFB:
return 'cfb';
case CRYPT_MODE_OFB:
return 'ofb';
}
}
/**
* Pad "packets".
*
@ -1161,53 +1496,113 @@ class Crypt_Base
}
/**
* Sets the internal crypt engine
* Test for engine validity
*
* Will be called automatically on "__construct()", so normally it's not
* necessary to call setEngine() manually, but ie for debuging or testing.
* @see Crypt_Base::Crypt_Base()
* @param Integer $engine
* @access public
* @return Boolean
*/
function isValidEngine($engine)
{
switch ($engine) {
case CRYPT_MODE_OPENSSL:
$this->openssl_emulate_ctr = false;
$result = $this->cipher_name_openssl &&
extension_loaded('openssl') &&
version_compare(PHP_VERSION, '5.3.0');
if (!$result) {
return false;
}
$methods = openssl_get_cipher_methods();
if (in_array($this->cipher_name_openssl, $methods)) {
return true;
}
// not all of openssl's symmetric cipher's support ctr. for those
// that don't we'll emulate it
switch ($this->mode) {
case CRYPT_MODE_CTR:
if (in_array($this->cipher_name_openssl_ecb, $methods)) {
$this->openssl_emulate_ctr = true;
return true;
}
}
return false;
case CRYPT_MODE_MCRYPT:
return $this->cipher_name_mcrypt &&
extension_loaded('mcrypt') &&
in_array($this->cipher_name_mcrypt, mcrypt_list_algorithms());
case CRYPT_MODE_INTERNAL:
return true;
}
}
/**
* Sets the preferred crypt engine
*
* Currently, $engine could be:
*
* - CRYPT_MODE_OPENSSL [very fast]
*
* - CRYPT_MODE_MCRYPT [fast]
*
* - CRYPT_MODE_INTERNAL [slow]
*
* Respectivly the officially alias constants of the choosen cipher,
* ie for AES: CRYPT_AES_MODE_MCRYPT or CRYPT_AES_MODE_INTERNAL
*
* If $engine is not explictly set, the fastest available $engine
* will be set (currently: CRYPT_MODE_MCRYPT)
*
* If $engine == CRYPT_MODE_MCRYPT but the mcrypt extension is not loaded/available it
* will be set the next available fastest $engine (currently: CRYPT_MODE_INTERNAL)
*
* If called, all internal buffers and cipher states will be reset, so,
* for example, switching the $engine while enableContinuousBuffer() will
* reset the ContinuousBuffer's.
*
* setEngine() returns always the $engine which was effectively set.
* If the preferred crypt engine is not available the fastest available one will be used
*
* @see Crypt_Base::Crypt_Base()
* @param optional Integer $engine
* @param Integer $engine
* @access public
* @return Integer
* @internal Could, but not must, extend by the child Crypt_* class
*/
function setEngine($engine = CRYPT_MODE_MCRYPT)
function setPreferredEngine($engine)
{
switch ($engine) {
//case CRYPT_MODE_OPENSSL:
case CRYPT_MODE_MCRYPT:
case CRYPT_MODE_INTERNAL:
$this->engine = CRYPT_MODE_INTERNAL;
$this->preferredEngine = $engine;
break;
default:
if ($this->cipher_name_mcrypt && extension_loaded('mcrypt') && in_array($this->cipher_name_mcrypt, mcrypt_list_algorithms())) {
$this->engine = CRYPT_MODE_MCRYPT;
} else {
$this->engine = CRYPT_MODE_INTERNAL;
}
$this->preferredEngine = CRYPT_MODE_OPENSSL;
}
if ($this->enmcrypt) {
$this->_setEngine();
}
/**
* Returns the engine currently being utilized
*
* @see Crypt_Base::_setEngine()
* @access public
*/
function getEngine()
{
return $this->engine;
}
/**
* Sets the engine as appropriate
*
* @see Crypt_Base::Crypt_Base()
* @access private
*/
function _setEngine()
{
switch (true) {
case $this->isValidEngine($this->preferredEngine):
$this->engine = $this->preferredEngine;
break;
case $this->isValidEngine(CRYPT_MODE_OPENSSL):
$this->engine = CRYPT_MODE_OPENSSL;
break;
case $this->isValidEngine(CRYPT_MODE_MCRYPT):
$this->engine = CRYPT_MODE_MCRYPT;
break;
default:
$this->engine = CRYPT_MODE_INTERNAL;
}
if ($this->engine != CRYPT_MODE_MCRYPT && $this->enmcrypt) {
// Closing the current mcrypt resource(s). _mcryptSetup() will, if needed,
// (re)open them with the module named in $this->cipher_name_mcrypt
mcrypt_module_close($this->enmcrypt);
@ -1222,7 +1617,6 @@ class Crypt_Base
}
$this->changed = true;
return $this->engine;
}
/**
@ -1443,7 +1837,7 @@ class Crypt_Base
* @access private
* @return String
*/
function _stringShift(&$string, $index = 1)
function _string_shift(&$string, $index = 1)
{
$substr = substr($string, 0, $index);
$string = substr($string, $index);
@ -1451,43 +1845,57 @@ class Crypt_Base
}
/**
* Generate CTR XOR encryption key
* String Pop
*
* Encrypt the output of this and XOR it against the ciphertext / plaintext to get the
* plaintext / ciphertext in CTR mode.
* Inspired by array_pop
*
* @param String $string
* @param optional Integer $index
* @access private
* @return String
*/
function _string_pop(&$string, $index = 1)
{
$substr = substr($string, -$index);
$string = substr($string, 0, -$index);
return $substr;
}
/**
* Increment the current string
*
* @see Crypt_Base::decrypt()
* @see Crypt_Base::encrypt()
* @param String $iv
* @param Integer $length
* @param String $var
* @access private
* @return String $xor
*/
function _generateXor(&$iv, $length)
function _increment_str(&$var)
{
$xor = '';
$block_size = $this->block_size;
$num_blocks = floor(($length + ($block_size - 1)) / $block_size);
for ($i = 0; $i < $num_blocks; $i++) {
$xor.= $iv;
for ($j = 4; $j <= $block_size; $j+= 4) {
$temp = substr($iv, -$j, 4);
switch ($temp) {
case "\xFF\xFF\xFF\xFF":
$iv = substr_replace($iv, "\x00\x00\x00\x00", -$j, 4);
break;
case "\x7F\xFF\xFF\xFF":
$iv = substr_replace($iv, "\x80\x00\x00\x00", -$j, 4);
break 2;
default:
extract(unpack('Ncount', $temp));
$iv = substr_replace($iv, pack('N', $count + 1), -$j, 4);
break 2;
}
for ($i = 4; $i <= strlen($var); $i+= 4) {
$temp = substr($var, -$i, 4);
switch ($temp) {
case "\xFF\xFF\xFF\xFF":
$var = substr_replace($var, "\x00\x00\x00\x00", -$i, 4);
break;
case "\x7F\xFF\xFF\xFF":
$var = substr_replace($var, "\x80\x00\x00\x00", -$i, 4);
return;
default:
$temp = unpack('Nnum', $temp);
$var = substr_replace($var, pack('N', $temp['num'] + 1), -$i, 4);
return;
}
}
return $xor;
$remainder = strlen($var) % 4;
if ($remainder == 0) {
return;
}
$temp = unpack('Nnum', str_pad(substr($var, 0, $remainder), 4, "\0", STR_PAD_LEFT));
$temp = substr(pack('N', $temp['num'] + 1), -$remainder);
$var = substr_replace($var, $temp, 0, $remainder);
}
/**
@ -1691,7 +2099,6 @@ class Crypt_Base
case CRYPT_MODE_ECB:
$encrypt = $init_encrypt . '
$_ciphertext = "";
$_text = $self->_pad($_text);
$_plaintext_len = strlen($_text);
for ($_i = 0; $_i < $_plaintext_len; $_i+= '.$block_size.') {
@ -1723,23 +2130,24 @@ class Crypt_Base
$_plaintext_len = strlen($_text);
$_xor = $self->encryptIV;
$_buffer = &$self->enbuffer;
if (strlen($_buffer["encrypted"])) {
for ($_i = 0; $_i < $_plaintext_len; $_i+= '.$block_size.') {
$_block = substr($_text, $_i, '.$block_size.');
if (strlen($_block) > strlen($_buffer["encrypted"])) {
$in = $self->_generateXor($_xor, '.$block_size.');
$in = $_xor;
'.$encrypt_block.'
$self->_increment_str($_xor);
$_buffer["encrypted"].= $in;
}
$_key = $self->_stringShift($_buffer["encrypted"], '.$block_size.');
$_key = $self->_string_shift($_buffer["encrypted"], '.$block_size.');
$_ciphertext.= $_block ^ $_key;
}
} else {
for ($_i = 0; $_i < $_plaintext_len; $_i+= '.$block_size.') {
$_block = substr($_text, $_i, '.$block_size.');
$in = $self->_generateXor($_xor, '.$block_size.');
$in = $_xor;
'.$encrypt_block.'
$self->_increment_str($_xor);
$_key = $in;
$_ciphertext.= $_block ^ $_key;
}
@ -1764,18 +2172,20 @@ class Crypt_Base
for ($_i = 0; $_i < $_ciphertext_len; $_i+= '.$block_size.') {
$_block = substr($_text, $_i, '.$block_size.');
if (strlen($_block) > strlen($_buffer["ciphertext"])) {
$in = $self->_generateXor($_xor, '.$block_size.');
$in = $_xor;
'.$encrypt_block.'
$self->_increment_str($_xor);
$_buffer["ciphertext"].= $in;
}
$_key = $self->_stringShift($_buffer["ciphertext"], '.$block_size.');
$_key = $self->_string_shift($_buffer["ciphertext"], '.$block_size.');
$_plaintext.= $_block ^ $_key;
}
} else {
for ($_i = 0; $_i < $_ciphertext_len; $_i+= '.$block_size.') {
$_block = substr($_text, $_i, '.$block_size.');
$in = $self->_generateXor($_xor, '.$block_size.');
$in = $_xor;
'.$encrypt_block.'
$self->_increment_str($_xor);
$_key = $in;
$_plaintext.= $_block ^ $_key;
}
@ -1905,7 +2315,7 @@ class Crypt_Base
$_xor = $in;
$_buffer["xor"].= $_xor;
}
$_key = $self->_stringShift($_buffer["xor"], '.$block_size.');
$_key = $self->_string_shift($_buffer["xor"], '.$block_size.');
$_ciphertext.= $_block ^ $_key;
}
} else {
@ -1941,7 +2351,7 @@ class Crypt_Base
$_xor = $in;
$_buffer["xor"].= $_xor;
}
$_key = $self->_stringShift($_buffer["xor"], '.$block_size.');
$_key = $self->_string_shift($_buffer["xor"], '.$block_size.');
$_plaintext.= $_block ^ $_key;
}
} else {
@ -1978,7 +2388,6 @@ class Crypt_Base
default:
$encrypt = $init_encrypt . '
$_ciphertext = "";
$_text = $self->_pad($_text);
$_plaintext_len = strlen($_text);
$in = $self->encryptIV;
@ -2058,7 +2467,7 @@ class Crypt_Base
define('CRYPT_BASE_WHIRLPOOL_AVAILABLE', (bool)(extension_loaded('hash') && in_array('whirlpool', hash_algos())));
}
// return pack('H*', md5($bytes) . sha1($bytes) . (CRYPT_BASE_WHIRLPOOL_AVAILABLE ? hash('whirlpool', $bytes) : '')); // Alternativ
// return pack('H*', md5($bytes) . sha1($bytes) . (CRYPT_BASE_WHIRLPOOL_AVAILABLE ? hash('whirlpool', $bytes) : '')); // Alternative
$result = '';
$hash = $bytes;

View File

@ -101,20 +101,6 @@ define('CRYPT_BLOWFISH_MODE_CFB', CRYPT_MODE_CFB);
define('CRYPT_BLOWFISH_MODE_OFB', CRYPT_MODE_OFB);
/**#@-*/
/**#@+
* @access private
* @see Crypt_Base::Crypt_Base()
*/
/**
* Toggles the internal implementation
*/
define('CRYPT_BLOWFISH_MODE_INTERNAL', CRYPT_MODE_INTERNAL);
/**
* Toggles the mcrypt implementation
*/
define('CRYPT_BLOWFISH_MODE_MCRYPT', CRYPT_MODE_MCRYPT);
/**#@-*/
/**
* Pure-PHP implementation of Blowfish.
*
@ -384,7 +370,6 @@ class Crypt_Blowfish extends Crypt_Base
* @param String $key
*/
function setKey($key)
{
$keylength = strlen($key);
if (!$keylength) {
@ -396,6 +381,29 @@ class Crypt_Blowfish extends Crypt_Base
parent::setKey($key);
}
/**
* Test for engine validity
*
* This is mainly just a wrapper to set things up for Crypt_Base::isValidEngine()
*
* @see Crypt_Base::Crypt_Base()
* @param Integer $engine
* @access public
* @return Boolean
*/
function isValidEngine($engine)
{
if ($engine == CRYPT_MODE_OPENSSL) {
if (strlen($this->key) != 16) {
return false;
}
$this->cipher_name_openssl_ecb = 'bf-ecb';
$this->cipher_name_openssl = 'bf-' . $this->_openssl_translate_mode();
}
return parent::isValidEngine($engine);
}
/**
* Setup the key (expansion)
*

View File

@ -121,20 +121,6 @@ define('CRYPT_DES_MODE_CFB', CRYPT_MODE_CFB);
define('CRYPT_DES_MODE_OFB', CRYPT_MODE_OFB);
/**#@-*/
/**#@+
* @access private
* @see Crypt_Base::Crypt_Base()
*/
/**
* Toggles the internal implementation
*/
define('CRYPT_DES_MODE_INTERNAL', CRYPT_MODE_INTERNAL);
/**
* Toggles the mcrypt implementation
*/
define('CRYPT_DES_MODE_MCRYPT', CRYPT_MODE_MCRYPT);
/**#@-*/
/**
* Pure-PHP implementation of DES.
*
@ -192,6 +178,21 @@ class Crypt_DES extends Crypt_Base
*/
var $cipher_name_mcrypt = 'des';
/**
* The OpenSSL names of the cipher / modes
*
* @see Crypt_Base::openssl_mode_names
* @var Array
* @access private
*/
var $openssl_mode_names = array(
CRYPT_MODE_ECB => 'des-ecb',
CRYPT_MODE_CBC => 'des-cbc',
CRYPT_MODE_CFB => 'des-cfb',
CRYPT_MODE_OFB => 'des-ofb'
// CRYPT_MODE_CTR is undefined for DES
);
/**
* Optimizing value while CFB-encrypting
*
@ -662,6 +663,26 @@ class Crypt_DES extends Crypt_Base
0x00000820, 0x00020020, 0x08000000, 0x08020800
);
/**
* Test for engine validity
*
* This is mainly just a wrapper to set things up for Crypt_Base::isValidEngine()
*
* @see Crypt_Base::Crypt_Base()
* @param Integer $engine
* @access public
* @return Boolean
*/
function isValidEngine($engine)
{
if ($engine == CRYPT_MODE_OPENSSL) {
$this->cipher_name_openssl_ecb = 'des-ecb';
$this->cipher_name_openssl = 'des-' . $this->_openssl_translate_mode();
}
return parent::isValidEngine($engine);
}
/**
* Sets the key.
*

View File

@ -99,20 +99,6 @@ define('CRYPT_RC2_MODE_CFB', CRYPT_MODE_CFB);
define('CRYPT_RC2_MODE_OFB', CRYPT_MODE_OFB);
/**#@-*/
/**#@+
* @access public
* @see Crypt_RC2::Crypt_RC2()
*/
/**
* Toggles the internal implementation
*/
define('CRYPT_RC2_MODE_INTERNAL', CRYPT_MODE_INTERNAL);
/**
* Toggles the mcrypt implementation
*/
define('CRYPT_RC2_MODE_MCRYPT', CRYPT_MODE_MCRYPT);
/**#@-*/
/**
* Pure-PHP implementation of RC2.
*
@ -141,6 +127,18 @@ class Crypt_RC2 extends Crypt_Base
*/
var $key;
/**
* The Original (unpadded) Key
*
* @see Crypt_Base::key
* @see setKey()
* @see encrypt()
* @see decrypt()
* @var String
* @access private
*/
var $orig_key;
/**
* The default password key_size used by setPassword()
*
@ -190,6 +188,17 @@ class Crypt_RC2 extends Crypt_Base
*/
var $default_key_length = 1024;
/**
* The key length in bits.
*
* @see Crypt_RC2::isValidEnine()
* @see Crypt_RC2::setKey()
* @var Integer
* @access private
* @internal Should be in range [1..1024].
*/
var $current_key_length;
/**
* The Key Schedule
*
@ -344,6 +353,30 @@ class Crypt_RC2 extends Crypt_Base
parent::Crypt_Base($mode);
}
/**
* Test for engine validity
*
* This is mainly just a wrapper to set things up for Crypt_Base::isValidEngine()
*
* @see Crypt_Base::Crypt_Base()
* @param Integer $engine
* @access public
* @return Boolean
*/
function isValidEngine($engine)
{
switch ($engine) {
case CRYPT_MODE_OPENSSL:
if ($this->current_key_length != 128 && strlen($this->orig_key) != 16) {
return false;
}
$this->cipher_name_openssl_ecb = 'rc2-ecb';
$this->cipher_name_openssl = 'rc2-' . $this->_openssl_translate_mode();
}
return parent::isValidEngine($engine);
}
/**
* Sets the key length
*
@ -375,15 +408,18 @@ class Crypt_RC2 extends Crypt_Base
* @see Crypt_Base::setKey()
* @access public
* @param String $key
* @param Integer $t1 optional Effective key length in bits.
* @param Integer $t1 optional Effective key length in bits.
*/
function setKey($key, $t1 = 0)
{
$this->orig_key = $key;
if ($t1 <= 0) {
$t1 = $this->default_key_length;
} else if ($t1 > 1024) {
$t1 = 1024;
}
$this->current_key_length = $t1;
// Key byte count should be 1..128.
$key = strlen($key) ? substr($key, 0, 128) : "\x00";
$t = strlen($key);
@ -416,6 +452,52 @@ class Crypt_RC2 extends Crypt_Base
parent::setKey(call_user_func_array('pack', $l));
}
/**
* Encrypts a message.
*
* Mostly a wrapper for Crypt_Base::encrypt, with some additional OpenSSL handling code
*
* @see decrypt()
* @access public
* @param String $plaintext
* @return String $ciphertext
*/
function encrypt($plaintext)
{
if ($this->engine == CRYPT_MODE_OPENSSL) {
$temp = $this->key;
$this->key = $this->orig_key;
$result = parent::encrypt($plaintext);
$this->key = $temp;
return $result;
}
return parent::encrypt($plaintext);
}
/**
* Decrypts a message.
*
* Mostly a wrapper for Crypt_Base::decrypt, with some additional OpenSSL handling code
*
* @see encrypt()
* @access public
* @param String $ciphertext
* @return String $plaintext
*/
function decrypt($ciphertext)
{
if ($this->engine == CRYPT_MODE_OPENSSL) {
$temp = $this->key;
$this->key = $this->orig_key;
$result = parent::decrypt($ciphertext);
$this->key = $temp;
return $result;
}
return parent::encrypt($ciphertext);
}
/**
* Encrypts a block
*

View File

@ -69,20 +69,6 @@ if (!class_exists('Crypt_Base')) {
include_once 'Base.php';
}
/**#@+
* @access public
* @see Crypt_RC4::Crypt_RC4()
*/
/**
* Toggles the internal implementation
*/
define('CRYPT_RC4_MODE_INTERNAL', CRYPT_MODE_INTERNAL);
/**
* Toggles the mcrypt implementation
*/
define('CRYPT_RC4_MODE_MCRYPT', CRYPT_MODE_MCRYPT);
/**#@-*/
/**#@+
* @access private
* @see Crypt_RC4::_crypt()
@ -182,6 +168,38 @@ class Crypt_RC4 extends Crypt_Base
parent::Crypt_Base(CRYPT_MODE_STREAM);
}
/**
* Test for engine validity
*
* This is mainly just a wrapper to set things up for Crypt_Base::isValidEngine()
*
* @see Crypt_Base::Crypt_Base()
* @param Integer $engine
* @access public
* @return Boolean
*/
function isValidEngine($engine)
{
switch ($engine) {
case CRYPT_MODE_OPENSSL:
switch (strlen($this->key)) {
case 5:
$this->cipher_name_openssl = 'rc4-40';
break;
case 8:
$this->cipher_name_openssl = 'rc4-64';
break;
case 16:
$this->cipher_name_openssl = 'rc4';
break;
default:
return false;
}
}
return parent::isValidEngine($engine);
}
/**
* Dummy function.
*

View File

@ -118,20 +118,6 @@ define('CRYPT_RIJNDAEL_MODE_CFB', CRYPT_MODE_CFB);
define('CRYPT_RIJNDAEL_MODE_OFB', CRYPT_MODE_OFB);
/**#@-*/
/**#@+
* @access private
* @see Crypt_Base::Crypt_Base()
*/
/**
* Toggles the internal implementation
*/
define('CRYPT_RIJNDAEL_MODE_INTERNAL', CRYPT_MODE_INTERNAL);
/**
* Toggles the mcrypt implementation
*/
define('CRYPT_RIJNDAEL_MODE_MCRYPT', CRYPT_MODE_MCRYPT);
/**#@-*/
/**
* Pure-PHP implementation of Rijndael.
*
@ -171,7 +157,7 @@ class Crypt_Rijndael extends Crypt_Base
*
* @see Crypt_Base::cipher_name_mcrypt
* @see Crypt_Base::engine
* @see _setupEngine()
* @see isValidEngine()
* @var String
* @access private
*/
@ -323,8 +309,6 @@ class Crypt_Rijndael extends Crypt_Base
*/
function setKey($key)
{
parent::setKey($key);
if (!$this->explicit_key_length) {
$length = strlen($key);
switch (true) {
@ -343,8 +327,8 @@ class Crypt_Rijndael extends Crypt_Base
default:
$this->key_size = 32;
}
$this->_setupEngine();
}
parent::setKey($key);
}
/**
@ -388,7 +372,7 @@ class Crypt_Rijndael extends Crypt_Base
$this->explicit_key_length = true;
$this->changed = true;
$this->_setupEngine();
$this->_setEngine();
}
/**
@ -411,33 +395,38 @@ class Crypt_Rijndael extends Crypt_Base
$this->Nb = $length;
$this->block_size = $length << 2;
$this->changed = true;
$this->_setupEngine();
$this->_setEngine();
}
/**
* Setup the fastest possible $engine
* Test for engine validity
*
* Determines if the mcrypt (MODE_MCRYPT) $engine available
* and usable for the current $block_size and $key_size.
* This is mainly just a wrapper to set things up for Crypt_Base::isValidEngine()
*
* If not, the slower MODE_INTERNAL $engine will be set.
*
* @see setKey()
* @see setKeyLength()
* @see setBlockLength()
* @access private
* @see Crypt_Base::Crypt_Base()
* @param Integer $engine
* @access public
* @return Boolean
*/
function _setupEngine()
function isValidEngine($engine)
{
// Set the mcrypt module name for the current $block_size of rijndael
$this->cipher_name_mcrypt = 'rijndael-' . ($this->block_size << 3);
// Set the engine
if ($this->key_size % 8) { // is it a 160/224-bit key?
// mcrypt is not usable for them, only for 128/192/256-bit keys
// so we are forced to set the slower MODE_INTERNAL $engine
$this->setEngine(CRYPT_MODE_INTERNAL);
switch ($engine) {
case CRYPT_MODE_OPENSSL:
if ($this->block_size != 16) {
return false;
}
$this->cipher_name_openssl_ecb = 'aes-' . ($this->key_size << 3) . '-ecb';
$this->cipher_name_openssl = 'aes-' . ($this->key_size << 3) . '-' . $this->_openssl_translate_mode();
break;
case CRYPT_MODE_MCRYPT:
$this->cipher_name_mcrypt = 'rijndael-' . ($this->block_size << 3);
if ($this->key_size % 8) { // is it a 160/224-bit key?
// mcrypt is not usable for them, only for 128/192/256-bit keys
return false;
}
}
return parent::isValidEngine($engine);
}
/**

View File

@ -218,6 +218,27 @@ class Crypt_TripleDES extends Crypt_DES
}
}
/**
* Test for engine validity
*
* This is mainly just a wrapper to set things up for Crypt_Base::isValidEngine()
*
* @see Crypt_Base::Crypt_Base()
* @param Integer $engine
* @access public
* @return Boolean
*/
function isValidEngine($engine)
{
if ($engine == CRYPT_MODE_OPENSSL) {
$this->cipher_name_openssl_ecb = 'des-ede3';
$mode = $this->_openssl_translate_mode();
$this->cipher_name_openssl = $mode == 'ecb' ? 'des-ede3' : 'des-ede3-' . $mode;
}
return parent::isValidEngine($engine);
}
/**
* Sets the initialization vector. (optional)
*
@ -260,7 +281,7 @@ class Crypt_TripleDES extends Crypt_DES
$key = str_pad(substr($key, 0, 24), 24, chr(0));
// if $key is between 64 and 128-bits, use the first 64-bits as the last, per this:
// http://php.net/function.mcrypt-encrypt#47973
//$key = $length <= 16 ? substr_replace($key, substr($key, 0, 8), 16) : substr($key, 0, 24);
$key = $length <= 16 ? substr_replace($key, substr($key, 0, 8), 16) : substr($key, 0, 24);
} else {
$key = str_pad($key, 8, chr(0));
}

View File

@ -101,20 +101,6 @@ define('CRYPT_TWOFISH_MODE_CFB', CRYPT_MODE_CFB);
define('CRYPT_TWOFISH_MODE_OFB', CRYPT_MODE_OFB);
/**#@-*/
/**#@+
* @access private
* @see Crypt_Base::Crypt_Base()
*/
/**
* Toggles the internal implementation
*/
define('CRYPT_TWOFISH_MODE_INTERNAL', CRYPT_MODE_INTERNAL);
/**
* Toggles the mcrypt implementation
*/
define('CRYPT_TWOFISH_MODE_MCRYPT', CRYPT_MODE_MCRYPT);
/**#@-*/
/**
* Pure-PHP implementation of Twofish.
*

View File

@ -7,10 +7,8 @@
class Unit_Crypt_AES_InternalTest extends Unit_Crypt_AES_TestCase
{
static public function setUpBeforeClass()
protected function setUp()
{
self::$engine = CRYPT_MODE_INTERNAL;
parent::setUpBeforeClass();
$this->engine = CRYPT_MODE_INTERNAL;
}
}

View File

@ -7,14 +7,8 @@
class Unit_Crypt_AES_McryptTest extends Unit_Crypt_AES_TestCase
{
static public function setUpBeforeClass()
protected function setUp()
{
if (!extension_loaded('mcrypt')) {
self::markTestSkipped('mcrypt extension is not available.');
}
self::$engine = CRYPT_MODE_MCRYPT;
parent::setUpBeforeClass();
$this->engine = CRYPT_MODE_MCRYPT;
}
}

View File

@ -0,0 +1,14 @@
<?php
/**
* @author Andreas Fischer <bantu@phpbb.com>
* @copyright MMXIII Andreas Fischer
* @license http://www.opensource.org/licenses/mit-license.html MIT License
*/
class Unit_Crypt_AES_OpenSSLTest extends Unit_Crypt_AES_TestCase
{
protected function setUp()
{
$this->engine = CRYPT_MODE_OPENSSL;
}
}

View File

@ -9,11 +9,21 @@ require_once 'Crypt/AES.php';
abstract class Unit_Crypt_AES_TestCase extends PhpseclibTestCase
{
protected static $engine;
protected $engine;
static public function setUpBeforeClass()
private function checkEngine($aes)
{
parent::setUpBeforeClass();
if ($aes->getEngine() != $this->engine) {
$engine = 'internal';
switch ($this->engine) {
case CRYPT_MODE_OPENSSL:
$engine = 'OpenSSL';
break;
case CRYPT_MODE_MCRYPT:
$engine = 'mcrypt';
}
self::markTestSkipped('Unable to initialize ' . $engine . ' engine');
}
}
/**
@ -63,11 +73,13 @@ abstract class Unit_Crypt_AES_TestCase extends PhpseclibTestCase
public function testEncryptDecryptWithContinuousBuffer($mode, $plaintext, $iv, $key)
{
$aes = new Crypt_AES(constant($mode));
$aes->setEngine(self::$engine);
$aes->setPreferredEngine($this->engine);
$aes->enableContinuousBuffer();
$aes->setIV($iv);
$aes->setKey($key);
$this->checkEngine($aes);
$actual = '';
for ($i = 0, $strlen = strlen($plaintext); $i < $strlen; ++$i) {
$actual .= $aes->decrypt($aes->encrypt($plaintext[$i]));
@ -85,9 +97,10 @@ abstract class Unit_Crypt_AES_TestCase extends PhpseclibTestCase
// https://web.archive.org/web/20070209120224/http://fp.gladman.plus.com/cryptography_technology/rijndael/aesdvec.zip
$aes = new Crypt_Rijndael();
$aes->setEngine(self::$engine);
$aes->setPreferredEngine($this->engine);
$aes->disablePadding();
$aes->setKey(pack('H*', '2b7e151628aed2a6abf7158809cf4f3c762e7160')); // 160-bit key. Valid in Rijndael.
//$this->checkEngine($aes); // should only work in internal mode
$ciphertext = $aes->encrypt(pack('H*', '3243f6a8885a308d313198a2e0370734'));
$this->assertEquals($ciphertext, pack('H*', '231d844639b31b412211cfe93712b880'));
}
@ -100,10 +113,96 @@ abstract class Unit_Crypt_AES_TestCase extends PhpseclibTestCase
// same as the above - just with a different ciphertext
$aes = new Crypt_AES();
$aes->setEngine(self::$engine);
$aes->setPreferredEngine($this->engine);
$aes->disablePadding();
$aes->setKey(pack('H*', '2b7e151628aed2a6abf7158809cf4f3c762e7160')); // 160-bit key. AES should null pad to 192-bits
$this->checkEngine($aes);
$ciphertext = $aes->encrypt(pack('H*', '3243f6a8885a308d313198a2e0370734'));
$this->assertEquals($ciphertext, pack('H*', 'c109292b173f841b88e0ee49f13db8c0'));
}
}
/**
* Produces all combinations of test values.
*
* @return array
*/
public function continuousBufferBatteryCombos()
{
$modes = array(
'CRYPT_MODE_CTR',
'CRYPT_MODE_OFB',
'CRYPT_MODE_CFB',
);
$combos = array(
array(16),
array(17),
array(1, 16),
array(3, 6, 7), // (3 to test the openssl_encrypt call and the buffer creation, 6 to test the exclusive use of the buffer and 7 to test the buffer's exhaustion and recreation)
array(15, 4), // (15 to test openssl_encrypt call and buffer creation and 4 to test something that spans multpile bloc
array(3, 6, 10, 16), // this is why the strlen check in the buffer-only code was needed
array(16, 16), // two full size blocks
array(3, 6, 7, 16), // partial block + full size block
array(16, 3, 6, 7),
// a few others just for fun
array(32,32),
array(31,31),
array(17,17),
array(99, 99)
);
$result = array();
// @codingStandardsIgnoreStart
foreach ($modes as $mode)
foreach ($combos as $combo)
foreach (array('encrypt', 'decrypt') as $op)
$result[] = array($op, $mode, $combo);
// @codingStandardsIgnoreEnd
return $result;
}
/**
* @dataProvider continuousBufferBatteryCombos
*/
public function testContinuousBufferBattery($op, $mode, $test)
{
$iv = str_repeat('x', 16);
$key = str_repeat('a', 16);
$aes = new Crypt_AES(constant($mode));
$aes->setPreferredEngine($this->engine);
$aes->setKey($key);
$aes->setIV($iv);
$this->checkEngine($aes);
$str = '';
$result = '';
foreach ($test as $len) {
$temp = str_repeat('d', $len);
$str.= $temp;
}
$c1 = $aes->$op($str);
$aes = new Crypt_AES(constant($mode));
$aes->setPreferredEngine($this->engine);
$aes->enableContinuousBuffer();
$aes->setKey($key);
$aes->setIV($iv);
$this->checkEngine($aes);
foreach ($test as $len) {
$temp = str_repeat('d', $len);
$output = $aes->$op($temp);
$result.= $output;
}
$c2 = $result;
$this->assertSame(bin2hex($c1), bin2hex($c2));
}
}

69
tests/Unit/Crypt/DES.php Normal file
View File

@ -0,0 +1,69 @@
<?php
/**
* @author Andreas Fischer <bantu@phpbb.com>
* @copyright MMXIII Andreas Fischer
* @license http://www.opensource.org/licenses/mit-license.html MIT License
*/
require_once 'Crypt/DES.php';
// the AES tests establish the correctness of the modes of operation. this test is inteded to establish the consistency of
// key and iv padding between the multiple engines
class Unit_Crypt_DES_TestCase extends PhpseclibTestCase
{
public function testEncryptPadding()
{
$des = new Crypt_DES(CRYPT_MODE_CBC);
$des->setKey('d');
$des->setIV('d');
$des->setPreferredEngine(CRYPT_MODE_INTERNAL);
$internal = $des->encrypt('d');
$des->setPreferredEngine(CRYPT_MODE_MCRYPT);
if ($des->getEngine() == CRYPT_MODE_MCRYPT) {
$mcrypt = $des->encrypt('d');
$this->assertEquals($internal, $mcrypt, 'Failed asserting that the internal and mcrypt engines produce identical results');
} else {
self::markTestSkipped('Unable to initialize mcrypt engine');
}
$des->setPreferredEngine(CRYPT_MODE_OPENSSL);
if ($des->getEngine() == CRYPT_MODE_OPENSSL) {
$openssl = $des->encrypt('d');
$this->assertEquals($internal, $openssl, 'Failed asserting that the internal and OpenSSL engines produce identical results');
} else {
self::markTestSkipped('Unable to initialize OpenSSL engine');
}
}
// phpseclib null pads ciphertext's if they're not long enough and you're in ecb / cbc mode. this silent failure mode is consistent
// with mcrypt's behavior. maybe throwing an exception would be better but whatever. this test is more intended to establish consistent
// behavior between the various engine's
public function testDecryptPadding()
{
$des = new Crypt_DES(CRYPT_MODE_CBC);
// when the key and iv are not specified they should be null padded
//$des->setKey();
//$des->setIV();
$des->setPreferredEngine(CRYPT_MODE_INTERNAL);
$internal = $des->decrypt('d');
$des->setPreferredEngine(CRYPT_MODE_MCRYPT);
if ($des->getEngine() == CRYPT_MODE_MCRYPT) {
$mcrypt = $des->decrypt('d');
$this->assertEquals($internal, $mcrypt, 'Failed asserting that the internal and mcrypt engines produce identical results');
} else {
self::markTestSkipped('Unable to initialize mcrypt engine');
}
$des->setPreferredEngine(CRYPT_MODE_OPENSSL);
if ($des->getEngine() == CRYPT_MODE_OPENSSL) {
$openssl = $des->decrypt('d');
$this->assertEquals($internal, $openssl, 'Failed asserting that the internal and OpenSSL engines produce identical results');
} else {
self::markTestSkipped('Unable to initialize OpenSSL engine');
}
}
}

71
tests/Unit/Crypt/RC2.php Normal file
View File

@ -0,0 +1,71 @@
<?php
/**
* @author Andreas Fischer <bantu@phpbb.com>
* @copyright MMXIII Andreas Fischer
* @license http://www.opensource.org/licenses/mit-license.html MIT License
*/
require_once 'Crypt/RC2.php';
// this test is just confirming RC2's key expansion
class Unit_Crypt_RC2_TestCase extends PhpseclibTestCase
{
public function testEncryptPadding()
{
$rc2 = new Crypt_RC2(CRYPT_MODE_ECB);
// unlike Crypt_AES / Crypt_Rijndael, when you tell Crypt_RC2 that the key length is 128-bits the key isn't null padded to that length.
// instead, RC2 key expansion is used to extend it out to that length. this isn't done for AES / Rijndael since that doesn't define any
// sort of key expansion algorithm.
// admittedly, phpseclib is inconsistent in this regard. RC4 and Blowfish support arbitrary key lengths between a certain range, as well,
// and they don't have any way to set the key length. but then again, neither do those algorithms have their own key expansion algorithm,
// whereas RC2 does. and technically, AES / Rijndael (and even Twofish) don't support arbitrary key lengths - they support variable key
// lengths. so in some ways, i suppose this inconsistency somewhat makes sense, although the fact that Crypt_Twofish doesn't have a
// setKeyLength() function whereas Crypt_AES / Crypt_Rijndael do not is, itself, an inconsistency.
// but that said, Crypt_RC2 is inconsistent in other ways: if you pass a 128-bit (16-byte) key to it via setKey() the key is not treated
// as a 128-bit key but rather as a 1024-bit key and is expanded accordingly, not via null padding, but via RC2's key expansion algorithm.
// this behavior is in contrast to mcrypt, which extends keys via null padding to 1024 bits. it is also in contrast to OpenSSL, which
// extends keys, via null padding, to 128 bits. mcrypt's approach seems preferable as one can simulate 128 bit keys by using RC2's
// key expansion algorithm to extend the key to 1024 bits and then changing the first byte of the new key with an inverse pitable mapping.
// in contrast, to my knowledge, there is no technique for expanding a key less than 128 bits to 128 bits, via RC2 key expansion. the only
// scenario in that regard is null padding.
// simple truncation is insufficient, since, quoting RFC2268, "the purpose of th key-expansion algorithm [in RC2] is to modify the key buffer
// so that each bit of the expanded key depends in a complicated way on every bit of the supplied input key".
// now, to OpenSSL's credit, null padding is internally consistent with OpenSSL. OpenSSL only supports fixed length keys. For rc2, rc4 and
// bf (blowfish), all keys are 128 bits (or are null padded / truncated accordingly). to use 40-bit or 64-bit keys with RC4 with OpenSSL you
// don't use the rc4 algorithm - you use the rc4-40 or rc4-64 algorithm. and similarily, it's not aes-cbc that you use - it's either aes-128-cbc
// or aes-192-cbc or aes-256-cbc. this is in contrast to mcrypt, which (with the exception of RC2) actually supports variable and arbitrary
// length keys.
// superficially, it seens like Rijndael would be another exception to mcrypt's key length handling, but it in fact is not. the reason being that,
// with mcrypt, when you specify MCRYPT_RIJNDAEL_128 or MCRYPT_RIJNDAEL_192 or MCRYPT_RIJNDAEL_256 the numbers at the end aren't referring to the
// key length, but rather, the block length. ie. Rijndael, unlike most block ciphers, doesn't just have a variable (but not arbitrary) key length -
// it also has a variable block length. AES's block length, however, is not variable, so technically, only MCRYPT_RIJNDAEL_128 is AES.
$rc2->setKey(str_repeat('d', 16), 128);
$rc2->setPreferredEngine(CRYPT_MODE_INTERNAL);
$internal = $rc2->encrypt('d');
$rc2->setPreferredEngine(CRYPT_MODE_MCRYPT);
if ($rc2->getEngine() == CRYPT_MODE_MCRYPT) {
$mcrypt = $rc2->encrypt('d');
$this->assertEquals($internal, $mcrypt, 'Failed asserting that the internal and mcrypt engines produce identical results');
} else {
self::markTestSkipped('Unable to initialize mcrypt engine');
}
$rc2->setPreferredEngine(CRYPT_MODE_OPENSSL);
if ($rc2->getEngine() == CRYPT_MODE_OPENSSL) {
$openssl = $rc2->encrypt('d');
$this->assertEquals($internal, $openssl, 'Failed asserting that the internal and OpenSSL engines produce identical results');
} else {
self::markTestSkipped('Unable to initialize OpenSSL engine');
}
}
}