From fb8d185804dc1c42278401a4cae3df0088db407c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans-J=C3=BCrgen=20Petrich?= Date: Sun, 2 Jun 2013 16:21:32 +0700 Subject: [PATCH] Base/AES/Rijndael: Optimizations - Base/Rijndael/AES: Comments updated. - Base: removed __desctructor(). Re: https://github.com/phpseclib/phpseclib/issues/107 - Base: setPassword() will use >= php-5.5's new (faster) hash_pbkdf2() function if availalbe/possible. 100% compatible to the internal implementation. Strong testet with all hashes/iterations/lengths/ciphers. - Rijndael: Runtime engine-switch: Will now use mcrypt (in case of 128/192/256-bit block/keys), if possible. Otherwise MODE_INTERNAL. AES: Soucecode reducing. After all, now, AES.php is virtually nothing other than a wrapper to Rijndael.php::new Crypt_Rijndael()->setBlockLength(128). No different in speed or functionality, but fixed block_size. --- phpseclib/Crypt/AES.php | 74 +--------------- phpseclib/Crypt/Base.php | 84 ++++++++----------- phpseclib/Crypt/Rijndael.php | 158 +++++++++++++++++++++++++++++------ 3 files changed, 166 insertions(+), 150 deletions(-) diff --git a/phpseclib/Crypt/AES.php b/phpseclib/Crypt/AES.php index 19c72d9e..9b70e98d 100644 --- a/phpseclib/Crypt/AES.php +++ b/phpseclib/Crypt/AES.php @@ -4,13 +4,13 @@ /** * Pure-PHP implementation of AES. * - * Uses mcrypt, if available, and an internal implementation, otherwise. + * Uses mcrypt, if available/possible, and an internal implementation, otherwise. * * PHP versions 4 and 5 * * If {@link Crypt_AES::setKeyLength() setKeyLength()} isn't called, it'll be calculated from * {@link Crypt_AES::setKey() setKey()}. ie. if the key is 128-bits, the key length will be 128-bits. If it's 136-bits - * it'll be null-padded to 160-bits and 160 bits will be the key length until {@link Crypt_Rijndael::setKey() setKey()} + * it'll be null-padded to 192-bits and 192 bits will be the key length until {@link Crypt_AES::setKey() setKey()} * is called, again, at which point, it'll be recalculated. * * Since Crypt_AES extends Crypt_Rijndael, some functions are available to be called that, in the context of AES, don't @@ -141,15 +141,6 @@ class Crypt_AES extends Crypt_Rijndael { */ var $const_namespace = 'AES'; - /** - * The mcrypt specific name of the cipher - * - * @see Crypt_Base::cipher_name_mcrypt - * @var String - * @access private - */ - var $cipher_name_mcrypt = 'rijndael-128'; - /** * Default Constructor. * @@ -192,67 +183,6 @@ class Crypt_AES extends Crypt_Rijndael { { return; } - - /** - * Sets the key length - * - * Valid key lengths are 128, 160, 192, 224, and 256. If the length is less than 128, it will be rounded up to - * 128. If the length is greater than 128 and invalid, it will be rounded down to the closest valid amount. - * - * Note: phpseclib extends AES for using 160- and 224-bit keys but they are officially not defined in AES - * and the most (if not all) implementations of AES are not able using 160/224-bit keys but round/pad - * them up to 192/256 bits as, for example, mcrypt will do. - * - * That said, if you want be compatible with other AES implementations, - * you should not setKeyLength(160) or setKeyLength(224). - * - * Additional: In case of 160- and 224-bit keys, phpseclib will/can, for that reason, not use - * the mcrypt php extention, even if available. This results then in slower encryption. - * - * @access public - * @param Integer $length - */ - function setKeyLength($length) - { - parent::setKeyLength($length); - - switch ($this->key_size) { - case 20: // 160-bits - case 28: // 224-bits - $this->engine = CRYPT_AES_MODE_INTERNAL; // because mcrypt is not able to use (real) 160/224-bit keys - break; // we force using our internal AES engine instead of mcrypt. - default: - $this->engine = CRYPT_AES_MODE; - } - } - - /** - * Setup the CRYPT_MODE_MCRYPT $engine - * - * Validates all the variables. - * - * @see Crypt_Base::_setupMcrypt() - * @access private - */ - function _setupMcrypt() - { - if (!$this->explicit_key_length) { - $length = strlen($this->key); - switch (true) { - case $length <= 16: - $this->key_size = 16; - break; - case $length <= 24: - $this->key_size = 24; - break; - default: - $this->key_size = 32; - } - } - - $this->key = str_pad(substr($this->key, 0, $this->key_size), $this->key_size, "\0"); - parent::_setupMcrypt(); - } } // vim: ts=4:sw=4:et: diff --git a/phpseclib/Crypt/Base.php b/phpseclib/Crypt/Base.php index 68c5f9f3..895cf524 100644 --- a/phpseclib/Crypt/Base.php +++ b/phpseclib/Crypt/Base.php @@ -73,7 +73,7 @@ define('CRYPT_MODE_CFB', 3); define('CRYPT_MODE_OFB', 4); /** * Encrypt / decrypt using streaming mode. - * + * */ define('CRYPT_MODE_STREAM', 5); /**#@-*/ @@ -493,9 +493,6 @@ class Crypt_Base { if ($this->mode == CRYPT_MODE_ECB) { return; } - if ($this->iv === $iv) { - return; - } $this->iv = $iv; $this->changed = true; @@ -518,10 +515,6 @@ class Crypt_Base { */ function setKey($key) { - if ($this->key === $key) { - return; - } - $this->key = $key; $this->changed = true; } @@ -533,8 +526,11 @@ class Crypt_Base { * {@link http://en.wikipedia.org/wiki/PBKDF2 pbkdf2}: * $hash, $salt, $count, $dkLen * + * Where $hash (default = sha1) currently supports the following hashes: see: Crypt/Hash.php + * * Note: Could, but not must, extend by the child Crypt_* class * + * @see Crypt/Hash.php * @param String $password * @param optional String $method * @access public @@ -560,25 +556,34 @@ class Crypt_Base { // Keylength $dkLen = isset($func_args[5]) ? $func_args[5] : $this->password_key_size; - if (!class_exists('Crypt_Hash')) { - require_once('Crypt/Hash.php'); - } - - $i = 1; - while (strlen($key) < $dkLen) { - $hmac = new Crypt_Hash(); - $hmac->setHash($hash); - $hmac->setKey($password); - $f = $u = $hmac->hash($salt . pack('N', $i++)); - for ($j = 2; $j <= $count; ++$j) { - $u = $hmac->hash($u); - $f^= $u; - } - $key.= $f; + // Determining if php[>=5.5.0]'s hash_pbkdf2() function avail- and useable + switch (true) { + case !function_exists('hash_pbkdf2'): + case !function_exists('hash_algos'): + case !in_array($hash, hash_algos()): + if (!class_exists('Crypt_Hash')) { + require_once('Crypt/Hash.php'); + } + $i = 1; + while (strlen($key) < $dkLen) { + $hmac = new Crypt_Hash(); + $hmac->setHash($hash); + $hmac->setKey($password); + $f = $u = $hmac->hash($salt . pack('N', $i++)); + for ($j = 2; $j <= $count; ++$j) { + $u = $hmac->hash($u); + $f^= $u; + } + $key.= $f; + } + $key = substr($key, 0, $dkLen); + break; + default: + $key = hash_pbkdf2($hash, $password, $salt, $count, $dkLen, true); } } - $this->setKey(substr($key, 0, $dkLen)); + $this->setKey($key); } /** @@ -1415,19 +1420,19 @@ class Crypt_Base { * _setupInlineCrypt() would be called only if: * * - $engine == CRYPT_MODE_INTERNAL and - * + * * - $use_inline_crypt === true - * + * * - each time on _setup(), after(!) _setupKey() * - * + * * This ensures that _setupInlineCrypt() has allways a * full ready2go initializated internal cipher $engine state * where, for example, the keys allready expanded, * keys/block_size calculated and such. * * It is, each time if called, the responsibility of _setupInlineCrypt(): - * + * * - to set $this->inline_crypt to a valid and fully working callback function * as a (faster) replacement for encrypt() / decrypt() * @@ -1949,29 +1954,6 @@ class Crypt_Base { static $functions = array(); return $functions; } - - /** - * Class destructor. - * - * Will be called, automatically, if you're using PHP5. If you're using PHP4, call it yourself. Only really - * needs to be called if mcrypt is being used. - * - * @access public - */ - function __destruct() - { - if ($this->engine == CRYPT_MODE_MCRYPT) { - if (is_resource($this->enmcrypt)) { - mcrypt_module_close($this->enmcrypt); - } - if (is_resource($this->demcrypt)) { - mcrypt_module_close($this->demcrypt); - } - if (is_resource($this->ecb)) { - mcrypt_module_close($this->ecb); - } - } - } } // vim: ts=4:sw=4:et: diff --git a/phpseclib/Crypt/Rijndael.php b/phpseclib/Crypt/Rijndael.php index 6b09ff82..6c6278a1 100644 --- a/phpseclib/Crypt/Rijndael.php +++ b/phpseclib/Crypt/Rijndael.php @@ -4,14 +4,14 @@ /** * Pure-PHP implementation of Rijndael. * - * Does not use mcrypt, even when available, for reasons that are explained below. + * Uses mcrypt, if available/possible, and an internal implementation, otherwise. * * PHP versions 4 and 5 * * If {@link Crypt_Rijndael::setBlockLength() setBlockLength()} isn't called, it'll be assumed to be 128 bits. If * {@link Crypt_Rijndael::setKeyLength() setKeyLength()} isn't called, it'll be calculated from * {@link Crypt_Rijndael::setKey() setKey()}. ie. if the key is 128-bits, the key length will be 128-bits. If it's - * 136-bits it'll be null-padded to 160-bits and 160 bits will be the key length until + * 136-bits it'll be null-padded to 192-bits and 192 bits will be the key length until * {@link Crypt_Rijndael::setKey() setKey()} is called, again, at which point, it'll be recalculated. * * Not all Rijndael implementations may support 160-bits or 224-bits as the block length / key length. mcrypt, for example, @@ -21,6 +21,7 @@ * are first defined as valid key / block lengths in * {@link http://csrc.nist.gov/archive/aes/rijndael/Rijndael-ammended.pdf#page=44 Rijndael-ammended.pdf#page=44}: * Extensions: Other block and Cipher Key lengths. + * Note: Use of 160/224-bit Keys must be explicitly set by setKeyLength(160) respectively setKeyLength(224). * * {@internal The variable names are the same as those in * {@link http://www.csrc.nist.gov/publications/fips/fips197/fips-197.pdf#page=10 fips-197.pdf#page=10}.}} @@ -161,6 +162,22 @@ class Crypt_Rijndael extends Crypt_Base { */ var $const_namespace = 'RIJNDAEL'; + /** + * The mcrypt specific name of the cipher + * + * Mcrypt is useable for 128/192/256-bit $block_size/$key_size. For 160/224 not. + * Crypt_Rijndael determines automatically whether mcrypt is useable + * or not for the current $block_size/$key_size. + * In case of, $cipher_name_mcrypt will be set dynamicaly at run time accordingly. + * + * @see Crypt_Base::cipher_name_mcrypt + * @see Crypt_Base::engine + * @see _setupEngine() + * @var String + * @access private + */ + var $cipher_name_mcrypt = 'rijndael-128'; + /** * The default salt used by setPassword() * @@ -252,6 +269,14 @@ class Crypt_Rijndael extends Crypt_Base { */ var $c; + /** + * Holds the last used key- and block_size information + * + * @var Array + * @access private + */ + var $kl; + /** * Precomputed mixColumns table * @@ -652,14 +677,6 @@ class Crypt_Rijndael extends Crypt_Base { 0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D ); - /** - * Holds the last used key- and blocksize information - * - * @var Array - * @access private - */ - var $kl; - /** * Default Constructor. * @@ -698,13 +715,31 @@ class Crypt_Rijndael extends Crypt_Base { * * If the key is not explicitly set, it'll be assumed to be all null bytes. * + * Note: 160/224-bit keys must explicitly set by setKeyLength(), otherwise they will be round/pad up to 192/256 bits. + * * @see Crypt_Base:setKey() + * @see setKeyLength() * @access public * @param String $key */ function setKey($key) { parent::setKey($key); + + if (!$this->explicit_key_length) { + $length = strlen($key); + switch (true) { + case $length <= 16: + $this->key_size = 16; + break; + case $length <= 24: + $this->key_size = 24; + break; + default: + $this->key_size = 32; + } + $this->_setupEngine(); + } } /** @@ -713,6 +748,17 @@ class Crypt_Rijndael extends Crypt_Base { * Valid key lengths are 128, 160, 192, 224, and 256. If the length is less than 128, it will be rounded up to * 128. If the length is greater than 128 and invalid, it will be rounded down to the closest valid amount. * + * Note: phpseclib extends Rijndael (and AES) for using 160- and 224-bit keys but they are officially not defined + * and the most (if not all) implementations are not able using 160/224-bit keys but round/pad them up to + * 192/256 bits as, for example, mcrypt will do. + * + * That said, if you want be compatible with other Rijndael and AES implementations, + * you should not setKeyLength(160) or setKeyLength(224). + * + * Additional: In case of 160- and 224-bit keys, phpseclib will/can, for that reason, not use + * the mcrypt php extention, even if available. + * This results then in slower encryption. + * * @access public * @param Integer $length */ @@ -737,6 +783,7 @@ class Crypt_Rijndael extends Crypt_Base { $this->explicit_key_length = true; $this->changed = true; + $this->_setupEngine(); } /** @@ -759,6 +806,76 @@ class Crypt_Rijndael extends Crypt_Base { $this->Nb = $length; $this->block_size = $length << 2; $this->changed = true; + $this->_setupEngine(); + } + + /** + * Setup the fastest possible $engine + * + * Determines if the mcrypt (MODE_MCRYPT) $engine available + * and usable for the current $block_size and $key_size. + * + * If not, the slower MODE_INTERNAL $engine will be set. + * + * @see setKey() + * @see setKeyLength() + * @see setBlockLength() + * @access private + */ + function _setupEngine() + { + if (constant('CRYPT_' . $this->const_namespace . '_MODE') == CRYPT_MODE_INTERNAL) { + // No mcrypt support at all for rijndael + return; + } + + // The required mcrypt module name for the current $block_size of rijndael + $cipher_name_mcrypt = 'rijndael-' . ($this->block_size << 3); + + // Determining the availibility/usability of $cipher_name_mcrypt + switch (true) { + case $this->key_size % 8: // mcrypt is not usable for 160/224-bit keys, only for 128/192/256-bit keys + case !in_array($cipher_name_mcrypt, mcrypt_list_algorithms()): // $cipher_name_mcrypt is not available for the current $block_size + $engine = CRYPT_MODE_INTERNAL; + break; + default: + $engine = CRYPT_MODE_MCRYPT; + } + + if ($this->engine == $engine && $this->cipher_name_mcrypt == $cipher_name_mcrypt) { + // allready set, so we not unnecessary close $this->enmcrypt/demcrypt/ecb + return; + } + + // Set the $engine + $this->engine = $engine; + $this->cipher_name_mcrypt = $cipher_name_mcrypt; + + if ($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); + mcrypt_module_close($this->demcrypt); + $this->enmcrypt = null; + $this->demcrypt = null; + + if ($this->ecb) { + mcrypt_module_close($this->ecb); + $this->ecb = null; + } + } + } + + /** + * Setup the CRYPT_MODE_MCRYPT $engine + * + * @see Crypt_Base::_setupMcrypt() + * @access private + */ + function _setupMcrypt() + { + $this->key = str_pad(substr($this->key, 0, $this->key_size), $this->key_size, "\0"); + parent::_setupMcrypt(); } /** @@ -973,26 +1090,13 @@ class Crypt_Rijndael extends Crypt_Base { 0x7D000000, 0xFA000000, 0xEF000000, 0xC5000000, 0x91000000 ); - if (!$this->explicit_key_length) { - $length = strlen($this->key); - switch (true) { - case $length <= 16: - $this->key_size = 16; - break; - case $length <= 24: - $this->key_size = 24; - break; - default: - $this->key_size = 32; - } - } - $key = str_pad(substr($this->key, 0, $this->key_size), $this->key_size, "\0"); + $this->key = str_pad(substr($this->key, 0, $this->key_size), $this->key_size, "\0"); - if (isset($this->kl['key']) && $key === $this->kl['key'] && $this->key_size === $this->kl['key_size'] && $this->block_size === $this->kl['block_size']) { + if (isset($this->kl['key']) && $this->key === $this->kl['key'] && $this->key_size === $this->kl['key_size'] && $this->block_size === $this->kl['block_size']) { // already expanded return; } - $this->kl = array('key' => $key, 'key_size' => $this->key_size, 'block_size' => $this->block_size); + $this->kl = array('key' => $this->key, 'key_size' => $this->key_size, 'block_size' => $this->block_size); $this->Nk = $this->key_size >> 2; // see Rijndael-ammended.pdf#page=44 @@ -1015,7 +1119,7 @@ class Crypt_Rijndael extends Crypt_Base { $this->c = array(0, 1, 3, 4); } - $w = array_values(unpack('N*words', $key)); + $w = array_values(unpack('N*words', $this->key)); $length = $this->Nb * ($this->Nr + 1); for ($i = $this->Nk; $i < $length; $i++) {