/** * Break a public or private key down into its constituent components * * @access public * @param string $key * @param string $password optional * @return array */ static function load($key, $password = '') { if (!is_string($key)) { return false; } $parts = explode(' ', $key, 3); $key = isset($parts[1]) ? Base64::decode($parts[1]) : Base64::decode($parts[0]); if ($key === false) { return false; } $comment = isset($parts[2]) ? $parts[2] : false; if (substr($key, 0, 11) != "ssh-rsa") { return false; } Strings::shift($key, 11); if (strlen($key) <= 4) { return false; } extract(unpack('Nlength', Strings::shift($key, 4))); if (strlen($key) <= $length) { return false; } $publicExponent = new BigInteger(Strings::shift($key, $length), -256); if (strlen($key) <= 4) { return false; } extract(unpack('Nlength', Strings::shift($key, 4))); if (strlen($key) != $length) { return false; } $modulus = new BigInteger(Strings::shift($key, $length), -256); return array('isPublicKey' => true, 'modulus' => $modulus, 'publicExponent' => $publicExponent, 'comment' => $comment); }
/** * Break a public or private key down into its constituent components * * @access public * @param string $key * @param string $password optional * @return array */ static function load($key, $password = '') { if (!is_string($key)) { return false; } static $one; if (!isset($one)) { $one = new BigInteger(1); } if (strpos($key, 'BEGIN SSH2 PUBLIC KEY')) { $data = preg_split('#[\\r\\n]+#', $key); $data = array_splice($data, 2, -1); $data = implode('', $data); $components = OpenSSH::load($data); if ($components === false) { return false; } if (!preg_match('#Comment: "(.+)"#', $key, $matches)) { return false; } $components['comment'] = str_replace(array('\\\\', '\\"'), array('\\', '"'), $matches[1]); return $components; } $components = array('isPublicKey' => false); $key = preg_split('#\\r\\n|\\r|\\n#', $key); $type = trim(preg_replace('#PuTTY-User-Key-File-2: (.+)#', '$1', $key[0])); if ($type != 'ssh-rsa') { return false; } $encryption = trim(preg_replace('#Encryption: (.+)#', '$1', $key[1])); $components['comment'] = trim(preg_replace('#Comment: (.+)#', '$1', $key[2])); $publicLength = trim(preg_replace('#Public-Lines: (\\d+)#', '$1', $key[3])); $public = Base64::decode(implode('', array_map('trim', array_slice($key, 4, $publicLength)))); $public = substr($public, 11); extract(unpack('Nlength', Strings::shift($public, 4))); $components['publicExponent'] = new BigInteger(Strings::shift($public, $length), -256); extract(unpack('Nlength', Strings::shift($public, 4))); $components['modulus'] = new BigInteger(Strings::shift($public, $length), -256); $privateLength = trim(preg_replace('#Private-Lines: (\\d+)#', '$1', $key[$publicLength + 4])); $private = Base64::decode(implode('', array_map('trim', array_slice($key, $publicLength + 5, $privateLength)))); switch ($encryption) { case 'aes256-cbc': $symkey = static::generateSymmetricKey($password, 32); $crypto = new AES(AES::MODE_CBC); } if ($encryption != 'none') { $crypto->setKey($symkey); $crypto->setIV(str_repeat("", $crypto->getBlockLength() >> 3)); $crypto->disablePadding(); $private = $crypto->decrypt($private); } extract(unpack('Nlength', Strings::shift($private, 4))); if (strlen($private) < $length) { return false; } $components['privateExponent'] = new BigInteger(Strings::shift($private, $length), -256); extract(unpack('Nlength', Strings::shift($private, 4))); if (strlen($private) < $length) { return false; } $components['primes'] = array(1 => new BigInteger(Strings::shift($private, $length), -256)); extract(unpack('Nlength', Strings::shift($private, 4))); if (strlen($private) < $length) { return false; } $components['primes'][] = new BigInteger(Strings::shift($private, $length), -256); $temp = $components['primes'][1]->subtract($one); $components['exponents'] = array(1 => $components['publicExponent']->modInverse($temp)); $temp = $components['primes'][2]->subtract($one); $components['exponents'][] = $components['publicExponent']->modInverse($temp); extract(unpack('Nlength', Strings::shift($private, 4))); if (strlen($private) < $length) { return false; } $components['coefficients'] = array(2 => new BigInteger(Strings::shift($private, $length), -256)); return $components; }
/** * Break a public or private key down into its constituent components * * @access public * @param string $key * @param string $password optional * @return array */ static function load($key, $password = '') { if (!is_string($key)) { return false; } $components = array('isPublicKey' => strpos($key, 'PUBLIC') !== false); /* Although PKCS#1 proposes a format that public and private keys can use, encrypting them is "outside the scope" of PKCS#1. PKCS#1 then refers you to PKCS#12 and PKCS#15 if you're wanting to protect private keys, however, that's not what OpenSSL* does. OpenSSL protects private keys by adding two new "fields" to the key - DEK-Info and Proc-Type. These fields are discussed here: http://tools.ietf.org/html/rfc1421#section-4.6.1.1 http://tools.ietf.org/html/rfc1421#section-4.6.1.3 DES-EDE3-CBC as an algorithm, however, is not discussed anywhere, near as I can tell. DES-CBC and DES-EDE are discussed in RFC1423, however, DES-EDE3-CBC isn't, nor is its key derivation function. As is, the definitive authority on this encoding scheme isn't the IETF but rather OpenSSL's own implementation. ie. the implementation *is* the standard and any bugs that may exist in that implementation are part of the standard, as well. * OpenSSL is the de facto standard. It's utilized by OpenSSH and other projects */ if (preg_match('#DEK-Info: (.+),(.+)#', $key, $matches)) { $iv = Hex::decode(trim($matches[2])); // remove the Proc-Type / DEK-Info sections as they're no longer needed $key = preg_replace('#^(?:Proc-Type|DEK-Info): .*#m', '', $key); $ciphertext = self::_extractBER($key); if ($ciphertext === false) { $ciphertext = $key; } $crypto = self::getEncryptionObject($matches[1]); $crypto->setKey(self::generateSymmetricKey($password, $iv, $crypto->getKeyLength() >> 3)); $crypto->setIV($iv); $key = $crypto->decrypt($ciphertext); if ($key === false) { return false; } } else { if (self::$format != self::MODE_DER) { $decoded = self::_extractBER($key); if ($decoded !== false) { $key = $decoded; } elseif (self::$format == self::MODE_PEM) { return false; } } } if (ord(Strings::shift($key)) != self::ASN1_SEQUENCE) { return false; } if (ASN1::decodeLength($key) != strlen($key)) { return false; } $tag = ord(Strings::shift($key)); /* intended for keys for which OpenSSL's asn1parse returns the following: 0:d=0 hl=4 l= 631 cons: SEQUENCE 4:d=1 hl=2 l= 1 prim: INTEGER :00 7:d=1 hl=2 l= 13 cons: SEQUENCE 9:d=2 hl=2 l= 9 prim: OBJECT :rsaEncryption 20:d=2 hl=2 l= 0 prim: NULL 22:d=1 hl=4 l= 609 prim: OCTET STRING ie. PKCS8 keys */ if ($tag == self::ASN1_INTEGER && substr($key, 0, 3) == "0") { Strings::shift($key, 3); $tag = self::ASN1_SEQUENCE; } if ($tag == self::ASN1_SEQUENCE) { $temp = Strings::shift($key, ASN1::decodeLength($key)); if (ord(Strings::shift($temp)) != self::ASN1_OBJECT) { return false; } $length = ASN1::decodeLength($temp); switch (Strings::shift($temp, $length)) { case "*†H†÷\r": // rsaEncryption break; case "*†H†÷\r": // pbeWithMD5AndDES-CBC /* PBEParameter ::= SEQUENCE { salt OCTET STRING (SIZE(8)), iterationCount INTEGER } */ if (ord(Strings::shift($temp)) != self::ASN1_SEQUENCE) { return false; } if (ASN1::decodeLength($temp) != strlen($temp)) { return false; } Strings::shift($temp); // assume it's an octet string $salt = Strings::shift($temp, ASN1::decodeLength($temp)); if (ord(Strings::shift($temp)) != self::ASN1_INTEGER) { return false; } ASN1::decodeLength($temp); list(, $iterationCount) = unpack('N', str_pad($temp, 4, chr(0), STR_PAD_LEFT)); Strings::shift($key); // assume it's an octet string $length = ASN1::decodeLength($key); if (strlen($key) != $length) { return false; } $crypto = new DES(DES::MODE_CBC); $crypto->setPassword($password, 'pbkdf1', 'md5', $salt, $iterationCount); $key = $crypto->decrypt($key); if ($key === false) { return false; } return self::load($key); default: return false; } /* intended for keys for which OpenSSL's asn1parse returns the following: 0:d=0 hl=4 l= 290 cons: SEQUENCE 4:d=1 hl=2 l= 13 cons: SEQUENCE 6:d=2 hl=2 l= 9 prim: OBJECT :rsaEncryption 17:d=2 hl=2 l= 0 prim: NULL 19:d=1 hl=4 l= 271 prim: BIT STRING */ $tag = ord(Strings::shift($key)); // skip over the BIT STRING / OCTET STRING tag ASN1::decodeLength($key); // skip over the BIT STRING / OCTET STRING length // "The initial octet shall encode, as an unsigned binary integer wtih bit 1 as the least significant bit, the number of // unused bits in the final subsequent octet. The number shall be in the range zero to seven." // -- http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf (section 8.6.2.2) if ($tag == self::ASN1_BITSTRING) { Strings::shift($key); } if (ord(Strings::shift($key)) != self::ASN1_SEQUENCE) { return false; } if (ASN1::decodeLength($key) != strlen($key)) { return false; } $tag = ord(Strings::shift($key)); } if ($tag != self::ASN1_INTEGER) { return false; } $length = ASN1::decodeLength($key); $temp = Strings::shift($key, $length); if (strlen($temp) != 1 || ord($temp) > 2) { $components['modulus'] = new BigInteger($temp, 256); Strings::shift($key); // skip over self::ASN1_INTEGER $length = ASN1::decodeLength($key); $components[$components['isPublicKey'] ? 'publicExponent' : 'privateExponent'] = new BigInteger(Strings::shift($key, $length), 256); return $components; } if (ord(Strings::shift($key)) != self::ASN1_INTEGER) { return false; } $length = ASN1::decodeLength($key); $components['modulus'] = new BigInteger(Strings::shift($key, $length), 256); Strings::shift($key); $length = ASN1::decodeLength($key); $components['publicExponent'] = new BigInteger(Strings::shift($key, $length), 256); Strings::shift($key); $length = ASN1::decodeLength($key); $components['privateExponent'] = new BigInteger(Strings::shift($key, $length), 256); Strings::shift($key); $length = ASN1::decodeLength($key); $components['primes'] = array(1 => new BigInteger(Strings::shift($key, $length), 256)); Strings::shift($key); $length = ASN1::decodeLength($key); $components['primes'][] = new BigInteger(Strings::shift($key, $length), 256); Strings::shift($key); $length = ASN1::decodeLength($key); $components['exponents'] = array(1 => new BigInteger(Strings::shift($key, $length), 256)); Strings::shift($key); $length = ASN1::decodeLength($key); $components['exponents'][] = new BigInteger(Strings::shift($key, $length), 256); Strings::shift($key); $length = ASN1::decodeLength($key); $components['coefficients'] = array(2 => new BigInteger(Strings::shift($key, $length), 256)); if (!empty($key)) { if (ord(Strings::shift($key)) != self::ASN1_SEQUENCE) { return false; } ASN1::decodeLength($key); while (!empty($key)) { if (ord(Strings::shift($key)) != self::ASN1_SEQUENCE) { return false; } ASN1::decodeLength($key); $key = substr($key, 1); $length = ASN1::decodeLength($key); $components['primes'][] = new BigInteger(Strings::shift($key, $length), 256); Strings::shift($key); $length = ASN1::decodeLength($key); $components['exponents'][] = new BigInteger(Strings::shift($key, $length), 256); Strings::shift($key); $length = ASN1::decodeLength($key); $components['coefficients'][] = new BigInteger(Strings::shift($key, $length), 256); } } return $components; }
/** * 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 * @throws \RuntimeException on badly formatted keys * @throws \phpseclib\Exception\NoSupportedAlgorithmsException when the key isn't in a supported format * @access public */ function getServerPublicHostKey() { if (!($this->bitmap & self::MASK_CONSTRUCTOR)) { if (!$this->_connect()) { return false; } } $signature = $this->signature; $server_public_host_key = $this->server_public_host_key; if (strlen($server_public_host_key) < 4) { return false; } extract(unpack('Nlength', Strings::shift($server_public_host_key, 4))); Strings::shift($server_public_host_key, $length); if ($this->signature_validated) { return $this->bitmap ? $this->signature_format . ' ' . Base64::encode($this->server_public_host_key) : false; } $this->signature_validated = true; switch ($this->signature_format) { case 'ssh-dss': $zero = new BigInteger(); if (strlen($server_public_host_key) < 4) { return false; } $temp = unpack('Nlength', Strings::shift($server_public_host_key, 4)); $p = new BigInteger(Strings::shift($server_public_host_key, $temp['length']), -256); if (strlen($server_public_host_key) < 4) { return false; } $temp = unpack('Nlength', Strings::shift($server_public_host_key, 4)); $q = new BigInteger(Strings::shift($server_public_host_key, $temp['length']), -256); if (strlen($server_public_host_key) < 4) { return false; } $temp = unpack('Nlength', Strings::shift($server_public_host_key, 4)); $g = new BigInteger(Strings::shift($server_public_host_key, $temp['length']), -256); if (strlen($server_public_host_key) < 4) { return false; } $temp = unpack('Nlength', Strings::shift($server_public_host_key, 4)); $y = new BigInteger(Strings::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', Strings::shift($signature, 4)); if ($temp['length'] != 40) { $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); throw new \RuntimeException('Invalid signature'); } $r = new BigInteger(Strings::shift($signature, 20), 256); $s = new BigInteger(Strings::shift($signature, 20), 256); switch (true) { case $r->equals($zero): case $r->compare($q) >= 0: case $s->equals($zero): case $s->compare($q) >= 0: $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); throw new \RuntimeException('Invalid signature'); } $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'); return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); } break; case 'ssh-rsa': if (strlen($server_public_host_key) < 4) { return false; } $temp = unpack('Nlength', Strings::shift($server_public_host_key, 4)); $e = new BigInteger(Strings::shift($server_public_host_key, $temp['length']), -256); if (strlen($server_public_host_key) < 4) { return false; } $temp = unpack('Nlength', Strings::shift($server_public_host_key, 4)); $rawN = Strings::shift($server_public_host_key, $temp['length']); $n = new BigInteger($rawN, -256); $nLength = strlen(ltrim($rawN, "")); /* if (strlen($signature) < 4) { return false; } $temp = unpack('Nlength', Strings::shift($signature, 4)); $signature = Strings::shift($signature, $temp['length']); $rsa = new RSA(); $rsa->load(array('e' => $e, 'n' => $n), 'raw'); $rsa->setHash('sha1'); if (!$rsa->verify($this->exchange_hash, $signature, RSA::PADDING_PKCS1)) { //user_error('Bad server signature'); return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); } */ if (strlen($signature) < 4) { return false; } $temp = unpack('Nlength', Strings::shift($signature, 4)); $s = new BigInteger(Strings::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) { $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); throw new \RuntimeException('Invalid signature'); } $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 - 2 - strlen($h)) . $h; if ($s != $h) { //user_error('Bad server signature'); return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); } break; default: $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); throw new NoSupportedAlgorithmsException('Unsupported signature format'); } return $this->signature_format . ' ' . Base64::encode($this->server_public_host_key); }
/** * Break a public or private key down into its constituent components * * @access public * @param string $key * @param string $password optional * @return array */ static function load($key, $password = '') { if (!is_string($key)) { return false; } $key = Base64::decode($key); if (!is_string($key) || strlen($key) < 20) { return false; } // PUBLICKEYSTRUC publickeystruc // https://msdn.microsoft.com/en-us/library/windows/desktop/aa387453(v=vs.85).aspx extract(unpack('atype/aversion/vreserved/Valgo', Strings::shift($key, 8))); switch (ord($type)) { case self::PUBLICKEYBLOB: case self::PUBLICKEYBLOBEX: $publickey = true; break; case self::PRIVATEKEYBLOB: $publickey = false; break; default: return false; } $components = array('isPublicKey' => $publickey); // https://msdn.microsoft.com/en-us/library/windows/desktop/aa375549(v=vs.85).aspx switch ($algo) { case self::CALG_RSA_KEYX: case self::CALG_RSA_SIGN: break; default: return false; } // RSAPUBKEY rsapubkey // https://msdn.microsoft.com/en-us/library/windows/desktop/aa387685(v=vs.85).aspx // could do V for pubexp but that's unsigned 32-bit whereas some PHP installs only do signed 32-bit extract(unpack('Vmagic/Vbitlen/a4pubexp', Strings::shift($key, 12))); switch ($magic) { case self::RSA2: $components['isPublicKey'] = false; case self::RSA1: break; default: return false; } $baseLength = $bitlen / 16; if (strlen($key) != 2 * $baseLength && strlen($key) != 9 * $baseLength) { return false; } $components[$components['isPublicKey'] ? 'publicExponent' : 'privateExponent'] = new BigInteger(strrev($pubexp), 256); // BYTE modulus[rsapubkey.bitlen/8] $components['modulus'] = new BigInteger(strrev(Strings::shift($key, $bitlen / 8)), 256); if ($publickey) { return $components; } $components['isPublicKey'] = false; // BYTE prime1[rsapubkey.bitlen/16] $components['primes'] = array(1 => new BigInteger(strrev(Strings::shift($key, $bitlen / 16)), 256)); // BYTE prime2[rsapubkey.bitlen/16] $components['primes'][] = new BigInteger(strrev(Strings::shift($key, $bitlen / 16)), 256); // BYTE exponent1[rsapubkey.bitlen/16] $components['exponents'] = array(1 => new BigInteger(strrev(Strings::shift($key, $bitlen / 16)), 256)); // BYTE exponent2[rsapubkey.bitlen/16] $components['exponents'][] = new BigInteger(strrev(Strings::shift($key, $bitlen / 16)), 256); // BYTE coefficient[rsapubkey.bitlen/16] $components['coefficients'] = array(2 => new BigInteger(strrev(Strings::shift($key, $bitlen / 16)), 256)); if (isset($components['privateExponent'])) { $components['publicExponent'] = $components['privateExponent']; } // BYTE privateExponent[rsapubkey.bitlen/8] $components['privateExponent'] = new BigInteger(strrev(Strings::shift($key, $bitlen / 8)), 256); return $components; }
/** * Receives a packet from an SSH server * * @return string * @throws \UnexpectedValueException on receipt of an unexpected packet * @access private */ function _receive() { switch ($this->mode) { case self::MODE_SSH2: return $this->ssh->_get_channel_packet(SSH2::CHANNEL_EXEC, true); case self::MODE_SSH1: if (!$this->ssh->bitmap) { return false; } while (true) { $response = $this->ssh->_get_binary_packet(); switch ($response[SSH1::RESPONSE_TYPE]) { case NET_SSH1_SMSG_STDOUT_DATA: if (strlen($response[SSH1::RESPONSE_DATA]) < 4) { return false; } extract(unpack('Nlength', $response[SSH1::RESPONSE_DATA])); return Strings::shift($response[SSH1::RESPONSE_DATA], $length); case NET_SSH1_SMSG_STDERR_DATA: break; case NET_SSH1_SMSG_EXITSTATUS: $this->ssh->_send_binary_packet(chr(NET_SSH1_CMSG_EXIT_CONFIRMATION)); fclose($this->ssh->fsock); $this->ssh->bitmap = 0; return false; default: throw new \UnexpectedValueException('Unknown packet received'); } } } }
/** * RSASSA-PKCS1-V1_5-VERIFY (relaxed matching) * * Per {@link http://tools.ietf.org/html/rfc3447#page-43 RFC3447#page-43} PKCS1 v1.5 * specified the use BER encoding rather than DER encoding that PKCS1 v2.0 specified. * This means that under rare conditions you can have a perfectly valid v1.5 signature * that fails to validate with _rsassa_pkcs1_v1_5_verify(). PKCS1 v2.1 also recommends * that if you're going to validate these types of signatures you "should indicate * whether the underlying BER encoding is a DER encoding and hence whether the signature * is valid with respect to the specification given in [PKCS1 v2.0+]". so if you do * $rsa->getLastPadding() and get RSA::PADDING_RELAXED_PKCS1 back instead of * RSA::PADDING_PKCS1... that means BER encoding was used. * * @access private * @param string $m * @param string $s * @return bool */ function _rsassa_pkcs1_v1_5_relaxed_verify($m, $s) { // Length checking if (strlen($s) != $this->k) { return false; } // RSA verification $s = $this->_os2ip($s); $m2 = $this->_rsavp1($s); if ($m2 === false) { return false; } $em = $this->_i2osp($m2, $this->k); if ($em === false) { return false; } if (Strings::shift($em, 2) != "") { return false; } $em = ltrim($em, "ÿ"); if (Strings::shift($em) != "") { return false; } $asn1 = new ASN1(); $decoded = $asn1->decodeBER($em); if (!is_array($decoded) || empty($decoded[0]) || strlen($em) > $decoded[0]['length']) { return false; } $AlgorithmIdentifier = array('type' => ASN1::TYPE_SEQUENCE, 'children' => array('algorithm' => array('type' => ASN1::TYPE_OBJECT_IDENTIFIER), 'parameters' => array('type' => ASN1::TYPE_ANY, 'optional' => true))); $DigestInfo = array('type' => ASN1::TYPE_SEQUENCE, 'children' => array('digestAlgorithm' => $AlgorithmIdentifier, 'digest' => array('type' => ASN1::TYPE_OCTET_STRING))); $oids = array('1.2.840.113549.2.2' => 'md2', '1.2.840.113549.2.4' => 'md4', '1.2.840.113549.2.5' => 'md5', '1.3.14.3.2.26' => 'sha1', '2.16.840.1.101.3.4.2.1' => 'sha256', '2.16.840.1.101.3.4.2.2' => 'sha384', '2.16.840.1.101.3.4.2.3' => 'sha512', '2.16.840.1.101.3.4.2.4' => 'sha224', '2.16.840.1.101.3.4.2.5' => 'sha512/224', '2.16.840.1.101.3.4.2.6' => 'sha512/256'); $asn1->loadOIDs($oids); $decoded = $asn1->asn1map($decoded[0], $DigestInfo); if (!isset($decoded) || $decoded === false) { return false; } if (!in_array($decoded['digestAlgorithm']['algorithm'], $oids)) { return false; } $hash = new Hash($decoded['digestAlgorithm']['algorithm']); $em = $hash->hash($m); $em2 = Base64::decode($decoded['digest']); return $this->_equals($em, $em2); }
/** * Receives SFTP Packets * * See '6. General Packet Format' of draft-ietf-secsh-filexfer-13 for more info. * * Incidentally, the number of SSH_MSG_CHANNEL_DATA messages has no bearing on the number of SFTP packets present. * There can be one SSH_MSG_CHANNEL_DATA messages containing two SFTP packets or there can be two SSH_MSG_CHANNEL_DATA * messages containing one SFTP packet. * * @see self::_send_sftp_packet() * @return string * @access private */ function _get_sftp_packet() { $this->curTimeout = false; $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838 // SFTP packet length while (strlen($this->packet_buffer) < 4) { $temp = $this->_get_channel_packet(self::CHANNEL); if (is_bool($temp)) { $this->packet_type = false; $this->packet_buffer = ''; return false; } $this->packet_buffer .= $temp; } if (strlen($this->packet_buffer) < 4) { return false; } extract(unpack('Nlength', Strings::shift($this->packet_buffer, 4))); $tempLength = $length; $tempLength -= strlen($this->packet_buffer); // SFTP packet type and data payload while ($tempLength > 0) { $temp = $this->_get_channel_packet(self::CHANNEL); if (is_bool($temp)) { $this->packet_type = false; $this->packet_buffer = ''; return false; } $this->packet_buffer .= $temp; $tempLength -= strlen($temp); } $stop = strtok(microtime(), ' ') + strtok(''); $this->packet_type = ord(Strings::shift($this->packet_buffer)); if ($this->request_id !== false) { Strings::shift($this->packet_buffer, 4); // remove the request id $length -= 5; // account for the request id and the packet type } else { $length -= 1; // account for the packet type } $packet = Strings::shift($this->packet_buffer, $length); if (defined('NET_SFTP_LOGGING')) { $packet_type = '<- ' . $this->packet_types[$this->packet_type] . ' (' . round($stop - $start, 4) . 's)'; if (NET_SFTP_LOGGING == self::LOG_REALTIME) { echo "<pre>\r\n" . $this->_format_log(array($packet), array($packet_type)) . "\r\n</pre>\r\n"; flush(); ob_flush(); } else { $this->packet_type_log[] = $packet_type; if (NET_SFTP_LOGGING == self::LOG_COMPLEX) { $this->packet_log[] = $packet; } } } return $packet; }
/** * Pure-PHP implementation of SHA512 * * @access private * @param string $m */ static function _sha512($m, $hash) { static $k; if (!isset($k)) { // Initialize table of round constants // (first 64 bits of the fractional parts of the cube roots of the first 80 primes 2..409) $k = array('428a2f98d728ae22', '7137449123ef65cd', 'b5c0fbcfec4d3b2f', 'e9b5dba58189dbbc', '3956c25bf348b538', '59f111f1b605d019', '923f82a4af194f9b', 'ab1c5ed5da6d8118', 'd807aa98a3030242', '12835b0145706fbe', '243185be4ee4b28c', '550c7dc3d5ffb4e2', '72be5d74f27b896f', '80deb1fe3b1696b1', '9bdc06a725c71235', 'c19bf174cf692694', 'e49b69c19ef14ad2', 'efbe4786384f25e3', '0fc19dc68b8cd5b5', '240ca1cc77ac9c65', '2de92c6f592b0275', '4a7484aa6ea6e483', '5cb0a9dcbd41fbd4', '76f988da831153b5', '983e5152ee66dfab', 'a831c66d2db43210', 'b00327c898fb213f', 'bf597fc7beef0ee4', 'c6e00bf33da88fc2', 'd5a79147930aa725', '06ca6351e003826f', '142929670a0e6e70', '27b70a8546d22ffc', '2e1b21385c26c926', '4d2c6dfc5ac42aed', '53380d139d95b3df', '650a73548baf63de', '766a0abb3c77b2a8', '81c2c92e47edaee6', '92722c851482353b', 'a2bfe8a14cf10364', 'a81a664bbc423001', 'c24b8b70d0f89791', 'c76c51a30654be30', 'd192e819d6ef5218', 'd69906245565a910', 'f40e35855771202a', '106aa07032bbd1b8', '19a4c116b8d2d0c8', '1e376c085141ab53', '2748774cdf8eeb99', '34b0bcb5e19b48a8', '391c0cb3c5c95a63', '4ed8aa4ae3418acb', '5b9cca4f7763e373', '682e6ff3d6b2b8a3', '748f82ee5defb2fc', '78a5636f43172f60', '84c87814a1f0ab72', '8cc702081a6439ec', '90befffa23631e28', 'a4506cebde82bde9', 'bef9a3f7b2c67915', 'c67178f2e372532b', 'ca273eceea26619c', 'd186b8c721c0c207', 'eada7dd6cde0eb1e', 'f57d4f7fee6ed178', '06f067aa72176fba', '0a637dc5a2c898a6', '113f9804bef90dae', '1b710b35131c471b', '28db77f523047d84', '32caab7b40c72493', '3c9ebe0a15c9bebc', '431d67c49c100d4c', '4cc5d4becb3e42b6', '597f299cfc657e2a', '5fcb6fab3ad6faec', '6c44198c4a475817'); for ($i = 0; $i < 80; $i++) { $k[$i] = new BigInteger($k[$i], 16); } } // Pre-processing $length = strlen($m); // to round to nearest 112 mod 128, we'll add 128 - (length + (128 - 112)) % 128 $m .= str_repeat(chr(0), 128 - ($length + 16 & 0x7f)); $m[$length] = chr(0x80); // we don't support hashing strings 512MB long $m .= pack('N4', 0, 0, 0, $length << 3); // Process the message in successive 1024-bit chunks $chunks = str_split($m, 128); foreach ($chunks as $chunk) { $w = array(); for ($i = 0; $i < 16; $i++) { $temp = new BigInteger(Strings::shift($chunk, 8), 256); $temp->setPrecision(64); $w[] = $temp; } // Extend the sixteen 32-bit words into eighty 32-bit words for ($i = 16; $i < 80; $i++) { $temp = array($w[$i - 15]->bitwise_rightRotate(1), $w[$i - 15]->bitwise_rightRotate(8), $w[$i - 15]->bitwise_rightShift(7)); $s0 = $temp[0]->bitwise_xor($temp[1]); $s0 = $s0->bitwise_xor($temp[2]); $temp = array($w[$i - 2]->bitwise_rightRotate(19), $w[$i - 2]->bitwise_rightRotate(61), $w[$i - 2]->bitwise_rightShift(6)); $s1 = $temp[0]->bitwise_xor($temp[1]); $s1 = $s1->bitwise_xor($temp[2]); $w[$i] = clone $w[$i - 16]; $w[$i] = $w[$i]->add($s0); $w[$i] = $w[$i]->add($w[$i - 7]); $w[$i] = $w[$i]->add($s1); } // Initialize hash value for this chunk $a = clone $hash[0]; $b = clone $hash[1]; $c = clone $hash[2]; $d = clone $hash[3]; $e = clone $hash[4]; $f = clone $hash[5]; $g = clone $hash[6]; $h = clone $hash[7]; // Main loop for ($i = 0; $i < 80; $i++) { $temp = array($a->bitwise_rightRotate(28), $a->bitwise_rightRotate(34), $a->bitwise_rightRotate(39)); $s0 = $temp[0]->bitwise_xor($temp[1]); $s0 = $s0->bitwise_xor($temp[2]); $temp = array($a->bitwise_and($b), $a->bitwise_and($c), $b->bitwise_and($c)); $maj = $temp[0]->bitwise_xor($temp[1]); $maj = $maj->bitwise_xor($temp[2]); $t2 = $s0->add($maj); $temp = array($e->bitwise_rightRotate(14), $e->bitwise_rightRotate(18), $e->bitwise_rightRotate(41)); $s1 = $temp[0]->bitwise_xor($temp[1]); $s1 = $s1->bitwise_xor($temp[2]); $temp = array($e->bitwise_and($f), $g->bitwise_and($e->bitwise_not())); $ch = $temp[0]->bitwise_xor($temp[1]); $t1 = $h->add($s1); $t1 = $t1->add($ch); $t1 = $t1->add($k[$i]); $t1 = $t1->add($w[$i]); $h = clone $g; $g = clone $f; $f = clone $e; $e = $d->add($t1); $d = clone $c; $c = clone $b; $b = clone $a; $a = $t1->add($t2); } // Add this chunk's hash to result so far $hash = array($hash[0]->add($a), $hash[1]->add($b), $hash[2]->add($c), $hash[3]->add($d), $hash[4]->add($e), $hash[5]->add($f), $hash[6]->add($g), $hash[7]->add($h)); } // Produce the final hash value (big-endian) // (\phpseclib\Crypt\Hash::hash() trims the output for hashes but not for HMACs. as such, we trim the output here) $temp = $hash[0]->toBytes() . $hash[1]->toBytes() . $hash[2]->toBytes() . $hash[3]->toBytes() . $hash[4]->toBytes() . $hash[5]->toBytes() . $hash[6]->toBytes() . $hash[7]->toBytes(); return $temp; }
/** * OpenSSL OFB Processor * * PHP's OpenSSL bindings do not operate in continuous mode so we'll wrap around it. Since the keystream * for OFB is the same for both encrypting and decrypting this function is re-used by both SymmetricKey::encrypt() * and SymmetricKey::decrypt(). * * @see self::encrypt() * @see self::decrypt() * @param string $plaintext * @param string $encryptIV * @param array $buffer * @return string * @access private */ function _openssl_ofb_process($plaintext, &$encryptIV, &$buffer) { if (strlen($buffer['xor'])) { $ciphertext = $plaintext ^ $buffer['xor']; $buffer['xor'] = substr($buffer['xor'], strlen($ciphertext)); $plaintext = substr($plaintext, strlen($ciphertext)); } else { $ciphertext = ''; } $block_size = $this->block_size; $len = strlen($plaintext); $key = $this->key; $overflow = $len % $block_size; if (strlen($plaintext)) { if ($overflow) { $ciphertext .= openssl_encrypt(substr($plaintext, 0, -$overflow) . str_repeat("", $block_size), $this->cipher_name_openssl, $key, $this->openssl_options, $encryptIV); $xor = $this->_string_pop($ciphertext, $block_size); if ($this->continuousBuffer) { $encryptIV = $xor; } $ciphertext .= Strings::shift($xor, $overflow) ^ substr($plaintext, -$overflow); if ($this->continuousBuffer) { $buffer['xor'] = $xor; } } else { $ciphertext = openssl_encrypt($plaintext, $this->cipher_name_openssl, $key, $this->openssl_options, $encryptIV); if ($this->continuousBuffer) { $encryptIV = substr($ciphertext, -$block_size) ^ substr($plaintext, -$block_size); } } } return $ciphertext; }
/** * Logs data packets * * Makes sure that only the last 1MB worth of packets will be logged * * @param string $data * @access private */ function _append_log($protocol_flags, $message) { switch (NET_SSH1_LOGGING) { // useful for benchmarks case self::LOG_SIMPLE: $this->protocol_flags_log[] = $protocol_flags; break; // the most useful log for SSH1 // the most useful log for SSH1 case self::LOG_COMPLEX: $this->protocol_flags_log[] = $protocol_flags; Strings::shift($message); $this->log_size += strlen($message); $this->message_log[] = $message; while ($this->log_size > self::LOG_MAX_SIZE) { $this->log_size -= strlen(array_shift($this->message_log)); array_shift($this->protocol_flags_log); } break; // dump the output out realtime; packets may be interspersed with non packets, // passwords won't be filtered out and select other packets may not be correctly // identified // dump the output out realtime; packets may be interspersed with non packets, // passwords won't be filtered out and select other packets may not be correctly // identified case self::LOG_REALTIME: echo "<pre>\r\n" . $this->_format_log(array($message), array($protocol_flags)) . "\r\n</pre>\r\n"; @flush(); @ob_flush(); break; // basically the same thing as self::LOG_REALTIME with the caveat that self::LOG_REALTIME_FILE // needs to be defined and that the resultant log file will be capped out at self::LOG_MAX_SIZE. // the earliest part of the log file is denoted by the first <<< START >>> and is not going to necessarily // at the beginning of the file // basically the same thing as self::LOG_REALTIME with the caveat that self::LOG_REALTIME_FILE // needs to be defined and that the resultant log file will be capped out at self::LOG_MAX_SIZE. // the earliest part of the log file is denoted by the first <<< START >>> and is not going to necessarily // at the beginning of the file case self::LOG_REALTIME_FILE: if (!isset($this->realtime_log_file)) { // PHP doesn't seem to like using constants in fopen() $filename = self::LOG_REALTIME_FILE; $fp = fopen($filename, 'w'); $this->realtime_log_file = $fp; } if (!is_resource($this->realtime_log_file)) { break; } $entry = $this->_format_log(array($message), array($protocol_flags)); if ($this->realtime_log_wrap) { $temp = "<<< START >>>\r\n"; $entry .= $temp; fseek($this->realtime_log_file, ftell($this->realtime_log_file) - strlen($temp)); } $this->realtime_log_size += strlen($entry); if ($this->realtime_log_size > self::LOG_MAX_SIZE) { fseek($this->realtime_log_file, 0); $this->realtime_log_size = strlen($entry); $this->realtime_log_wrap = true; } fputs($this->realtime_log_file, $entry); } }