diff --git a/phpseclib/Crypt/AES.php b/phpseclib/Crypt/AES.php index de006e1e..36364398 100644 --- a/phpseclib/Crypt/AES.php +++ b/phpseclib/Crypt/AES.php @@ -206,7 +206,7 @@ class Crypt_AES extends Crypt_Rijndael default: $this->key_size = 32; } - $this->_setupEngine(); + $this->_setEngine(); } } } diff --git a/phpseclib/Crypt/Base.php b/phpseclib/Crypt/Base.php index bf4b4585..d8be982b 100644 --- a/phpseclib/Crypt/Base.php +++ b/phpseclib/Crypt/Base.php @@ -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 * @author Hans-Juergen Petrich - * @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; diff --git a/phpseclib/Crypt/Blowfish.php b/phpseclib/Crypt/Blowfish.php index 61e1db72..7dfa419b 100644 --- a/phpseclib/Crypt/Blowfish.php +++ b/phpseclib/Crypt/Blowfish.php @@ -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) * diff --git a/phpseclib/Crypt/DES.php b/phpseclib/Crypt/DES.php index 778767bb..395f9d30 100644 --- a/phpseclib/Crypt/DES.php +++ b/phpseclib/Crypt/DES.php @@ -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. * diff --git a/phpseclib/Crypt/RC2.php b/phpseclib/Crypt/RC2.php index 6a4c71db..f6857c7e 100644 --- a/phpseclib/Crypt/RC2.php +++ b/phpseclib/Crypt/RC2.php @@ -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 * diff --git a/phpseclib/Crypt/RC4.php b/phpseclib/Crypt/RC4.php index dca8dda9..4c473a01 100644 --- a/phpseclib/Crypt/RC4.php +++ b/phpseclib/Crypt/RC4.php @@ -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. * diff --git a/phpseclib/Crypt/Rijndael.php b/phpseclib/Crypt/Rijndael.php index 5f2b7519..18444e6c 100644 --- a/phpseclib/Crypt/Rijndael.php +++ b/phpseclib/Crypt/Rijndael.php @@ -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); } /** diff --git a/phpseclib/Crypt/TripleDES.php b/phpseclib/Crypt/TripleDES.php index 1945ab19..c75000ac 100644 --- a/phpseclib/Crypt/TripleDES.php +++ b/phpseclib/Crypt/TripleDES.php @@ -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)); } diff --git a/phpseclib/Crypt/Twofish.php b/phpseclib/Crypt/Twofish.php index bb024adc..c99f3ad8 100644 --- a/phpseclib/Crypt/Twofish.php +++ b/phpseclib/Crypt/Twofish.php @@ -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. * diff --git a/tests/Unit/Crypt/AES/InternalTest.php b/tests/Unit/Crypt/AES/InternalTest.php index 41801c85..73f39d0a 100644 --- a/tests/Unit/Crypt/AES/InternalTest.php +++ b/tests/Unit/Crypt/AES/InternalTest.php @@ -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; } } diff --git a/tests/Unit/Crypt/AES/McryptTest.php b/tests/Unit/Crypt/AES/McryptTest.php index 78902fc0..7830408b 100644 --- a/tests/Unit/Crypt/AES/McryptTest.php +++ b/tests/Unit/Crypt/AES/McryptTest.php @@ -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; } } diff --git a/tests/Unit/Crypt/AES/OpenSSLTest.php b/tests/Unit/Crypt/AES/OpenSSLTest.php new file mode 100644 index 00000000..ff950d08 --- /dev/null +++ b/tests/Unit/Crypt/AES/OpenSSLTest.php @@ -0,0 +1,14 @@ + + * @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; + } +} diff --git a/tests/Unit/Crypt/AES/TestCase.php b/tests/Unit/Crypt/AES/TestCase.php index 2a40450c..6e7f8d65 100644 --- a/tests/Unit/Crypt/AES/TestCase.php +++ b/tests/Unit/Crypt/AES/TestCase.php @@ -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)); + } +} \ No newline at end of file diff --git a/tests/Unit/Crypt/DES.php b/tests/Unit/Crypt/DES.php new file mode 100644 index 00000000..3b6d5a4b --- /dev/null +++ b/tests/Unit/Crypt/DES.php @@ -0,0 +1,69 @@ + + * @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'); + } + } +} \ No newline at end of file diff --git a/tests/Unit/Crypt/RC2.php b/tests/Unit/Crypt/RC2.php new file mode 100644 index 00000000..545df9b5 --- /dev/null +++ b/tests/Unit/Crypt/RC2.php @@ -0,0 +1,71 @@ + + * @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'); + } + } +} \ No newline at end of file