예제 #1
0
 /**
  * Generate a random string.
  *
  * Although microoptimizations are generally discouraged as they impair readability this function is ripe with
  * microoptimizations because this function has the potential of being called a huge number of times.
  * eg. for RSA key generation.
  *
  * @param Integer $length
  *
  * @return String
  * @access public
  */
 function crypt_random_string($length)
 {
     if (CRYPT_RANDOM_IS_WINDOWS) {
         // method 1. prior to PHP 5.3 this would call rand() on windows hence the function_exists('class_alias') call.
         // ie. class_alias is a function that was introduced in PHP 5.3
         if (version_compare(PHP_VERSION, '5.3.6', '!=') && function_exists('mcrypt_create_iv') && function_exists('class_alias')) {
             return mcrypt_create_iv($length);
         }
         // method 2. openssl_random_pseudo_bytes was introduced in PHP 5.3.0 but prior to PHP 5.3.4 there was,
         // to quote <http://php.net/ChangeLog-5.php#5.3.4>, "possible blocking behavior". as of 5.3.4
         // openssl_random_pseudo_bytes and mcrypt_create_iv do the exact same thing on Windows. ie. they both
         // call php_win32_get_random_bytes():
         //
         // https://github.com/php/php-src/blob/7014a0eb6d1611151a286c0ff4f2238f92c120d6/ext/openssl/openssl.c#L5008
         // https://github.com/php/php-src/blob/7014a0eb6d1611151a286c0ff4f2238f92c120d6/ext/mcrypt/mcrypt.c#L1392
         //
         // php_win32_get_random_bytes() is defined thusly:
         //
         // https://github.com/php/php-src/blob/7014a0eb6d1611151a286c0ff4f2238f92c120d6/win32/winutil.c#L80
         //
         // we're calling it, all the same, in the off chance that the mcrypt extension is not available
         if (function_exists('openssl_random_pseudo_bytes') && version_compare(PHP_VERSION, '5.3.4', '>=')) {
             return openssl_random_pseudo_bytes($length);
         }
     } else {
         // method 1. the fastest
         if (function_exists('openssl_random_pseudo_bytes')) {
             return openssl_random_pseudo_bytes($length);
         }
         // method 2
         static $fp = true;
         if ($fp === true) {
             // warning's will be output unles the error suppression operator is used. errors such as
             // "open_basedir restriction in effect", "Permission denied", "No such file or directory", etc.
             $fp = @fopen('/dev/urandom', 'rb');
         }
         if ($fp !== true && $fp !== false) {
             // surprisingly faster than !is_bool() or is_resource()
             return fread($fp, $length);
         }
         // method 3. pretty much does the same thing as method 2 per the following url:
         // https://github.com/php/php-src/blob/7014a0eb6d1611151a286c0ff4f2238f92c120d6/ext/mcrypt/mcrypt.c#L1391
         // surprisingly slower than method 2. maybe that's because mcrypt_create_iv does a bunch of error checking that we're
         // not doing. regardless, this'll only be called if this PHP script couldn't open /dev/urandom due to open_basedir
         // restrictions or some such
         if (function_exists('mcrypt_create_iv')) {
             return mcrypt_create_iv($length, MCRYPT_DEV_URANDOM);
         }
     }
     // at this point we have no choice but to use a pure-PHP CSPRNG
     // cascade entropy across multiple PHP instances by fixing the session and collecting all
     // environmental variables, including the previous session data and the current session
     // data.
     //
     // mt_rand seeds itself by looking at the PID and the time, both of which are (relatively)
     // easy to guess at. linux uses mouse clicks, keyboard timings, etc, as entropy sources, but
     // PHP isn't low level to be able to use those as sources and on a web server there's not likely
     // going to be a ton of keyboard or mouse action. web servers do have one thing that we can use
     // however, a ton of people visiting the website. obviously you don't want to base your seeding
     // soley on parameters a potential attacker sends but (1) not everything in $_SERVER is controlled
     // by the user and (2) this isn't just looking at the data sent by the current user - it's based
     // on the data sent by all users. one user requests the page and a hash of their info is saved.
     // another user visits the page and the serialization of their data is utilized along with the
     // server envirnment stuff and a hash of the previous http request data (which itself utilizes
     // a hash of the session data before that). certainly an attacker should be assumed to have
     // full control over his own http requests. he, however, is not going to have control over
     // everyone's http requests.
     static $crypto = false, $v;
     if ($crypto === false) {
         // save old session data
         $old_session_id = session_id();
         $old_use_cookies = ini_get('session.use_cookies');
         $old_session_cache_limiter = session_cache_limiter();
         $_OLD_SESSION = isset($_SESSION) ? $_SESSION : false;
         if ($old_session_id != '') {
             session_write_close();
         }
         session_id(1);
         ini_set('session.use_cookies', 0);
         session_cache_limiter('');
         session_start();
         $v = $seed = $_SESSION['seed'] = pack('H*', sha1(serialize($_SERVER) . serialize($_POST) . serialize($_GET) . serialize($_COOKIE) . serialize($GLOBALS) . serialize($_SESSION) . serialize($_OLD_SESSION)));
         if (!isset($_SESSION['count'])) {
             $_SESSION['count'] = 0;
         }
         $_SESSION['count']++;
         session_write_close();
         // restore old session data
         if ($old_session_id != '') {
             session_id($old_session_id);
             session_start();
             ini_set('session.use_cookies', $old_use_cookies);
             session_cache_limiter($old_session_cache_limiter);
         } else {
             if ($_OLD_SESSION !== false) {
                 $_SESSION = $_OLD_SESSION;
                 unset($_OLD_SESSION);
             } else {
                 unset($_SESSION);
             }
         }
         // in SSH2 a shared secret and an exchange hash are generated through the key exchange process.
         // the IV client to server is the hash of that "nonce" with the letter A and for the encryption key it's the letter C.
         // if the hash doesn't produce enough a key or an IV that's long enough concat successive hashes of the
         // original hash and the current hash. we'll be emulating that. for more info see the following URL:
         //
         // http://tools.ietf.org/html/rfc4253#section-7.2
         //
         // see the is_string($crypto) part for an example of how to expand the keys
         $key = pack('H*', sha1($seed . 'A'));
         $iv = pack('H*', sha1($seed . 'C'));
         // ciphers are used as per the nist.gov link below. also, see this link:
         //
         // http://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator#Designs_based_on_cryptographic_primitives
         switch (true) {
             case mwp_phpseclib_resolve_include_path('Crypt/AES.php'):
                 if (!class_exists('Crypt_AES')) {
                     require_once dirname(__FILE__) . '/AES.php';
                 }
                 $crypto = new Crypt_AES(CRYPT_AES_MODE_CTR);
                 break;
             case mwp_phpseclib_resolve_include_path('Crypt/Twofish.php'):
                 if (!class_exists('Crypt_Twofish')) {
                     require_once dirname(__FILE__) . '/Twofish.php';
                 }
                 $crypto = new Crypt_Twofish(CRYPT_TWOFISH_MODE_CTR);
                 break;
             case mwp_phpseclib_resolve_include_path('Crypt/Blowfish.php'):
                 if (!class_exists('Crypt_Blowfish')) {
                     require_once dirname(__FILE__) . '/Blowfish.php';
                 }
                 $crypto = new Crypt_Blowfish(CRYPT_BLOWFISH_MODE_CTR);
                 break;
             case mwp_phpseclib_resolve_include_path('Crypt/TripleDES.php'):
                 if (!class_exists('Crypt_TripleDES')) {
                     require_once dirname(__FILE__) . '/TripleDES.php';
                 }
                 $crypto = new Crypt_TripleDES(CRYPT_DES_MODE_CTR);
                 break;
             case mwp_phpseclib_resolve_include_path('Crypt/DES.php'):
                 if (!class_exists('Crypt_DES')) {
                     require_once dirname(__FILE__) . '/DES.php';
                 }
                 $crypto = new Crypt_DES(CRYPT_DES_MODE_CTR);
                 break;
             case mwp_phpseclib_resolve_include_path('Crypt/RC4.php'):
                 if (!class_exists('Crypt_RC4')) {
                     require_once dirname(__FILE__) . '/RC4.php';
                 }
                 $crypto = new Crypt_RC4();
                 break;
             default:
                 user_error('crypt_random_string requires at least one symmetric cipher be loaded');
                 return false;
         }
         $crypto->setKey($key);
         $crypto->setIV($iv);
         $crypto->enableContinuousBuffer();
     }
     //return $crypto->encrypt(str_repeat("\0", $length));
     // the following is based off of ANSI X9.31:
     //
     // http://csrc.nist.gov/groups/STM/cavp/documents/rng/931rngext.pdf
     //
     // OpenSSL uses that same standard for it's random numbers:
     //
     // http://www.opensource.apple.com/source/OpenSSL/OpenSSL-38/openssl/fips-1.0/rand/fips_rand.c
     // (do a search for "ANS X9.31 A.2.4")
     $result = '';
     while (strlen($result) < $length) {
         $i = $crypto->encrypt(microtime());
         // strlen(microtime()) == 21
         $r = $crypto->encrypt($i ^ $v);
         // strlen($v) == 20
         $v = $crypto->encrypt($r ^ $i);
         // strlen($r) == 20
         $result .= $r;
     }
     return substr($result, 0, $length);
 }
예제 #2
0
 /**
  * Key Exchange
  *
  * @param String $kexinit_payload_server
  *
  * @access private
  */
 public function _key_exchange($kexinit_payload_server)
 {
     static $kex_algorithms = array('diffie-hellman-group1-sha1', 'diffie-hellman-group14-sha1');
     static $server_host_key_algorithms = array('ssh-rsa', 'ssh-dss');
     static $encryption_algorithms = false;
     if ($encryption_algorithms === false) {
         $encryption_algorithms = array('arcfour256', 'arcfour128', 'arcfour', 'aes128-ctr', 'aes192-ctr', 'aes256-ctr', 'twofish128-ctr', 'twofish192-ctr', 'twofish256-ctr', 'aes128-cbc', 'aes192-cbc', 'aes256-cbc', 'twofish128-cbc', 'twofish192-cbc', 'twofish256-cbc', 'twofish-cbc', 'blowfish-ctr', 'blowfish-cbc', '3des-ctr', '3des-cbc', 'none');
         if (mwp_phpseclib_resolve_include_path('Crypt/RC4.php') === false) {
             $encryption_algorithms = array_diff($encryption_algorithms, array('arcfour256', 'arcfour128', 'arcfour'));
         }
         if (mwp_phpseclib_resolve_include_path('Crypt/Rijndael.php') === false) {
             $encryption_algorithms = array_diff($encryption_algorithms, array('aes128-ctr', 'aes192-ctr', 'aes256-ctr', 'aes128-cbc', 'aes192-cbc', 'aes256-cbc'));
         }
         if (mwp_phpseclib_resolve_include_path('Crypt/Twofish.php') === false) {
             $encryption_algorithms = array_diff($encryption_algorithms, array('twofish128-ctr', 'twofish192-ctr', 'twofish256-ctr', 'twofish128-cbc', 'twofish192-cbc', 'twofish256-cbc', 'twofish-cbc'));
         }
         if (mwp_phpseclib_resolve_include_path('Crypt/Blowfish.php') === false) {
             $encryption_algorithms = array_diff($encryption_algorithms, array('blowfish-ctr', 'blowfish-cbc'));
         }
         if (mwp_phpseclib_resolve_include_path('Crypt/TripleDES.php') === false) {
             $encryption_algorithms = array_diff($encryption_algorithms, array('3des-ctr', '3des-cbc'));
         }
         $encryption_algorithms = array_values($encryption_algorithms);
     }
     $mac_algorithms = array('hmac-sha1-96', 'hmac-sha1', 'hmac-md5-96', 'hmac-md5', 'none');
     static $compression_algorithms = array('none');
     // some SSH servers have buggy implementations of some of the above algorithms
     switch ($this->server_identifier) {
         case 'SSH-2.0-SSHD':
             $mac_algorithms = array_values(array_diff($mac_algorithms, array('hmac-sha1-96', 'hmac-md5-96')));
     }
     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 = crypt_random_string(16);
     $response = $kexinit_payload_server;
     $this->_string_shift($response, 1);
     // skip past the message number (it should be SSH_MSG_KEXINIT)
     $server_cookie = $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']));
     extract(unpack('Cfirst_kex_packet_follows', $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');
         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':
         case '3des-ctr':
             $decryptKeyLength = 24;
             // eg. 192 / 8
             break;
         case 'aes256-cbc':
         case 'aes256-ctr':
         case 'twofish-cbc':
         case 'twofish256-cbc':
         case 'twofish256-ctr':
             $decryptKeyLength = 32;
             // eg. 256 / 8
             break;
         case 'aes192-cbc':
         case 'aes192-ctr':
         case 'twofish192-cbc':
         case 'twofish192-ctr':
             $decryptKeyLength = 24;
             // eg. 192 / 8
             break;
         case 'aes128-cbc':
         case 'aes128-ctr':
         case 'twofish128-cbc':
         case 'twofish128-ctr':
         case 'blowfish-cbc':
         case 'blowfish-ctr':
             $decryptKeyLength = 16;
             // eg. 128 / 8
             break;
         case 'arcfour':
         case 'arcfour128':
             $decryptKeyLength = 16;
             // eg. 128 / 8
             break;
         case 'arcfour256':
             $decryptKeyLength = 32;
             // eg. 128 / 8
             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');
         return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
     }
     $encrypt = $encryption_algorithms[$i];
     switch ($encrypt) {
         case '3des-cbc':
         case '3des-ctr':
             $encryptKeyLength = 24;
             break;
         case 'aes256-cbc':
         case 'aes256-ctr':
         case 'twofish-cbc':
         case 'twofish256-cbc':
         case 'twofish256-ctr':
             $encryptKeyLength = 32;
             break;
         case 'aes192-cbc':
         case 'aes192-ctr':
         case 'twofish192-cbc':
         case 'twofish192-ctr':
             $encryptKeyLength = 24;
             break;
         case 'aes128-cbc':
         case 'aes128-ctr':
         case 'twofish128-cbc':
         case 'twofish128-ctr':
         case 'blowfish-cbc':
         case 'blowfish-ctr':
             $encryptKeyLength = 16;
             break;
         case 'arcfour':
         case 'arcfour128':
             $encryptKeyLength = 16;
             break;
         case 'arcfour256':
             $encryptKeyLength = 32;
             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');
         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':
             $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' . '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' . '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' . 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF';
             break;
             // see http://tools.ietf.org/html/rfc3526#section-3
         // see http://tools.ietf.org/html/rfc3526#section-3
         case 'diffie-hellman-group14-sha1':
             $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' . '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' . '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' . 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' . '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' . '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' . 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' . '3995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF';
             break;
     }
     // For both diffie-hellman-group1-sha1 and diffie-hellman-group14-sha1
     // the generator field element is 2 (decimal) and the hash function is sha1.
     $g = new Math_BigInteger(2);
     $prime = new Math_BigInteger($prime, 16);
     $kexHash = new Crypt_Hash('sha1');
     //$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 */
     $one = new Math_BigInteger(1);
     $keyLength = min($keyLength, $kexHash->getLength());
     $max = $one->bitwise_leftShift(16 * $keyLength);
     // 2 * 8 * $keyLength
     $max = $max->subtract($one);
     $x = $one->random($one, $max);
     $e = $g->modPow($x, $prime);
     $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');
         return false;
     }
     $response = $this->_get_binary_packet();
     if ($response === false) {
         user_error('Connection closed by server');
         return false;
     }
     extract(unpack('Ctype', $this->_string_shift($response, 1)));
     if ($type != NET_SSH2_MSG_KEXDH_REPLY) {
         user_error('Expected SSH_MSG_KEXDH_REPLY');
         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));
     $this->signature = $this->_string_shift($response, $temp['length']);
     $temp = unpack('Nlength', $this->_string_shift($this->signature, 4));
     $this->signature_format = $this->_string_shift($this->signature, $temp['length']);
     $key = $f->modPow($x, $prime);
     $keyBytes = $key->toBytes(true);
     $this->exchange_hash = 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);
     $this->exchange_hash = $kexHash->hash($this->exchange_hash);
     if ($this->session_id === false) {
         $this->session_id = $this->exchange_hash;
     }
     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');
         return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
     }
     if ($public_key_format != $server_host_key_algorithms[$i] || $this->signature_format != $server_host_key_algorithms[$i]) {
         user_error('Server Host Key Algorithm Mismatch');
         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');
         return false;
     }
     extract(unpack('Ctype', $this->_string_shift($response, 1)));
     if ($type != NET_SSH2_MSG_NEWKEYS) {
         user_error('Expected SSH_MSG_NEWKEYS');
         return false;
     }
     switch ($encrypt) {
         case '3des-cbc':
             if (!class_exists('Crypt_TripleDES')) {
                 require_once dirname(__FILE__) . '/../Crypt/TripleDES.php';
             }
             $this->encrypt = new Crypt_TripleDES();
             // $this->encrypt_block_size = 64 / 8 == the default
             break;
         case '3des-ctr':
             if (!class_exists('Crypt_TripleDES')) {
                 require_once dirname(__FILE__) . '/../Crypt/TripleDES.php';
             }
             $this->encrypt = new Crypt_TripleDES(CRYPT_DES_MODE_CTR);
             // $this->encrypt_block_size = 64 / 8 == the default
             break;
         case 'aes256-cbc':
         case 'aes192-cbc':
         case 'aes128-cbc':
             if (!class_exists('Crypt_Rijndael')) {
                 require_once dirname(__FILE__) . '/../Crypt/Rijndael.php';
             }
             $this->encrypt = new Crypt_Rijndael();
             $this->encrypt_block_size = 16;
             // eg. 128 / 8
             break;
         case 'aes256-ctr':
         case 'aes192-ctr':
         case 'aes128-ctr':
             if (!class_exists('Crypt_Rijndael')) {
                 require_once dirname(__FILE__) . '/../Crypt/Rijndael.php';
             }
             $this->encrypt = new Crypt_Rijndael(CRYPT_RIJNDAEL_MODE_CTR);
             $this->encrypt_block_size = 16;
             // eg. 128 / 8
             break;
         case 'blowfish-cbc':
             if (!class_exists('Crypt_Blowfish')) {
                 require_once dirname(__FILE__) . '/../Crypt/Blowfish.php';
             }
             $this->encrypt = new Crypt_Blowfish();
             $this->encrypt_block_size = 8;
             break;
         case 'blowfish-ctr':
             if (!class_exists('Crypt_Blowfish')) {
                 require_once dirname(__FILE__) . '/../Crypt/Blowfish.php';
             }
             $this->encrypt = new Crypt_Blowfish(CRYPT_BLOWFISH_MODE_CTR);
             $this->encrypt_block_size = 8;
             break;
         case 'twofish128-cbc':
         case 'twofish192-cbc':
         case 'twofish256-cbc':
         case 'twofish-cbc':
             if (!class_exists('Crypt_Twofish')) {
                 require_once dirname(__FILE__) . '/../Crypt/Twofish.php';
             }
             $this->encrypt = new Crypt_Twofish();
             $this->encrypt_block_size = 16;
             break;
         case 'twofish128-ctr':
         case 'twofish192-ctr':
         case 'twofish256-ctr':
             if (!class_exists('Crypt_Twofish')) {
                 require_once dirname(__FILE__) . '/../Crypt/Twofish.php';
             }
             $this->encrypt = new Crypt_Twofish(CRYPT_TWOFISH_MODE_CTR);
             $this->encrypt_block_size = 16;
             break;
         case 'arcfour':
         case 'arcfour128':
         case 'arcfour256':
             if (!class_exists('Crypt_RC4')) {
                 require_once dirname(__FILE__) . '/../Crypt/RC4.php';
             }
             $this->encrypt = new Crypt_RC4();
             break;
         case 'none':
             //$this->encrypt = new Crypt_Null();
     }
     switch ($decrypt) {
         case '3des-cbc':
             if (!class_exists('Crypt_TripleDES')) {
                 require_once dirname(__FILE__) . '/../Crypt/TripleDES.php';
             }
             $this->decrypt = new Crypt_TripleDES();
             break;
         case '3des-ctr':
             if (!class_exists('Crypt_TripleDES')) {
                 require_once dirname(__FILE__) . '/../Crypt/TripleDES.php';
             }
             $this->decrypt = new Crypt_TripleDES(CRYPT_DES_MODE_CTR);
             break;
         case 'aes256-cbc':
         case 'aes192-cbc':
         case 'aes128-cbc':
             if (!class_exists('Crypt_Rijndael')) {
                 require_once dirname(__FILE__) . '/../Crypt/Rijndael.php';
             }
             $this->decrypt = new Crypt_Rijndael();
             $this->decrypt_block_size = 16;
             break;
         case 'aes256-ctr':
         case 'aes192-ctr':
         case 'aes128-ctr':
             if (!class_exists('Crypt_Rijndael')) {
                 require_once dirname(__FILE__) . '/../Crypt/Rijndael.php';
             }
             $this->decrypt = new Crypt_Rijndael(CRYPT_RIJNDAEL_MODE_CTR);
             $this->decrypt_block_size = 16;
             break;
         case 'blowfish-cbc':
             if (!class_exists('Crypt_Blowfish')) {
                 require_once dirname(__FILE__) . '/../Crypt/Blowfish.php';
             }
             $this->decrypt = new Crypt_Blowfish();
             $this->decrypt_block_size = 8;
             break;
         case 'blowfish-ctr':
             if (!class_exists('Crypt_Blowfish')) {
                 require_once dirname(__FILE__) . '/../Crypt/Blowfish.php';
             }
             $this->decrypt = new Crypt_Blowfish(CRYPT_BLOWFISH_MODE_CTR);
             $this->decrypt_block_size = 8;
             break;
         case 'twofish128-cbc':
         case 'twofish192-cbc':
         case 'twofish256-cbc':
         case 'twofish-cbc':
             if (!class_exists('Crypt_Twofish')) {
                 require_once dirname(__FILE__) . '/../Crypt/Twofish.php';
             }
             $this->decrypt = new Crypt_Twofish();
             $this->decrypt_block_size = 16;
             break;
         case 'twofish128-ctr':
         case 'twofish192-ctr':
         case 'twofish256-ctr':
             if (!class_exists('Crypt_Twofish')) {
                 require_once dirname(__FILE__) . '/../Crypt/Twofish.php';
             }
             $this->decrypt = new Crypt_Twofish(CRYPT_TWOFISH_MODE_CTR);
             $this->decrypt_block_size = 16;
             break;
         case 'arcfour':
         case 'arcfour128':
         case 'arcfour256':
             if (!class_exists('Crypt_RC4')) {
                 require_once dirname(__FILE__) . '/../Crypt/RC4.php';
             }
             $this->decrypt = new Crypt_RC4();
             break;
         case 'none':
             //$this->decrypt = new Crypt_Null();
     }
     $keyBytes = pack('Na*', strlen($keyBytes), $keyBytes);
     if ($this->encrypt) {
         $this->encrypt->enableContinuousBuffer();
         $this->encrypt->disablePadding();
         $iv = $kexHash->hash($keyBytes . $this->exchange_hash . 'A' . $this->session_id);
         while ($this->encrypt_block_size > strlen($iv)) {
             $iv .= $kexHash->hash($keyBytes . $this->exchange_hash . $iv);
         }
         $this->encrypt->setIV(substr($iv, 0, $this->encrypt_block_size));
         $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'C' . $this->session_id);
         while ($encryptKeyLength > strlen($key)) {
             $key .= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
         }
         $this->encrypt->setKey(substr($key, 0, $encryptKeyLength));
     }
     if ($this->decrypt) {
         $this->decrypt->enableContinuousBuffer();
         $this->decrypt->disablePadding();
         $iv = $kexHash->hash($keyBytes . $this->exchange_hash . 'B' . $this->session_id);
         while ($this->decrypt_block_size > strlen($iv)) {
             $iv .= $kexHash->hash($keyBytes . $this->exchange_hash . $iv);
         }
         $this->decrypt->setIV(substr($iv, 0, $this->decrypt_block_size));
         $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'D' . $this->session_id);
         while ($decryptKeyLength > strlen($key)) {
             $key .= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
         }
         $this->decrypt->setKey(substr($key, 0, $decryptKeyLength));
     }
     /* The "arcfour128" algorithm is the RC4 cipher, as described in
                [SCHNEIER], using a 128-bit key.  The first 1536 bytes of keystream
                generated by the cipher MUST be discarded, and the first byte of the
                first encrypted packet MUST be encrypted using the 1537th byte of
                keystream.
     
                -- http://tools.ietf.org/html/rfc4345#section-4 */
     if ($encrypt == 'arcfour128' || $encrypt == 'arcfour256') {
         $this->encrypt->encrypt(str_repeat("", 1536));
     }
     if ($decrypt == 'arcfour128' || $decrypt == 'arcfour256') {
         $this->decrypt->decrypt(str_repeat("", 1536));
     }
     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');
         return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
     }
     $createKeyLength = 0;
     // ie. $mac_algorithms[$i] == 'none'
     switch ($mac_algorithms[$i]) {
         case 'hmac-sha1':
             $this->hmac_create = new Crypt_Hash('sha1');
             $createKeyLength = 20;
             break;
         case 'hmac-sha1-96':
             $this->hmac_create = new Crypt_Hash('sha1-96');
             $createKeyLength = 20;
             break;
         case 'hmac-md5':
             $this->hmac_create = new Crypt_Hash('md5');
             $createKeyLength = 16;
             break;
         case 'hmac-md5-96':
             $this->hmac_create = new Crypt_Hash('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');
         return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
     }
     $checkKeyLength = 0;
     $this->hmac_size = 0;
     switch ($mac_algorithms[$i]) {
         case 'hmac-sha1':
             $this->hmac_check = new Crypt_Hash('sha1');
             $checkKeyLength = 20;
             $this->hmac_size = 20;
             break;
         case 'hmac-sha1-96':
             $this->hmac_check = new Crypt_Hash('sha1-96');
             $checkKeyLength = 20;
             $this->hmac_size = 12;
             break;
         case 'hmac-md5':
             $this->hmac_check = new Crypt_Hash('md5');
             $checkKeyLength = 16;
             $this->hmac_size = 16;
             break;
         case 'hmac-md5-96':
             $this->hmac_check = new Crypt_Hash('md5-96');
             $checkKeyLength = 16;
             $this->hmac_size = 12;
     }
     $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'E' . $this->session_id);
     while ($createKeyLength > strlen($key)) {
         $key .= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
     }
     $this->hmac_create->setKey(substr($key, 0, $createKeyLength));
     $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'F' . $this->session_id);
     while ($checkKeyLength > strlen($key)) {
         $key .= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
     }
     $this->hmac_check->setKey(substr($key, 0, $checkKeyLength));
     for ($i = 0; $i < count($compression_algorithms) && !in_array($compression_algorithms[$i], $this->compression_algorithms_server_to_client); $i++) {
     }
     if ($i == count($compression_algorithms)) {
         user_error('No compatible server to client compression algorithms found');
         return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
     }
     $this->decompress = $compression_algorithms[$i] == 'zlib';
     for ($i = 0; $i < count($compression_algorithms) && !in_array($compression_algorithms[$i], $this->compression_algorithms_client_to_server); $i++) {
     }
     if ($i == count($compression_algorithms)) {
         user_error('No compatible client to server compression algorithms found');
         return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
     }
     $this->compress = $compression_algorithms[$i] == 'zlib';
     return true;
 }