/** * Create public / private key pair * * Returns an array with the following three elements: * - 'privatekey': The private key. * - 'publickey': The public key. * - 'partialkey': A partially computed key (if the execution time exceeded $timeout). * Will need to be passed back to RSA::createKey() as the third parameter for further processing. * * @access public * @param optional Integer $bits * @param optional Integer $timeout * @param optional BigInteger $p */ function createKey($bits = 1024, $timeout = false, $partial = array()) { if (RSA_MODE == RSA_MODE_OPENSSL) { $rsa = openssl_pkey_new(array('private_key_bits' => $bits)); openssl_pkey_export($rsa, $privatekey); $publickey = openssl_pkey_get_details($rsa); $publickey = $publickey['key']; if ($this->privateKeyFormat != RSA_PRIVATE_FORMAT_PKCS1) { $privatekey = call_user_func_array(array($this, '_convertPrivateKey'), array_values($this->_parseKey($privatekey, RSA_PRIVATE_FORMAT_PKCS1))); $publickey = call_user_func_array(array($this, '_convertPublicKey'), array_values($this->_parseKey($publickey, RSA_PUBLIC_FORMAT_PKCS1))); } return array('privatekey' => $privatekey, 'publickey' => $publickey, 'partialkey' => false); } static $e; if (!isset($e)) { if (!defined('RSA_EXPONENT')) { // http://en.wikipedia.org/wiki/65537_%28number%29 define('RSA_EXPONENT', '65537'); } if (!defined('RSA_COMMENT')) { define('RSA_COMMENT', 'phpseclib-generated-key'); } // per <http://cseweb.ucsd.edu/~hovav/dist/survey.pdf#page=5>, this number ought not result in primes smaller // than 256 bits. if (!defined('RSA_SMALLEST_PRIME')) { define('RSA_SMALLEST_PRIME', 4096); } $e = new BigInteger(RSA_EXPONENT); } extract($this->_generateMinMax($bits)); $absoluteMin = $min; $temp = $bits >> 1; if ($temp > RSA_SMALLEST_PRIME) { $num_primes = floor($bits / RSA_SMALLEST_PRIME); $temp = RSA_SMALLEST_PRIME; } else { $num_primes = 2; } extract($this->_generateMinMax($temp + $bits % $temp)); $finalMax = $max; extract($this->_generateMinMax($temp)); $generator = new BigInteger(); $generator->setRandomGenerator('Random'); $n = $this->one->copy(); if (!empty($partial)) { extract(unserialize($partial)); } else { $exponents = $coefficients = $primes = array(); $lcm = array('top' => $this->one->copy(), 'bottom' => false); } $start = time(); $i0 = count($primes) + 1; do { for ($i = $i0; $i <= $num_primes; $i++) { if ($timeout !== false) { $timeout -= time() - $start; $start = time(); if ($timeout <= 0) { return array('privatekey' => '', 'publickey' => '', 'partialkey' => serialize(array('primes' => $primes, 'coefficients' => $coefficients, 'lcm' => $lcm, 'exponents' => $exponents))); } } if ($i == $num_primes) { list($min, $temp) = $absoluteMin->divide($n); if (!$temp->equals($this->zero)) { $min = $min->add($this->one); // ie. ceil() } $primes[$i] = $generator->randomPrime($min, $finalMax, $timeout); } else { $primes[$i] = $generator->randomPrime($min, $max, $timeout); } if ($primes[$i] === false) { // if we've reached the timeout if (count($primes) > 1) { $partialkey = ''; } else { array_pop($primes); $partialkey = serialize(array('primes' => $primes, 'coefficients' => $coefficients, 'lcm' => $lcm, 'exponents' => $exponents)); } return array('privatekey' => '', 'publickey' => '', 'partialkey' => $partialkey); } // the first coefficient is calculated differently from the rest // ie. instead of being $primes[1]->modInverse($primes[2]), it's $primes[2]->modInverse($primes[1]) if ($i > 2) { $coefficients[$i] = $n->modInverse($primes[$i]); } $n = $n->multiply($primes[$i]); $temp = $primes[$i]->subtract($this->one); // textbook RSA implementations use Euler's totient function instead of the least common multiple. // see http://en.wikipedia.org/wiki/Euler%27s_totient_function $lcm['top'] = $lcm['top']->multiply($temp); $lcm['bottom'] = $lcm['bottom'] === false ? $temp : $lcm['bottom']->gcd($temp); $exponents[$i] = $e->modInverse($temp); } list($lcm) = $lcm['top']->divide($lcm['bottom']); $gcd = $lcm->gcd($e); $i0 = 1; } while (!$gcd->equals($this->one)); $d = $e->modInverse($lcm); $coefficients[2] = $primes[2]->modInverse($primes[1]); // from <http://tools.ietf.org/html/rfc3447#appendix-A.1.2>: // RSAPrivateKey ::= SEQUENCE { // version Version, // modulus INTEGER, -- n // publicExponent INTEGER, -- e // privateExponent INTEGER, -- d // prime1 INTEGER, -- p // prime2 INTEGER, -- q // exponent1 INTEGER, -- d mod (p-1) // exponent2 INTEGER, -- d mod (q-1) // coefficient INTEGER, -- (inverse of q) mod p // otherPrimeInfos OtherPrimeInfos OPTIONAL // } return array('privatekey' => $this->_convertPrivateKey($n, $e, $d, $primes, $exponents, $coefficients), 'publickey' => $this->_convertPublicKey($n, $e), 'partialkey' => false); }
/** * 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. Returns false if the server signature is not signed correctly with the public host key. * * @return Mixed * @access public */ function getServerPublicHostKey() { $signature = $this->signature; $server_public_host_key = $this->server_public_host_key; extract(unpack('Nlength', $this->_string_shift($server_public_host_key, 4))); $this->_string_shift($server_public_host_key, $length); switch ($this->signature_format) { case 'ssh-dss': $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4)); $p = new BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256); $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4)); $q = new BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256); $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4)); $g = new BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256); $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4)); $y = new 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(SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); } $r = new BigInteger($this->_string_shift($signature, 20), 256); $s = new 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(SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); } $w = $s->modInverse($q); $u1 = $w->multiply(new BigInteger(sha1($this->exchange_hash), 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->equals($r)) { user_error('Bad server signature', E_USER_NOTICE); return $this->_disconnect(SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); } break; case 'ssh-rsa': $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4)); $e = new BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256); $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4)); $n = new BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256); $nLength = $temp['length']; /* $temp = unpack('Nlength', $this->_string_shift($signature, 4)); $signature = $this->_string_shift($signature, $temp['length']); if (!class_exists('RSA')) { require_once('Crypt/RSA.php'); } $rsa = new RSA(); $rsa->setSignatureMode(RSA_SIGNATURE_PKCS1); $rsa->loadKey(array('e' => $e, 'n' => $n), RSA_PUBLIC_FORMAT_RAW); if (!$rsa->verify($this->exchange_hash, $signature)) { user_error('Bad server signature', E_USER_NOTICE); return $this->_disconnect(SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); } */ $temp = unpack('Nlength', $this->_string_shift($signature, 4)); $s = new 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 BigInteger()) < 0 || $s->compare($n->subtract(new BigInteger(1))) > 0) { user_error('Invalid signature', E_USER_NOTICE); return $this->_disconnect(SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); } $s = $s->modPow($e, $n); $s = $s->toBytes(); $h = pack('N4H*', 0x302130, 0x906052b, 0xe03021a, 0x5000414, sha1($this->exchange_hash)); $h = chr(0x1) . str_repeat(chr(0xff), $nLength - 3 - strlen($h)) . $h; if ($s != $h) { user_error('Bad server signature', E_USER_NOTICE); return $this->_disconnect(SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); } } return $this->server_public_host_key; }