From df0fe2386a66e8b9625d254df5488bdf8f172149 Mon Sep 17 00:00:00 2001 From: Jim Wigginton Date: Mon, 23 Jul 2007 05:21:39 +0000 Subject: [PATCH] - added SSH2.php and HMAC.php - fixed issue with the IV's in TripleDES.php and DES.php - fixed decryption in TripleDES.php using CRYPT_DES_MODE_INTERNAL - renamed CRYPT_DES_MODE_SSH to CRYPT_DES_MODE_3CBC - added CRYPT_DES_MODE_CBC3 as an alias for CRYPT_DES_MODE_CBC - fixed issue with RC4.php using CRYPT_RC4_MODE_MCRYPT git-svn-id: http://phpseclib.svn.sourceforge.net/svnroot/phpseclib/trunk@4 21d32557-59b3-4da0-833f-c5933fad653e --- phpseclib/Crypt/DES.php | 6 +- phpseclib/Crypt/HMAC.php | 200 +++++ phpseclib/Crypt/RC4.php | 163 ++-- phpseclib/Crypt/Random.php | 2 +- phpseclib/Crypt/TripleDES.php | 95 ++- phpseclib/Math/BigInteger.php | 26 +- phpseclib/Net/SSH2.php | 1323 +++++++++++++++++++++++++++++++++ 7 files changed, 1712 insertions(+), 103 deletions(-) create mode 100644 phpseclib/Crypt/HMAC.php create mode 100644 phpseclib/Net/SSH2.php diff --git a/phpseclib/Crypt/DES.php b/phpseclib/Crypt/DES.php index b8324004..c284608a 100644 --- a/phpseclib/Crypt/DES.php +++ b/phpseclib/Crypt/DES.php @@ -53,7 +53,7 @@ * @author Jim Wigginton * @copyright MMVII Jim Wigginton * @license http://www.gnu.org/licenses/lgpl.txt - * @version $Id: DES.php,v 1.1 2007-07-02 04:19:47 terrafrost Exp $ + * @version $Id: DES.php,v 1.2 2007-07-23 05:21:39 terrafrost Exp $ * @link http://pear.php.net/package/Crypt_DES */ @@ -264,7 +264,7 @@ class Crypt_DES { */ function setIV($iv) { - $this->iv = str_pad(substr($iv, 0, 8), 8, chr(0));; + $this->encryptIV = $this->decryptIV = $this->iv = str_pad(substr($iv, 0, 8), 8, chr(0));; } /** @@ -521,7 +521,7 @@ class Crypt_DES { return $text; } - $length = ord($text{strlen($text) - 1}); + $length = ord($text[strlen($text) - 1]); return substr($text, 0, -$length); } diff --git a/phpseclib/Crypt/HMAC.php b/phpseclib/Crypt/HMAC.php new file mode 100644 index 00000000..876b5105 --- /dev/null +++ b/phpseclib/Crypt/HMAC.php @@ -0,0 +1,200 @@ + + * @version 0.1.0 + * @access public + * @package Crypt_HMAC + */ +class Crypt_HMAC { + /** + * Byte-length of compression blocks + * + * The following URL provides more information: + * + * {@link http://tools.ietf.org/html/rfc2104#section-2 http://tools.ietf.org/html/rfc2104#section-2} + * + * @see Crypt_HMAC::setHash() + * @var Integer + * @access private + */ + var $b; + + /** + * Byte-length of hash outputs + * + * @see Crypt_HMAC::setHash() + * @var Integer + * @access private + */ + var $l; + + /** + * Hash Algorithm + * + * @see Crypt_HMAC::setHash() + * @var String + * @access private + */ + var $hash; + + /** + * Key + * + * @see Crypt_HMAC::setKey() + * @var String + * @access private + */ + var $key = ''; + + /** + * Outer XOR + * + * @see Crypt_HMAC::setKey() + * @var String + * @access private + */ + var $opad; + + /** + * Inner XOR + * + * @see Crypt_HMAC::setKey() + * @var String + * @access private + */ + var $ipad; + + /** + * Default Constructor. + * + * @return Crypt_HMAC + * @access public + */ + function Crypt_HMAC() + { + if ( !defined('CRYPT_HMAC_MODE') ) { + switch (true) { + case extension_loaded('hash'): + define('CRYPT_HMAC_MODE', CRYPT_HMAC_MODE_HASH); + break; + case extension_loaded('mhash'): + define('CRYPT_HMAC_MODE', CRYPT_HMAC_MODE_MHASH); + break; + default: + define('CRYPT_HMAC_MODE', CRYPT_HMAC_MODE_INTERNAL); + } + } + + $this->setHash('sha1'); + } + + /** + * Sets the key. + * + * @access public + * @param String $key + */ + function setKey($key) + { + $this->key = $key; + } + + /** + * Sets the hash function. + * + * Currently, only 'sha1' and 'md5' are supported. If you do not supply a valid $hash, 'sha1' will be used. + * + * @access public + * @param String $hash + */ + function setHash($hash) + { + switch (CRYPT_HMAC_MODE) { + case CRYPT_HMAC_MODE_MHASH: + switch ($hash) { + case 'md5': + $this->hash = MHASH_MD5; + break; + case 'sha1': + default: + $this->hash = MHASH_SHA1; + } + return; + case CRYPT_HMAC_MODE_HASH: + switch ($hash) { + case 'md5': + case 'sha1': + $this->hash = $hash; + return; + default: + $this->hash = 'sha1'; + } + } + + switch ($hash) { + case 'md5': + $this->b = 64; + $this->l = 16; + $this->hash = 'md5'; + break; + case 'sha1': + default: + $this->b = 64; + $this->l = 20; + $this->hash = 'sha1'; + } + + $this->ipad = str_repeat(chr(0x36), $this->b); + $this->opad = str_repeat(chr(0x5C), $this->b); + } + + /** + * Compute the HMAC. + * + * @access public + * @param String $text + */ + function hmac($text) + { + switch (CRYPT_HMAC_MODE) { + case CRYPT_HMAC_MODE_MHASH: + return mhash($this->hash, $text, $this->key); + case CRYPT_HMAC_MODE_HASH: + return hash_hmac($this->hash, $text, $this->key, true); + } + + $hash = $this->hash; + + $key = strlen($this->key) > $this->b ? $this->hash($this->key) : $this->key; + $key = str_pad($key, $this->b, chr(0)); // step 1 + $temp = $this->ipad ^ $key; // step 2 + $temp.= $text; // step 3 + $temp = pack('H*', $hash($temp)); // step 4 + $hmac = $this->opad ^ $key; // step 5 + $hmac.= $temp; // step 6 + $hmac = pack('H*', $hash($hmac)); // step 7 + + return $hmac; + } +} \ No newline at end of file diff --git a/phpseclib/Crypt/RC4.php b/phpseclib/Crypt/RC4.php index 99680e18..e5b09ae1 100644 --- a/phpseclib/Crypt/RC4.php +++ b/phpseclib/Crypt/RC4.php @@ -55,7 +55,7 @@ * @author Jim Wigginton * @copyright MMVII Jim Wigginton * @license http://www.gnu.org/licenses/lgpl.txt - * @version $Id: RC4.php,v 1.1 2007-07-02 04:19:47 terrafrost Exp $ + * @version $Id: RC4.php,v 1.2 2007-07-23 05:21:39 terrafrost Exp $ * @link http://pear.php.net/package/Crypt_RC4 */ @@ -139,6 +139,15 @@ class Crypt_RC4 { */ var $decryptIndex = 0; + /** + * MCrypt parameters + * + * @see Crypt_RC4::setMCrypt() + * @var Array + * @access private + */ + var $mcrypt = array('', ''); + /** * The Encryption Algorithm * @@ -168,38 +177,29 @@ class Crypt_RC4 { // but since that can be changed after the object has been created, there doesn't seem to be // a lot of point... define('CRYPT_RC4_MODE', CRYPT_RC4_MODE_MCRYPT); - switch (true) { - case defined('MCRYPT_ARCFOUR'): - $this->mode = MCRYPT_ARCFOUR; - break; - case defined('MCRYPT_RC4'); - $this->mode = MCRYPT_RC4; - } - $this->encryptStream = @mcrypt_module_open($this->mode, '', MCRYPT_MODE_STREAM, ''); - $this->decryptStream = @mcrypt_module_open($this->mode, '', MCRYPT_MODE_STREAM, ''); break; default: define('CRYPT_RC4_MODE', CRYPT_RC4_MODE_INTERNAL); } } + + switch ( CRYPT_RC4_MODE ) { + case CRYPT_RC4_MODE_MCRYPT: + switch (true) { + case defined('MCRYPT_ARCFOUR'): + $this->mode = MCRYPT_ARCFOUR; + break; + case defined('MCRYPT_RC4'); + $this->mode = MCRYPT_RC4; + } + } } /** * Sets the key. * * Keys can be between 1 and 256 bytes long. If they are longer then 256 bytes, the first 256 bytes will - * be used. If no key is explicitly set, it'll be assumed to be a single null byte. Some protocols, such - * as WEP, prepend an "initialization vector" to the key, effectively creating a new key [1]. If you need - * to use an initialization vector in this manner, feel free to preend it to the key, yourself, before - * calling this function. - * - * [1] WEP's initialization vectors (IV's) are used in a somewhat insecure way. Since, in that protocol, - * the IV's are relatively easy to predict, an attack described by - * {@link http://www.drizzle.com/~aboba/IEEE/rc4_ksaproc.pdf Scott Fluhrer, Itsik Mantin, and Adi Shamir} - * can be used to quickly guess at the rest of the key. The following links elaborate: - * - * {@link http://www.rsa.com/rsalabs/node.asp?id=2009 http://www.rsa.com/rsalabs/node.asp?id=2009} - * {@link http://en.wikipedia.org/wiki/Related_key_attack http://en.wikipedia.org/wiki/Related_key_attack} + * be used. If no key is explicitly set, it'll be assumed to be a single null byte. * * @access public * @param String $key @@ -229,6 +229,29 @@ class Crypt_RC4 { $this->encryptStream = $this->decryptStream = $keyStream; } + /** + * Dummy function. + * + * Some protocols, such as WEP, prepend an "initialization vector" to the key, effectively creating a new key [1]. + * If you need to use an initialization vector in this manner, feel free to prepend it to the key, yourself, before + * calling setKey(). + * + * [1] WEP's initialization vectors (IV's) are used in a somewhat insecure way. Since, in that protocol, + * the IV's are relatively easy to predict, an attack described by + * {@link http://www.drizzle.com/~aboba/IEEE/rc4_ksaproc.pdf Scott Fluhrer, Itsik Mantin, and Adi Shamir} + * can be used to quickly guess at the rest of the key. The following links elaborate: + * + * {@link http://www.rsa.com/rsalabs/node.asp?id=2009 http://www.rsa.com/rsalabs/node.asp?id=2009} + * {@link http://en.wikipedia.org/wiki/Related_key_attack http://en.wikipedia.org/wiki/Related_key_attack} + * + * @param String $iv + * @see Crypt_RC4::setKey() + * @access public + */ + function setIV($iv) + { + } + /** * Sets MCrypt parameters. (optional) * @@ -241,8 +264,10 @@ class Crypt_RC4 { */ function setMCrypt($algorithm_directory, $mode_directory) { - $this->encryptStream = @mcrypt_module_open($this->mode, $algorithm_directory, MCRYPT_MODE_STREAM, $mode_directory); - $this->decryptStream = @mcrypt_module_open($this->mode, $algorithm_directory, MCRYPT_MODE_STREAM, $mode_directory); + if ( CRYPT_RC4_MODE == CRYPT_RC4_MODE_MCRYPT ) { + $this->mcrypt = array($algorithm_directory, $mode_directory); + $this->_closeMCrypt(); + } } /** @@ -280,6 +305,23 @@ class Crypt_RC4 { * @param Integer $mode */ function _crypt($text, $mode) { + if ( CRYPT_RC4_MODE == CRYPT_RC4_MODE_MCRYPT ) { + $keyStream = $mode == CRYPT_RC4_ENCRYPT ? 'encryptStream' : 'decryptStream'; + + if ($this->$keyStream === false) {echo "crypt-toe'ing $keyStream"; + $this->$keyStream = mcrypt_module_open($this->mode, $this->mcrypt[0], MCRYPT_MODE_STREAM, $this->mcrypt[1]); + mcrypt_generic_init($this->$keyStream, $this->key, ''); + } else if (!$this->continuousBuffer) { + mcrypt_generic_init($this->$keyStream, $this->key, ''); + } + $newText = mcrypt_generic($this->$keyStream, $text); + if (!$this->continuousBuffer) { + mcrypt_generic_deinit($this->$keyStream); + } + + return $newText; + } + switch ($mode) { case CRYPT_RC4_ENCRYPT: $keyStream = $this->encryptStream; @@ -290,18 +332,6 @@ class Crypt_RC4 { list($i, $j) = $this->decryptIndex; } - if ( CRYPT_RC4_MODE == CRYPT_RC4_MODE_MCRYPT ) { - if (!$this->continuousBuffer) { - mcrypt_generic_init($keyStream, $this->key, ''); - } - $newText = mcrypt_generic($keyStream, $text); - if (!$this->continuousBuffer) { - mcrypt_generic_deinit($keyStream); - } - - return $newText; - } - $newText = ''; for ($k = 0; $k < strlen($text); $k++) { $i = ($i + 1) & 255; @@ -367,14 +397,6 @@ class Crypt_RC4 { */ function enableContinuousBuffer() { - if ( CRYPT_RC4_MODE == CRYPT_RC4_MODE_MCRYPT ) { - $this->encryptStream = mcrypt_module_open($this->mode, $this->mcrypt[0], MCRYPT_MODE_STREAM, $this->mcrypt[1]); - $this->decryptStream = mcrypt_module_open($this->mode, $this->mcrypt[0], MCRYPT_MODE_STREAM, $this->mcrypt[1]); - - mcrypt_generic_init($this->encryptStream, $this->key, ''); - mcrypt_generic_init($this->decryptStream, $this->key, ''); - } - $this->continuousBuffer = true; } @@ -396,6 +418,29 @@ class Crypt_RC4 { $this->continuousBuffer = false; } + /** + * Dummy function. + * + * Since RC4 is a stream cipher and not a block cipher, no padding is necessary. The only reason this function is + * included is so that you can switch between a block cipher and a stream cipher transparently. + * + * @see Crypt_RC4::disablePadding() + * @access public + */ + function enablePadding() + { + } + + /** + * Dummy function. + * + * @see Crypt_RC4::enablePadding() + * @access public + */ + function disablePadding() + { + } + /** * Class destructor. * @@ -404,18 +449,38 @@ class Crypt_RC4 { * * @access public */ - function __destruct() { - if ( CRYPT_RC4_MODE == CRYPT_RC4_MODE_MCRYPT && isset($this->encryptStream) ) { + function __destruct() + { + if ( CRYPT_RC4_MODE == CRYPT_RC4_MODE_MCRYPT ) { + $this->_closeMCrypt(); + } + } + + /** + * Properly close the MCrypt objects. + * + * @access prviate + */ + function _closeMCrypt() + { + if ( $this->encryptStream !== false ) { if ( $this->continuousBuffer ) { mcrypt_generic_deinit($this->encryptStream); - mcrypt_generic_deinit($this->decryptStream); } mcrypt_module_close($this->encryptStream); + + $this->encryptStream = false; + } + + if ( $this->decryptStream !== false ) { + if ( $this->continuousBuffer ) { + mcrypt_generic_deinit($this->decryptStream); + } + mcrypt_module_close($this->decryptStream); - unset($this->encryptStream); - unset($this->decryptStream); + $this->decryptStream = false; } } } \ No newline at end of file diff --git a/phpseclib/Crypt/Random.php b/phpseclib/Crypt/Random.php index 5a8a72ab..96cd8560 100644 --- a/phpseclib/Crypt/Random.php +++ b/phpseclib/Crypt/Random.php @@ -35,7 +35,7 @@ * @author Jim Wigginton * @copyright MMVII Jim Wigginton * @license http://www.gnu.org/licenses/lgpl.txt - * @version $Id: Random.php,v 1.1 2007-07-02 04:19:47 terrafrost Exp $ + * @version $Id: Random.php,v 1.2 2007-07-23 05:21:39 terrafrost Exp $ * @link http://pear.php.net/package/Crypt_Random */ diff --git a/phpseclib/Crypt/TripleDES.php b/phpseclib/Crypt/TripleDES.php index 12756fc2..f26e5ccf 100644 --- a/phpseclib/Crypt/TripleDES.php +++ b/phpseclib/Crypt/TripleDES.php @@ -4,7 +4,7 @@ /** * Pure-PHP implementations of Triple DES. * - * Uses mcrypt, if available, and an internal implementation, otherwise. + * Uses mcrypt, if available, and an internal implementation, otherwise. Operates in the EDE3 mode (encrypt-decrypt-encrypt). * * PHP versions 4 and 5 * @@ -47,7 +47,7 @@ * @author Jim Wigginton * @copyright MMVII Jim Wigginton * @license http://www.gnu.org/licenses/lgpl.txt - * @version $Id: TripleDES.php,v 1.1 2007-07-02 04:19:47 terrafrost Exp $ + * @version $Id: TripleDES.php,v 1.2 2007-07-23 05:21:39 terrafrost Exp $ * @link http://pear.php.net/package/Crypt_TripleDES */ @@ -57,11 +57,18 @@ require_once 'DES.php'; /** - * Encrypt / decrypt using a method required by SSHv1 + * Encrypt / decrypt using inner chaining * - * mcrypt does a single XOR operation for each block in CBC mode. SSHv1 requires three XOR operations. + * Inner chaining is used by SSH-1 and is generally considered to be less secure then outer chaining (CRYPT_DES_MODE_CBC3). */ -define('CRYPT_DES_MODE_SSH', 3); +define('CRYPT_DES_MODE_3CBC', 3); + +/** + * Encrypt / decrypt using outer chaining + * + * Outer chaining is used by SSH-2 and when the mode is set to CRYPT_DES_MODE_CBC. + */ +define('CRYPT_DES_MODE_CBC3', CRYPT_DES_MODE_CBC); /** * Pure-PHP implementation of Triple DES. @@ -69,7 +76,7 @@ define('CRYPT_DES_MODE_SSH', 3); * @author Jim Wigginton * @version 0.1.0 * @access public - * @package Crypt_DES + * @package Crypt_TerraDES */ class Crypt_TripleDES { /** @@ -177,13 +184,19 @@ class Crypt_TripleDES { } } - if ( $mode == CRYPT_DES_MODE_SSH ) { - $this->mode = CRYPT_DES_MODE_SSH; + if ( $mode == CRYPT_DES_MODE_3CBC ) { + $this->mode = CRYPT_DES_MODE_3CBC; $this->des = array( new Crypt_DES(CRYPT_DES_MODE_CBC), new Crypt_DES(CRYPT_DES_MODE_CBC), new Crypt_DES(CRYPT_DES_MODE_CBC) ); + + // we're going to be doing the padding, ourselves, so disable it in the Crypt_DES objects + $this->des[0]->disablePadding(); + $this->des[1]->disablePadding(); + $this->des[2]->disablePadding(); + return; } @@ -204,6 +217,11 @@ class Crypt_TripleDES { new Crypt_DES(CRYPT_DES_MODE_ECB), new Crypt_DES(CRYPT_DES_MODE_ECB) ); + + // we're going to be doing the padding, ourselves, so disable it in the Crypt_DES objects + $this->des[0]->disablePadding(); + $this->des[1]->disablePadding(); + $this->des[2]->disablePadding(); switch ($mode) { case CRYPT_DES_MODE_ECB: @@ -236,26 +254,15 @@ class Crypt_TripleDES { $key = str_pad($key, 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) : $key; + $key = $length <= 16 ? substr_replace($key, substr($key, 0, 8), 16) : substr($key, 0, 24); } $this->key = $key; switch (true) { case CRYPT_DES_MODE == CRYPT_DES_MODE_INTERNAL: - case $this->mode == CRYPT_DES_MODE_SSH: + case $this->mode == CRYPT_DES_MODE_3CBC: $this->des[0]->setKey(substr($key, 0, 8)); $this->des[1]->setKey(substr($key, 8, 8)); $this->des[2]->setKey(substr($key, 16, 8)); - - // we're going to be doing the padding, ourselves, so disable it in the Crypt_DES objects - $this->des[0]->disablePadding(); - $this->des[1]->disablePadding(); - $this->des[2]->disablePadding(); - } - - if ($this->mode == CRYPT_DES_MODE_SSH) { - $this->des[0]->enableContinuousBuffer(); - $this->des[1]->enableContinuousBuffer(); - $this->des[2]->enableContinuousBuffer(); } } @@ -270,7 +277,12 @@ class Crypt_TripleDES { */ function setIV($iv) { - $this->iv = str_pad(substr($key, 0, 8), 8, chr(0));; + $this->encryptIV = $this->decryptIV = $this->iv = str_pad(substr($iv, 0, 8), 8, chr(0)); + if ($this->mode == CRYPT_DES_MODE_3CBC) { + $this->des[0]->setIV($iv); + $this->des[1]->setIV($iv); + $this->des[2]->setIV($iv); + } } /** @@ -286,7 +298,7 @@ class Crypt_TripleDES { function setMCrypt($algorithm_directory, $mode_directory) { $this->mcrypt = array($algorithm_directory, $mode_directory); - if ( $this->mode == CRYPT_DES_MODE_SSH ) { + if ( $this->mode == CRYPT_DES_MODE_3CBC ) { $this->des[0]->setMCrypt($algorithm_directory, $mode_directory); $this->des[1]->setMCrypt($algorithm_directory, $mode_directory); $this->des[2]->setMCrypt($algorithm_directory, $mode_directory); @@ -301,17 +313,17 @@ class Crypt_TripleDES { */ function encrypt($plaintext) { + if ($this->padding) { + $plaintext = $this->_pad($plaintext); + } + // if the key is smaller then 8, do what we'd normally do - if ($this->mode == CRYPT_DES_MODE_SSH && strlen($this->key) > 8) { + if ($this->mode == CRYPT_DES_MODE_3CBC && strlen($this->key) > 8) { $ciphertext = $this->des[2]->encrypt($this->des[1]->decrypt($this->des[0]->encrypt($plaintext))); return $ciphertext; } - if ($this->padding) { - $plaintext = $this->_pad($plaintext); - } - if ( CRYPT_DES_MODE == CRYPT_DES_MODE_MCRYPT ) { $td = mcrypt_module_open(MCRYPT_3DES, $this->mcrypt[0], $this->mode, $this->mcrypt[1]); mcrypt_generic_init($td, $this->key, $this->encryptIV); @@ -375,10 +387,10 @@ class Crypt_TripleDES { */ function decrypt($ciphertext) { - if ($this->mode == CRYPT_DES_MODE_SSH && strlen($this->key) > 8) { + if ($this->mode == CRYPT_DES_MODE_3CBC && strlen($this->key) > 8) { $plaintext = $this->des[0]->decrypt($this->des[1]->encrypt($this->des[2]->decrypt($ciphertext))); - return $plaintext; + return $this->_unpad($plaintext); } // we pad with chr(0) since that's what mcrypt_generic does. to quote from http://php.net/function.mcrypt-generic : @@ -404,7 +416,7 @@ class Crypt_TripleDES { if (strlen($this->key) <= 8) { $this->des[0]->mode = $this->mode; - return $this->des[0]->decrypt($plaintext); + return $this->_unpad($this->des[0]->decrypt($plaintext)); } $plaintext = ''; @@ -412,9 +424,9 @@ class Crypt_TripleDES { case CRYPT_DES_MODE_ECB: for ($i = 0; $i < strlen($ciphertext); $i+=8) { $block = substr($ciphertext, $i, 8); - $block = $this->des[0]->_processBlock($block, CRYPT_DES_DECRYPT); - $block = $this->des[1]->_processBlock($block, CRYPT_DES_ENCRYPT); $block = $this->des[2]->_processBlock($block, CRYPT_DES_DECRYPT); + $block = $this->des[1]->_processBlock($block, CRYPT_DES_ENCRYPT); + $block = $this->des[0]->_processBlock($block, CRYPT_DES_DECRYPT); $plaintext.= $block; } break; @@ -422,9 +434,9 @@ class Crypt_TripleDES { $xor = $this->decryptIV; for ($i = 0; $i < strlen($ciphertext); $i+=8) { $orig = $block = substr($ciphertext, $i, 8); - $block = $this->des[0]->_processBlock($block, CRYPT_DES_DECRYPT); - $block = $this->des[1]->_processBlock($block, CRYPT_DES_ENCRYPT); $block = $this->des[2]->_processBlock($block, CRYPT_DES_DECRYPT); + $block = $this->des[1]->_processBlock($block, CRYPT_DES_ENCRYPT); + $block = $this->des[0]->_processBlock($block, CRYPT_DES_DECRYPT); $plaintext.= $block ^ $xor; $xor = $orig; } @@ -476,6 +488,11 @@ class Crypt_TripleDES { function enableContinuousBuffer() { $this->continuousBuffer = true; + if ($this->mode == CRYPT_DES_MODE_3CBC) { + $this->des[0]->enableContinuousBuffer(); + $this->des[1]->enableContinuousBuffer(); + $this->des[2]->enableContinuousBuffer(); + } } /** @@ -491,6 +508,12 @@ class Crypt_TripleDES { $this->continuousBuffer = false; $this->encryptIV = $this->iv; $this->decryptIV = $this->iv; + + if ($this->mode == CRYPT_DES_MODE_3CBC) { + $this->des[0]->disableContinuousBuffer(); + $this->des[1]->disableContinuousBuffer(); + $this->des[2]->disableContinuousBuffer(); + } } /** @@ -554,7 +577,7 @@ class Crypt_TripleDES { return $text; } - $length = ord($text{strlen($text) - 1}); + $length = ord($text[strlen($text) - 1]); return substr($text, 0, -$length); } } diff --git a/phpseclib/Math/BigInteger.php b/phpseclib/Math/BigInteger.php index bc06714e..22b2387c 100644 --- a/phpseclib/Math/BigInteger.php +++ b/phpseclib/Math/BigInteger.php @@ -69,7 +69,7 @@ * @author Jim Wigginton * @copyright MMVI Jim Wigginton * @license http://www.gnu.org/licenses/lgpl.txt - * @version $Id: BigInteger.php,v 1.1 2007-07-02 04:19:47 terrafrost Exp $ + * @version $Id: BigInteger.php,v 1.2 2007-07-23 05:21:39 terrafrost Exp $ * @link http://pear.php.net/package/Math_BigInteger */ @@ -1076,7 +1076,7 @@ class Math_BigInteger { return false; } - return $temp->modPow($e,$n); + return $temp->modPow($e, $n); } switch ( MATH_BIGINTEGER_MODE ) { @@ -1158,9 +1158,6 @@ class Math_BigInteger { * however, this function performs a modular reduction after every multiplication and squaring operation. * As such, this function has the same preconditions that the reductions being used do. * - * The window size is calculated in the same fashion that the window size in BigInteger.java's oddModPow - * function is. - * * @param Math_BigInteger $e * @param Math_BigInteger $n * @param Integer $mode @@ -1169,7 +1166,8 @@ class Math_BigInteger { */ function _slidingWindow($e, $n, $mode) { - static $window_ranges = array(7, 25, 81, 241, 673, 1793); + static $window_ranges = array(7, 25, 81, 241, 673, 1793); // from BigInteger.java's oddModPow function + //static $window_ranges = array(0, 7, 36, 140, 450, 1303, 3529); // from MPM 7.3.1 $e_length = count($e->value) - 1; $e_bits = decbin($e->value[$e_length]); @@ -1227,13 +1225,13 @@ class Math_BigInteger { $result = $result->$undo($n); for ($i = 0; $i < $e_length; ) { - if ( !$e_bits{$i} ) { + if ( !$e_bits[$i] ) { $result = $result->_square(); $result = $result->$reduce($n); $i++; } else { for ($j = $window_size - 1; $j >= 0; $j--) { - if ( $e_bits{$i + $j} ) { + if ( $e_bits[$i + $j] ) { break; } } @@ -2091,8 +2089,8 @@ class Math_BigInteger { $carry = 0; for ($i = strlen($x) - 1; $i >= 0; $i--) { - $temp = ord($x{$i}) << $shift | $carry; - $x{$i} = chr($temp); + $temp = ord($x[$i]) << $shift | $carry; + $x[$i] = chr($temp); $carry = $temp >> 8; } $carry = ($carry != 0) ? chr($carry) : ''; @@ -2127,11 +2125,11 @@ class Math_BigInteger { } $carry = 0; - $carry_shift = 8-$shift; + $carry_shift = 8 - $shift; for ($i = 0; $i < strlen($x); $i++) { - $temp = (ord($x{$i}) >> $shift) | $carry; - $carry = (ord($x{$i}) << $carry_shift) & 0xFF; - $x{$i} = chr($temp); + $temp = (ord($x[$i]) >> $shift) | $carry; + $carry = (ord($x[$i]) << $carry_shift) & 0xFF; + $x[$i] = chr($temp); } $x = ltrim($x, chr(0)); diff --git a/phpseclib/Net/SSH2.php b/phpseclib/Net/SSH2.php new file mode 100644 index 00000000..cc143a13 --- /dev/null +++ b/phpseclib/Net/SSH2.php @@ -0,0 +1,1323 @@ + + * @copyright MMVII Jim Wigginton + * @license http://www.gnu.org/licenses/lgpl.txt + * @version $Id: SSH2.php,v 1.1 2007-07-23 05:21:39 terrafrost Exp $ + * @link http://phpseclib.sourceforge.net + */ + +/** + * Include Math_BigInteger + * + * Used to do Diffie-Hellman key exchange and DSA/RSA signature verification. + */ +require_once('Math/BigInteger.php'); + +/** + * Include Crypt_Random + */ +require_once('Crypt/Random.php'); + +/** + * Include Crypt_HMAC.php + */ +require_once('Crypt/HMAC.php'); + +/** + * Include Crypt_TripleDES.php + */ +require_once('Crypt/TripleDES.php'); + +/** + * Include Crypt_RC4.php + */ +require_once('Crypt/RC4.php'); + +/**#@+ + * Message Numbers + * + * @access private + */ +define('NET_SSH2_MSG_DISCONNECT', 1); +define('NET_SSH2_MSG_IGNORE', 2); +define('NET_SSH2_MSG_UNIMPLEMENTED', 3); +define('NET_SSH2_MSG_DEBUG', 4); +define('NET_SSH2_MSG_SERVICE_REQUEST', 5); +define('NET_SSH2_MSG_SERVICE_ACCEPT', 6); +define('NET_SSH2_MSG_KEXINIT', 20); +define('NET_SSH2_MSG_NEWKEYS', 21); +define('NET_SSH2_MSG_KEXDH_INIT', 30); +define('NET_SSH2_MSG_KEXDH_REPLY', 31); +define('NET_SSH2_MSG_USERAUTH_REQUEST', 50); +define('NET_SSH2_MSG_USERAUTH_FAILURE', 51); +define('NET_SSH2_MSG_USERAUTH_SUCCESS', 52); +define('NET_SSH2_MSG_USERAUTH_BANNER', 53); +define('NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ', 60); +/**#@-*/ + +/**#@+ + * Disconnection Message 'reason codes' defined in RFC4253 + * + * @access private + */ +define('NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED', 3); +/**#@-*/ + +/**#@+ + * Execution Bitmap Masks + * + * @see Net_SSH2::bitmap + * @access private + */ +define('NET_SSH2_MASK_CONSTRUCTOR', 0x00000001); +define('NET_SSH2_MASK_LOGIN', 0x00000002); +define('NET_SSH2_MASK_SHELL', 0x00000004); +/**#@-*/ + +/** + * Pure-PHP implementation of SSHv2. + * + * @author Jim Wigginton + * @version 0.1.0 + * @access public + * @package Net_SSH2 + */ +class Net_SSH2 { + /** + * The SSH identifier + * + * @var String + * @access private + */ + var $identifier = 'SSH-2.0-phpseclib_0.1'; + + /** + * The Socket Object + * + * @var Object + * @access private + */ + var $fsock; + + /** + * Execution Bitmap + * + * The bits that are set reprsent functions that have been called already. This is used to determine + * if a requisite function has been successfully executed. If not, an error should be thrown. + * + * @var Boolean + * @access private + */ + var $bitmap = true; + + /** + * Debug Info + * + * @see Net_SSH2::getDebugInfo() + * @var String + * @access private + */ + var $debug_info = ''; + + /** + * Server Identifier + * + * @see Net_SSH2::getServerIdentification() + * @var String + * @access private + */ + var $server_identifier = ''; + + /** + * Key Exchange Algorithms + * + * @see Net_SSH2::getKexAlgorithims() + * @var Array + * @access private + */ + var $kex_algorithms; + + /** + * Server Host Key Algorithms + * + * @see Net_SSH2::getServerHostKeyAlgorithms() + * @var Array + * @access private + */ + var $server_host_key_algorithms; + + /** + * Encryption Algorithms: Client to Server + * + * @see Net_SSH2::getEncryptionAlgorithmsClient2Server() + * @var Array + * @access private + */ + var $encryption_algorithms_client_to_server; + + /** + * Encryption Algorithms: Server to Client + * + * @see Net_SSH2::getEncryptionAlgorithmsServer2Client() + * @var Array + * @access private + */ + var $encryption_algorithms_server_to_client; + + /** + * MAC Algorithms: Client to Server + * + * @see Net_SSH2::getMACAlgorithmsClient2Server() + * @var Array + * @access private + */ + var $mac_algorithms_client_to_server; + + /** + * MAC Algorithms: Server to Client + * + * @see Net_SSH2::getMACAlgorithmsServer2Client() + * @var Array + * @access private + */ + var $mac_algorithms_server_to_client; + + /** + * Compression Algorithms: Client to Server + * + * @see Net_SSH2::getCompressionAlgorithmsClient2Server() + * @var Array + * @access private + */ + var $compression_algorithms_client_to_server; + + /** + * Compression Algorithms: Server to Client + * + * @see Net_SSH2::getCompressionAlgorithmsServer2Client() + * @var Array + * @access private + */ + var $compression_algorithms_server_to_client; + + /** + * Languages: Server to Client + * + * @see Net_SSH2::getLanguagesServer2Client() + * @var Array + * @access private + */ + var $languages_server_to_client; + + /** + * Languages: Client to Server + * + * @see Net_SSH2::getLanguagesClient2Server() + * @var Array + * @access private + */ + var $languages_client_to_server; + + /** + * Block Size + * + * @see Net_SSH2::_send_binary_packet() + * @var Integer + * @access private + */ + var $block_size = 8; + + /** + * Server to Client Encryption Object + * + * @see Net_SSH2::_get_binary_packet() + * @var Object + * @access private + */ + var $decrypt = false; + + /** + * Client to Server Encryption Object + * + * @see Net_SSH2::_send_binary_packet() + * @var Object + * @access private + */ + var $encrypt = false; + + /** + * Client to Server HMAC Object + * + * @see Net_SSH2::_send_binary_packet() + * @var Object + * @access private + */ + var $hmac_create = false; + + /** + * Server to Client HMAC Object + * + * @see Net_SSH2::_get_binary_packet() + * @var Object + * @access private + */ + var $hmac_check = false; + + /** + * Size of server to client HMAC + * + * We need to know how big the HMAC will be for the server to client direction so that we know how many bytes to read. + * For the client to server side, the HMAC object will make the HMAC as long as it needs to be. All we need to do is + * append it. + * + * @see Net_SSH2::_get_binary_packet() + * @var Integer + * @access private + */ + var $hmac_size = false; + + /** + * Server Public Host Key + * + * @see Net_SSH2::getServerPublicHostKey() + * @var String + * @access private + */ + var $server_public_host_key; + + /** + * Session identifer + * + * "The exchange hash H from the first key exchange is additionally + * used as the session identifier, which is a unique identifier for + * this connection." + * + * -- http://tools.ietf.org/html/rfc4253#section-7.2 + * + * @see Net_SSH2::_key_exchange() + * @var String + * @access private + */ + var $session_id = false; + + /** + * Default Constructor. + * + * Connects to an SSHv1 server + * + * @param String $host + * @param optional Integer $port + * @param optional Integer $cipher + * @return Net_SSH2 + * @access public + */ + function Net_SSH2($host, $port = 22) + { + $this->fsock = fsockopen($host, $port, $errno, $errstr, 10); + if (!$this->fsock) { + user_error(rtrim("Cannot connect to $host. Error $errno. $errstr"), E_USER_NOTICE); + return; + } + + /* According to the SSH2 specs, + + "The server MAY send other lines of data before sending the version + string. Each line SHOULD be terminated by a Carriage Return and Line + Feed. Such lines MUST NOT begin with "SSH-", and SHOULD be encoded + in ISO-10646 UTF-8 [RFC3629] (language is not specified). Clients + MUST be able to process such lines." */ + $temp = ''; + while (!feof($this->fsock) && !preg_match('#^SSH-(\d.\d+)#', $temp, $matches)) { + if (substr($temp, -2) == "\r\n") { + $this->debug_info.= $temp; + $temp = ''; + } + $temp.= fgets($this->fsock, 255); + } + $this->server_identifier = trim($temp); + $this->debug_info = utf8_decode($this->debug_info); + + if ($matches[1] != '1.99' && $matches[1] != '2.0') { + user_error("Cannot connect to SSH $matches[1] servers", E_USER_NOTICE); + return; + } + + fputs($this->fsock, $this->identifier . "\r\n"); + + $response = $this->_get_binary_packet(); + if ($response === false) { + user_error('Connection closed by server', E_USER_NOTICE); + return; + } + + if (ord($response[0]) != NET_SSH2_MSG_KEXINIT) { + user_error('Expected SSH_MSG_KEXINIT', E_USER_NOTICE); + return false; + } + + if (!$this->_key_exchange($response)) { + return; + } + + $this->bitmap = NET_SSH2_MASK_CONSTRUCTOR; + } + + /** + * Key Exchange + * + * @param String $kexinit_payload_server + * @access private + */ + function _key_exchange($kexinit_payload_server) + { + static $kex_algorithms = array( + 'diffie-hellman-group1-sha1', // REQUIRED + 'diffie-hellman-group14-sha1' // REQUIRED + ); + + static $server_host_key_algorithms = array( + 'ssh-rsa', // RECOMMENDED sign Raw RSA Key + 'ssh-dss' // REQUIRED sign Raw DSS Key + ); + + static $encryption_algorithms = array( + '3des-cbc', // REQUIRED three-key 3DES in CBC mode + 'aes256-cbc', // OPTIONAL AES in CBC mode, with a 256-bit key + 'aes192-cbc', // OPTIONAL AES with a 192-bit key + 'aes128-cbc', // RECOMMENDED AES with a 128-bit key + 'arcfour', // OPTIONAL the ARCFOUR stream cipher with a 128-bit key + 'none' // OPTIONAL no encryption; NOT RECOMMENDED + ); + + static $mac_algorithms = array( + 'hmac-sha1', // REQUIRED HMAC-SHA1 (digest length = key length = 20) + 'hmac-sha1-96', // RECOMMENDED first 96 bits of HMAC-SHA1 (digest length = 12, key length = 20) + 'hmac-md5', // OPTIONAL HMAC-MD5 (digest length = key length = 16) + 'hmac-md5-96', // OPTIONAL first 96 bits of HMAC-MD5 (digest length = 12, key length = 16) + 'none' // OPTIONAL no MAC; NOT RECOMMENDED + ); + + static $compression_algorithms = array( + 'none', // REQUIRED no compression + 'zlib' // OPTIONAL ZLIB (LZ77) compression + ); + + static $str_kex_algorithms, $str_server_host_key_algorithms, + $encryption_algorithms_server_to_client, $mac_algorithms_server_to_client, $compression_algorithms_server_to_client, + $encryption_algorithms_client_to_server, $mac_algorithms_client_to_server, $compression_algorithms_client_to_server; + + if (empty($str_kex_algorithms)) { + $str_kex_algorithms = implode(',', $kex_algorithms); + $str_server_host_key_algorithms = implode(',', $server_host_key_algorithms); + $encryption_algorithms_server_to_client = $encryption_algorithms_client_to_server = implode(',', $encryption_algorithms); + $mac_algorithms_server_to_client = $mac_algorithms_client_to_server = implode(',', $mac_algorithms); + $compression_algorithms_server_to_client = $compression_algorithms_client_to_server = implode(',', $compression_algorithms); + } + + $client_cookie = ''; + for ($i = 0; $i < 16; $i++) { + $client_cookie.= chr(crypt_random(0, 255)); + } + + $response = $kexinit_payload_server; + $this->_string_shift($response, 1); // skip past the message number (it should be SSH_MSG_KEXINIT) + list(, $server_cookie) = unpack('a16', $this->_string_shift($response, 16)); + + $temp = unpack('Nlength', $this->_string_shift($response, 4)); + $this->kex_algorithms = explode(',', $this->_string_shift($response, $temp['length'])); + + $temp = unpack('Nlength', $this->_string_shift($response, 4)); + $this->server_host_key_algorithms = explode(',', $this->_string_shift($response, $temp['length'])); + + $temp = unpack('Nlength', $this->_string_shift($response, 4)); + $this->encryption_algorithms_client_to_server = explode(',', $this->_string_shift($response, $temp['length'])); + + $temp = unpack('Nlength', $this->_string_shift($response, 4)); + $this->encryption_algorithms_server_to_client = explode(',', $this->_string_shift($response, $temp['length'])); + + $temp = unpack('Nlength', $this->_string_shift($response, 4)); + $this->mac_algorithms_client_to_server = explode(',', $this->_string_shift($response, $temp['length'])); + + $temp = unpack('Nlength', $this->_string_shift($response, 4)); + $this->mac_algorithms_server_to_client = explode(',', $this->_string_shift($response, $temp['length'])); + + $temp = unpack('Nlength', $this->_string_shift($response, 4)); + $this->compression_algorithms_client_to_server = explode(',', $this->_string_shift($response, $temp['length'])); + + $temp = unpack('Nlength', $this->_string_shift($response, 4)); + $this->compression_algorithms_server_to_client = explode(',', $this->_string_shift($response, $temp['length'])); + + $temp = unpack('Nlength', $this->_string_shift($response, 4)); + $this->languages_client_to_server = explode(',', $this->_string_shift($response, $temp['length'])); + + $temp = unpack('Nlength', $this->_string_shift($response, 4)); + $this->languages_server_to_client = explode(',', $this->_string_shift($response, $temp['length'])); + + list(, $first_kex_packet_follows) = unpack('C', $this->_string_shift($response, 1)); + $first_kex_packet_follows = $first_kex_packet_follows != 0; + + // the sending of SSH2_MSG_KEXINIT could go in one of two places. this is the second place. + $kexinit_payload_client = pack('Ca*Na*Na*Na*Na*Na*Na*Na*Na*Na*Na*CN', + NET_SSH2_MSG_KEXINIT, $client_cookie, strlen($str_kex_algorithms), $str_kex_algorithms, + strlen($str_server_host_key_algorithms), $str_server_host_key_algorithms, strlen($encryption_algorithms_client_to_server), + $encryption_algorithms_client_to_server, strlen($encryption_algorithms_server_to_client), $encryption_algorithms_server_to_client, + strlen($mac_algorithms_client_to_server), $mac_algorithms_client_to_server, strlen($mac_algorithms_server_to_client), + $mac_algorithms_server_to_client, strlen($compression_algorithms_client_to_server), $compression_algorithms_client_to_server, + strlen($compression_algorithms_server_to_client), $compression_algorithms_server_to_client, 0, '', 0, '', + 0, 0 + ); + + if (!$this->_send_binary_packet($kexinit_payload_client)) { + return false; + } + // here ends the second place. + + // we need to decide upon the symmetric encryption algorithms before we do the diffie-hellman key exchange + for ($i = 0; $i < count($encryption_algorithms) && !in_array($encryption_algorithms[$i], $this->encryption_algorithms_server_to_client); $i++); + if ($i == count($encryption_algorithms)) { + user_error('No compatible server to client encryption algorithms found', E_USER_NOTICE); + return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + } + + // we don't initialize any crypto-objects, yet - we do that, later. for now, we need the lengths to make the + // diffie-hellman key exchange as fast as possible + $decrypt = $encryption_algorithms[$i]; + switch ($decrypt) { + case '3des-cbc': + // uses the default block size + $decryptKeyLength = 24; // eg. 192 / 8 + $decryptIVLength = 8; // eg. 64 / 8 + break; + case 'aes256-cbc': + $decryptKeyLength = 32; // eg. 256 / 8 + break; + case 'aes192-cbc': + $decryptKeyLength = 24; // eg. 192 / 8 + break; + case 'aes128-cbc': + $decryptKeyLength = 16; // eg. 128 / 8 + break; + case 'arcfour': + $decryptKeyLength = 16; // eg. 128 / 8 + $decryptIVLength = 0; + break; + case 'none'; + $decryptKeyLength = 0; + } + + for ($i = 0; $i < count($encryption_algorithms) && !in_array($encryption_algorithms[$i], $this->encryption_algorithms_client_to_server); $i++); + if ($i == count($encryption_algorithms)) { + user_error('No compatible client to server encryption algorithms found', E_USER_NOTICE); + return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + } + + $encrypt = $encryption_algorithms[$i]; + switch ($encrypt) { + case '3des-cbc': + $encryptKeyLength = 24; + $encryptIVLength = 8; + break; + case 'aes256-cbc': + $encryptKeyLength = 32; + break; + case 'aes192-cbc': + $encryptKeyLength = 24; + break; + case 'aes128-cbc': + $encryptKeyLength = 16; + break; + case 'arcfour': + $encryptKeyLength = 16; + $encryptIVLength = 0; + break; + case 'none'; + $encryptKeyLength = 0; + } + + $keyLength = $decryptKeyLength > $encryptKeyLength ? $decryptKeyLength : $encryptKeyLength; + + // through diffie-hellman key exchange a symmetric key is obtained + for ($i = 0; $i < count($kex_algorithms) && !in_array($kex_algorithms[$i], $this->kex_algorithms); $i++); + if ($i == count($kex_algorithms)) { + user_error('No compatible key exchange algorithms found', E_USER_NOTICE); + return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + } + + switch ($kex_algorithms[$i]) { + // see http://tools.ietf.org/html/rfc2409#section-6.2 and + // http://tools.ietf.org/html/rfc2412, appendex E + case 'diffie-hellman-group1-sha1': + $p = pack('N32', 0xFFFFFFFF, 0xFFFFFFFF, 0xC90FDAA2, 0x2168C234, 0xC4C6628B, 0x80DC1CD1, + 0x29024E08, 0x8A67CC74, 0x020BBEA6, 0x3B139B22, 0x514A0879, 0x8E3404DD, + 0xEF9519B3, 0xCD3A431B, 0x302B0A6D, 0xF25F1437, 0x4FE1356D, 0x6D51C245, + 0xE485B576, 0x625E7EC6, 0xF44C42E9, 0xA637ED6B, 0x0BFF5CB6, 0xF406B7ED, + 0xEE386BFB, 0x5A899FA5, 0xAE9F2411, 0x7C4B1FE6, 0x49286651, 0xECE65381, + 0xFFFFFFFF, 0xFFFFFFFF); + $keyLength = $keyLength < 160 ? $keyLength : 160; + $hash = 'sha1'; + break; + // see http://tools.ietf.org/html/rfc3526#section-3 + case 'diffie-hellman-group14-sha1': + $p = pack('N64', 0xFFFFFFFF, 0xFFFFFFFF, 0xC90FDAA2, 0x2168C234, 0xC4C6628B, 0x80DC1CD1, + 0x29024E08, 0x8A67CC74, 0x020BBEA6, 0x3B139B22, 0x514A0879, 0x8E3404DD, + 0xEF9519B3, 0xCD3A431B, 0x302B0A6D, 0xF25F1437, 0x4FE1356D, 0x6D51C245, + 0xE485B576, 0x625E7EC6, 0xF44C42E9, 0xA637ED6B, 0x0BFF5CB6, 0xF406B7ED, + 0xEE386BFB, 0x5A899FA5, 0xAE9F2411, 0x7C4B1FE6, 0x49286651, 0xECE45B3D, + 0xC2007CB8, 0xA163BF05, 0x98DA4836, 0x1C55D39A, 0x69163FA8, 0xFD24CF5F, + 0x83655D23, 0xDCA3AD96, 0x1C62F356, 0x208552BB, 0x9ED52907, 0x7096966D, + 0x670C354E, 0x4ABC9804, 0xF1746C08, 0xCA18217C, 0x32905E46, 0x2E36CE3B, + 0xE39E772C, 0x180E8603, 0x9B2783A2, 0xEC07A28F, 0xB5C55DF0, 0x6F4C52C9, + 0xDE2BCBF6, 0x95581718, 0x3995497C, 0xEA956AE5, 0x15D22618, 0x98FA0510, + 0x15728E5A, 0x8AACAA68, 0xFFFFFFFF, 0xFFFFFFFF); + $keyLength = $keyLength < 160 ? $keyLength : 160; + $hash = 'sha1'; + } + + $p = new Math_BigInteger($p, 256); + //$q = $p->bitwise_rightShift(1); + + /* To increase the speed of the key exchange, both client and server may + reduce the size of their private exponents. It should be at least + twice as long as the key material that is generated from the shared + secret. For more details, see the paper by van Oorschot and Wiener + [VAN-OORSCHOT]. + + -- http://tools.ietf.org/html/rfc4419#section-6.2 */ + $q = new Math_BigInteger(1); + $q = $q->bitwise_leftShift(2 * $keyLength); + $q = $q->subtract(new Math_BigInteger(1)); + + $g = new Math_BigInteger(2); + $x = new Math_BigInteger(); + $x = $x->random(new Math_BigInteger(1), $q, 'crypt_random'); + $e = $g->modPow($x, $p); + + $eBytes = $e->toBytes(true); + $data = pack('CNa*', NET_SSH2_MSG_KEXDH_INIT, strlen($eBytes), $eBytes); + + if (!$this->_send_binary_packet($data)) { + user_error('Connection closed by server', E_USER_NOTICE); + return false; + } + + $response = $this->_get_binary_packet(); + if ($response === false) { + user_error('Connection closed by server', E_USER_NOTICE); + return false; + } + + list(, $type) = unpack('C', $this->_string_shift($response, 1)); + + if ($type != NET_SSH2_MSG_KEXDH_REPLY) { + user_error('Expected SSH_MSG_KEXDH_REPLY', E_USER_NOTICE); + return false; + } + + $temp = unpack('Nlength', $this->_string_shift($response, 4)); + $this->server_public_host_key = $server_public_host_key = $this->_string_shift($response, $temp['length']); + + $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4)); + $public_key_format = $this->_string_shift($server_public_host_key, $temp['length']); + + $temp = unpack('Nlength', $this->_string_shift($response, 4)); + $fBytes = $this->_string_shift($response, $temp['length']); + $f = new Math_BigInteger($fBytes, -256); + + $temp = unpack('Nlength', $this->_string_shift($response, 4)); + $signature = $this->_string_shift($response, $temp['length']); + + $temp = unpack('Nlength', $this->_string_shift($signature, 4)); + $signature_format = $this->_string_shift($signature, $temp['length']); + + $key = $f->modPow($x, $p); + $keyBytes = $key->toBytes(true); + + $source = pack('Na*Na*Na*Na*Na*Na*Na*Na*', + strlen($this->identifier), $this->identifier, strlen($this->server_identifier), $this->server_identifier, + strlen($kexinit_payload_client), $kexinit_payload_client, strlen($kexinit_payload_server), + $kexinit_payload_server, strlen($this->server_public_host_key), $this->server_public_host_key, strlen($eBytes), + $eBytes, strlen($fBytes), $fBytes, strlen($keyBytes), $keyBytes + ); + + $source = pack('H*', $hash($source)); + + if ($this->session_id === false) { + $this->session_id = $source; + } + + // if you the server's assymetric key matches the one you have on file, then you should be able to decrypt the + // "signature" and get something that should equal the "exchange hash", as defined in the SSH-2 specs. + // here, we just check to see if the "signature" is good. you can verify whether or not the assymetric key is good, + // later, with the getServerHostKeyAlgorithm() function + for ($i = 0; $i < count($server_host_key_algorithms) && !in_array($server_host_key_algorithms[$i], $this->server_host_key_algorithms); $i++); + if ($i == count($server_host_key_algorithms)) { + user_error('No compatible server host key algorithms found', E_USER_NOTICE); + return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + } + + if ($public_key_format != $server_host_key_algorithms[$i] || $signature_format != $server_host_key_algorithms[$i]) { + user_error('Sever Host Key Algorithm Mismatch', E_USER_NOTICE); + return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + } + + switch ($server_host_key_algorithms[$i]) { + case 'ssh-dss': + $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4)); + $p = new Math_BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256); + + $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4)); + $q = new Math_BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256); + + $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4)); + $g = new Math_BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256); + + $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4)); + $y = new Math_BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256); + + /* The value for 'dss_signature_blob' is encoded as a string containing + r, followed by s (which are 160-bit integers, without lengths or + padding, unsigned, and in network byte order). */ + $temp = unpack('Nlength', $this->_string_shift($signature, 4)); + if ($temp['length'] != 40) { + user_error('Invalid signature', E_USER_NOTICE); + return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + } + + $r = new Math_BigInteger($this->_string_shift($signature, 20), 256); + $s = new Math_BigInteger($this->_string_shift($signature, 20), 256); + + if ($r->compare($q) >= 0 || $s->compare($q) >= 0) { + user_error('Invalid signature', E_USER_NOTICE); + return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + } + + $w = $s->modInverse($q); + + $u1 = $w->multiply(new Math_BigInteger(sha1($source), 16)); + list(, $u1) = $u1->divide($q); + + $u2 = $w->multiply($r); + list(, $u2) = $u2->divide($q); + + $g = $g->modPow($u1, $p); + $y = $y->modPow($u2, $p); + + $v = $g->multiply($y); + list(, $v) = $v->divide($p); + list(, $v) = $v->divide($q); + + if ($v->compare($r) != 0) { + user_error('Invalid signature', E_USER_NOTICE); + return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + } + + break; + case 'ssh-rsa': + $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4)); + $e = new Math_BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256); + + $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4)); + $n = new Math_BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256); + $nLength = $temp['length']; + + $temp = unpack('Nlength', $this->_string_shift($signature, 4)); + $s = new Math_BigInteger($this->_string_shift($signature, $temp['length']), 256); + + // validate an RSA signature per "8.2 RSASSA-PKCS1-v1_5", "5.2.2 RSAVP1", and "9.1 EMSA-PSS" in the + // following URL: + // ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1.pdf + + // also, see SSHRSA.c (rsa2_verifysig) in PuTTy's source. + + if ($s->compare(new Math_BigInteger()) < 0 || $s->compare($n->subtract(new Math_BigInteger(1))) > 0) { + user_error('Invalid signature', E_USER_NOTICE); + return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + } + + $s = $s->modPow($e, $n); + $s = $s->toBytes(); + + $h = chr(0x00) . chr(0x30) . chr(0x21) . chr(0x30) . chr(0x09) . chr(0x06) . chr(0x05) . chr(0x2B) . + chr(0x0E) . chr(0x03) . chr(0x02) . chr(0x1A) . chr(0x05) . chr(0x00) . chr(0x04) . chr(0x14) . + pack('H*', sha1($source)); + $h = chr(0x01) . str_repeat(chr(0xFF), $nLength - 3 - strlen($h)) . $h; + + if ($s != $h) { + user_error('Bad server signature', E_USER_NOTICE); + return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + } + } + + $packet = pack('C', + NET_SSH2_MSG_NEWKEYS + ); + + if (!$this->_send_binary_packet($packet)) { + return false; + } + + $response = $this->_get_binary_packet(); + + if ($response === false) { + user_error('Connection closed by server', E_USER_NOTICE); + return false; + } + + list(, $type) = unpack('C', $this->_string_shift($response, 1)); + + if ($type != NET_SSH2_MSG_NEWKEYS) { + user_error('Expected SSH_MSG_NEWKEYS', E_USER_NOTICE); + return false; + } + + switch ($encrypt) { + case '3des-cbc': + $this->encrypt = new Crypt_TripleDES(); + break; + case 'aes256-cbc': + //$this->encrypt = new Crypt_AES(); + break; + case 'aes192-cbc': + //$this->encrypt = new Crypt_AES(); + break; + case 'aes128-cbc': + //$this->encrypt = new Crypt_AES(); + break; + case 'arcfour': + $this->encrypt = new Crypt_RC4(); + break; + case 'none'; + //$this->encrypt = new Crypt_Null(); + } + + + switch ($decrypt) { + case '3des-cbc': + $this->decrypt = new Crypt_TripleDES(); + break; + case 'aes256-cbc': + //$this->decrypt = new Crypt_AES(); + break; + case 'aes192-cbc': + //$this->decrypt = new Crypt_AES(); + break; + case 'aes128-cbc': + //$this->decrypt = new Crypt_AES(); + break; + case 'arcfour': + $this->decrypt = new Crypt_RC4(); + break; + case 'none'; + //$this->decrypt = new Crypt_Null(); + } + + $this->encrypt->enableContinuousBuffer(); + $this->decrypt->enableContinuousBuffer(); + + $this->encrypt->disablePadding(); + $this->decrypt->disablePadding(); + + for ($i = 0; $i < count($mac_algorithms) && !in_array($mac_algorithms[$i], $this->mac_algorithms_client_to_server); $i++); + if ($i == count($mac_algorithms)) { + user_error('No compatible client to server message authentication algorithms found', E_USER_NOTICE); + return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + } + + $this->hmac_create = new Crypt_HMAC(); + switch ($mac_algorithms[$i]) { + case 'none': + $this->hmac_create->setHash('none'); + $createKeyLength = 0; + break; + case 'hmac-sha1': + $this->hmac_create->setHash('sha1'); + $createKeyLength = 20; + break; + case 'hmac-sha1-96': + $this->hmac_create->setHash('sha1-96'); + $createKeyLength = 20; + break; + case 'hmac-md5': + $this->hmac_create->setHash('md5'); + $createKeyLength = 16; + break; + case 'hmac-md5-96': + $this->hmac_create->setHash('md5-96'); + $createKeyLength = 16; + } + + for ($i = 0; $i < count($mac_algorithms) && !in_array($mac_algorithms[$i], $this->mac_algorithms_server_to_client); $i++); + if ($i == count($mac_algorithms)) { + user_error('No compatible server to client message authentication algorithms found', E_USER_NOTICE); + return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + } + + $this->hmac_check = new Crypt_HMAC(); + switch ($mac_algorithms[$i]) { + case 'none': + $this->hmac_check->setHash('none'); + $checkKeyLength = 0; + $this->hmac_size = 0; + break; + case 'hmac-sha1': + $this->hmac_check->setHash('sha1'); + $checkKeyLength = 20; + $this->hmac_size = 20; + break; + case 'hmac-sha1-96': + $this->hmac_check->setHash('sha1-96'); + $checkKeyLength = 20; + $this->hmac_size = 12; + break; + case 'hmac-md5': + $this->hmac_check->setHash('md5'); + $checkKeyLength = 16; + $this->hmac_size = 16; + break; + case 'hmac-md5-96': + $this->hmac_check->setHash('md5-96'); + $checkKeyLength = 16; + $this->hmac_size = 12; + } + + $keyBytes = pack('Na*', strlen($keyBytes), $keyBytes); + + $iv = pack('H*', $hash($keyBytes . $source . 'A' . $this->session_id)); + while ($encryptIVLength > strlen($iv)) { + $iv.= pack('H*', $hash($keyBytes . $source . $iv)); + } + $this->encrypt->setIV(substr($iv, 0, $encryptIVLength)); + + $iv = pack('H*', $hash($keyBytes . $source . 'B' . $this->session_id)); + while ($decryptIVLength > strlen($iv)) { + $iv.= pack('H*', $hash($keyBytes . $source . $iv)); + } + $this->decrypt->setIV(substr($iv, 0, $decryptIVLength)); + + $key = pack('H*', $hash($keyBytes . $source . 'C' . $this->session_id)); + while ($encryptKeyLength > strlen($key)) { + $key.= pack('H*', $hash($keyBytes . $source . $key)); + } + $this->encrypt->setKey(substr($key, 0, $encryptKeyLength)); + + $key = pack('H*', $hash($keyBytes . $source . 'D' . $this->session_id)); + while ($decryptKeyLength > strlen($key)) { + $key.= pack('H*', $hash($keyBytes . $source . $key)); + } + $this->decrypt->setKey(substr($key, 0, $decryptKeyLength)); + + $key = pack('H*', $hash($keyBytes . $source . 'E' . $this->session_id)); + while ($createKeyLength > strlen($key)) { + $key.= pack('H*', $hash($keyBytes . $source . $key)); + } + $this->hmac_create->setKey(substr($key, 0, $createKeyLength)); + + $key = pack('H*', $hash($keyBytes . $source . 'F' . $this->session_id)); + while ($checkKeyLength > strlen($key)) { + $key.= pack('H*', $hash($keyBytes . $source . $key)); + } + $this->hmac_check->setKey(substr($key, 0, $checkKeyLength)); + } + + /** + * Login + * + * @param String $username + * @param optional String $password + * @return Boolean + * @access public + */ + function login($username, $password = '') + { + if (!($this->bitmap & NET_SSH2_MASK_CONSTRUCTOR)) { + return false; + } + + $packet = pack('CNa*', + NET_SSH2_MSG_SERVICE_REQUEST, strlen('ssh-userauth'), 'ssh-userauth' + ); + + if (!$this->_send_binary_packet($packet)) { + return false; + } + + $response = $this->_get_binary_packet(); + if ($response === false) { + user_error('Connection closed by server', E_USER_NOTICE); + return false; + } + + list(, $type) = unpack('C', $this->_string_shift($response, 1)); + + if ($type != NET_SSH2_MSG_SERVICE_ACCEPT) { + user_error('Expected SSH_MSG_SERVICE_ACCEPT', E_USER_NOTICE); + return false; + } + + // publickey authentatication is required, per the SSH-2 specs, however, we don't support it. + + $utf8_password = utf8_encode($password); + $packet = pack('CNa*Na*Na*CNa*', + NET_SSH2_MSG_USERAUTH_REQUEST, strlen($username), $username, strlen('ssh-connection'), 'ssh-connection', + strlen('password'), 'password', 0, strlen($utf8_password), $utf8_password + ); + + if (!$this->_send_binary_packet($packet)) { + return false; + } + + $response = $this->_get_binary_packet($packet); + if ($response === false) { + user_error('Connection closed by server', E_USER_NOTICE); + return false; + } + + list(, $type) = unpack('C', $this->_string_shift($response, 1)); + + // maybe we should do a 'none' login in the constructor. if that works, it works and we don't need to do anything. + // if it doesn't, we can get a list of the "authentications that can continue" name-list. + + // also, we should account for SSH_MSG_USERAUTH_BANNER (http://tools.ietf.org/html/rfc4252#section-5.4) + + switch ($type) { + case NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ: // in theory, the password can be changed + list(, $length) = unpack('N', $this->_string_shift($response, 4)); + $this->debug_info.= "\r\n\r\nNET_SSH_MSG_USERAUTH_PASSWD_CHANGEREQ:\r\n" . utf8_decode($this->_string_shift($response, $length)); + return false; + case NET_SSH2_MSG_USERAUTH_FAILURE: + list(, $length) = unpack('Nlength', $this->_string_shift($response, 4)); + $this->debug_info.= "\r\n\r\nNET_SSH_MSG_USERAUTH_FAILURE:\r\n" . $this->_string_shift($response, $length); + return false; + case NET_SSH2_MSG_USERAUTH_SUCCESS: + return true; + } + + return false; + } + + /** + * Gets Binary Packets + * + * See '6. Binary Packet Protocol' of rfc4253 for more info. + * + * @see Net_SSH2::_send_binary_packet() + * @return Array + * @access private + */ + function _get_binary_packet() + { + if (feof($this->fsock)) { + user_error('Connection closed prematurely', E_USER_NOTICE); + return false; + } + + static $seq = 0; + + $raw = fread($this->fsock, $this->block_size); + + if ($this->decrypt !== false) { + $raw = $this->decrypt->decrypt($raw); + } + + $temp = unpack('Npacket_length/Cpadding_length', $this->_string_shift($raw, 5)); + $packet_length = $temp['packet_length']; + $padding_length = $temp['padding_length']; + + $temp = fread($this->fsock, $packet_length + 4 - $this->block_size); + $raw.= $this->decrypt !== false ? $this->decrypt->decrypt($temp) : $temp; + + $payload = $this->_string_shift($raw, $packet_length - $padding_length - 1); + $padding = $this->_string_shift($raw, $padding_length); // should leave $raw empty + + if ($this->hmac_check !== false) { + $hmac = fread($this->fsock, $this->hmac_size); + if ($hmac != $this->hmac_check->hmac(pack('NNCa*', $seq, $packet_length, $padding_length, $payload . $padding))) { + user_error('Invalid HMAC', E_USER_NOTICE); + return false; + } + } + + $seq++; + + switch (ord($payload[0])) { + case NET_SSH2_MSG_DISCONNECT: + $this->_string_shift($payload, 5); + list(, $length) = unpack('N', $this->_string_shift($payload, 4)); + $this->debug_info.= "\r\n\r\nNET_SSH_MSG_DISCONNECT:\r\n" . utf8_decode($this->_string_shift($payload, $length)); + $this->bitmask = 0; + return false; + case NET_SSH2_MSG_IGNORE: + $payload = $this->_get_binary_packet(); + break; + case NET_SSH2_MSG_DEBUG: + $this->_string_shift($payload, 2); + list(, $length) = unpack('N', $payload); + $this->debug_info.= "\r\n\r\nNET_SSH_MSG_DEBUG:\r\n" . utf8_decode($this->_string_shift($payload, $length)); + $payload = $this->_get_binary_packet(); + break; + case NET_SSH2_MSG_UNIMPLEMENTED: + return false; + case NET_SSH2_MSG_KEXINIT: + if ($this->session_id !== false) { + if (!$this->_key_exchange($payload)) { + $this->bitmask = 0; + return false; + } + return $this->_get_binary_packet(); + } + } + + return $payload; + } + + /** + * Sends Binary Packets + * + * See '6. Binary Packet Protocol' of rfc4253 for more info. + * + * @param String $data + * @see Net_SSH2::_get_binary_packet() + * @return A rray + * @access private + */ + function _send_binary_packet($data) + { + if (feof($this->fsock)) { + user_error('Connection closed prematurely', E_USERe_NOTICE); + return false; + } + + static $seq = 0; + + // 4, for the packet length + 1, for the padding length + 4, for the minimal padding amount + $packet_length = strlen($data) + 9; + // round up to the nearest $this->block_size + $packet_length+= (($this->block_size - 1) * $packet_length) % $this->block_size; + // subtracting strlen($data) is obvious - subtracting 5 is necessary because of packet_length and padding_length + $padding_length = $packet_length - strlen($data) - 5; + + $padding = ''; + for ($i = 0; $i < $padding_length; $i++) { + $padding.= chr(crypt_random(0, 255)); + } + + // we subtract 4 from packet_length because the packet_length field isn't supposed to include itself + $packet = pack('NCa*', $packet_length - 4, $padding_length, $data . $padding); + + $hmac = $this->hmac_create !== false ? $this->hmac_create->hmac(pack('Na*', $seq, $packet)) : ''; + $seq++; + + if ($this->encrypt !== false) { + $packet = $this->encrypt->encrypt($packet); + } + + $packet.= $hmac; + + return strlen($packet) == fputs($this->fsock, $packet); + } + + /** + * Disconnect + * + * @param Integer $reason + * @return Boolean + * @access private + */ + function _disconnect($reason) + { + $data = pack('CNNa*Na*', NET_SSH2_MSG_DISCONNECT, $reason, 0, '', 0, ''); + $this->_send_binary_packet($data); + $this->bitmask = 0; + return false; + } + + /** + * String Shift + * + * Inspired by array_shift + * + * @param String $string + * @param optional Integer $index + * @return String + * @access private + */ + function _string_shift(&$string, $index = 1) + { + $substr = substr($string, 0, $index); + $string = substr($string, $index); + return $substr; + } + + /** + * Returns Debug Information + * + * If any debug information is sent by the server, this function can be used to access it. + * + * @return String; + * @access private + */ + function getDebugInfo() + { + return $this->debug_info; + } + + /** + * Return the server identification. + * + * @return String + * @access public + */ + function getServerIdentification() + { + return $this->server_identifier; + } + + /** + * Return a list of the key exchange algorithms the server supports. + * + * @return Array + * @access public + */ + function getKexAlgorithms() + { + return $this->kex_algorithms; + } + + /** + * Return a list of the host key (public key) algorithms the server supports. + * + * @return Array + * @access public + */ + function getServerHostKeyAlgorithms() + { + return $this->server_host_key_algorithms; + } + + /** + * Return a list of the (symmetric key) encryption algorithms the server supports, when receiving stuff from the client. + * + * @return Array + * @access public + */ + function getEncryptionAlgorithmsClient2Server() + { + return $this->encryption_algorithms_client_to_server; + } + + /** + * Return a list of the (symmetric key) encryption algorithms the server supports, when sending stuff to the client. + * + * @return Array + * @access public + */ + function getEncryptionAlgorithmsServer2Client() + { + return $this->encryption_algorithms_server_to_client; + } + + /** + * Return a list of the MAC algorithms the server supports, when receiving stuff from the client. + * + * @return Array + * @access public + */ + function getMACAlgorithmsClient2Server() + { + return $this->mac_algorithms_client_to_server; + } + + /** + * Return a list of the MAC algorithms the server supports, when sending stuff to the client. + * + * @return Array + * @access public + */ + function getMACAlgorithmsServer2Client() + { + return $this->mac_algorithms_server_to_client; + } + + /** + * Return a list of the compression algorithms the server supports, when receiving stuff from the client. + * + * @return Array + * @access public + */ + function getCompressionAlgorithmsClient2Server() + { + return $this->compression_algorithms_client_to_server; + } + + /** + * Return a list of the compression algorithms the server supports, when sending stuff to the client. + * + * @return Array + * @access public + */ + function getCompressionAlgorithmsServer2Client() + { + return $this->compression_algorithms_server_to_client; + } + + /** + * Return a list of the languages the server supports, when sending stuff to the client. + * + * @return Array + * @access public + */ + function getLanguagesServer2Client() + { + return $this->languages_server_to_client; + } + + /** + * Return a list of the languages the server supports, when receiving stuff from the client. + * + * @return Array + * @access public + */ + function getLanguagesClient2Server() + { + return $this->languages_client_to_server; + } + + /** + * Returns the server public host key. + * + * Caching this the first time you connect to a server and checking the result on subsequent connections + * is recommended. + * + * @return Array + * @access public + */ + function getServerPublicHostKey() + { + return $this->server_public_host_key; + } +} +?> \ No newline at end of file