Skip to main content
Version: 4.0

Connecting

Basic Example with Signature Verification

As the "secure" part of Secure Shell (SSH) implies, SSH is designed to work over hostile networks. SSH encrypts your data so that eavesdroppers cannot read the data being sent back and forth and it provides a method that can be used to verify that the server you're connecting to hasn't been replaced with a hostile server. To facilitate the latter SSH servers have a host public key. Data that's unique to the SSH session is signed by the server and should be verified by the client with the host public key. Of course, simply verifying the signature is insufficient - you need to verify that the host public key is correct. X.509 / SSL / TLS does this with certificate authorities but in SSH, in theory, you'd get the host public key through some out-of-band method. In practice, however, people usually just cache the key the first time they connect to a server and assume all subsequent connections should be using that same key. How the expected host key is saved is up to the application designer (OpenSSH saves them in ~/.ssh/known_hosts) but here is an example of how the host key would be retrieved from the SSH server (prior to authentication) and checked against the expected value ($expected):

use phpseclib4\Net\SSH2;

$ssh = new SSH2('localhost', 22);
if ($expected != $ssh->getServerPublicHostKey()) {
throw new \Exception('Host key verification failed');
}

All subsequent code samples omit this part for brevity but if you're concerned about eavesdroppers (which isn't always a legit concern; eg. if you're connecting to localhost) it should not be skipped.

The constructor signature is:

public function __construct(
mixed $host,
int $port = 22,
int $timeout = 10
)

The port number is optional and defaults to 22. The third argument is the TCP connect timeout in seconds.

Connection is Lazy

The constructor doesn't actually connect to anything. The TCP connection and SSH handshake only happen on the first call to one of:

  • login()
  • getServerIdentification()
  • getServerAlgorithms()
  • getAlgorithmsNegotiated()
  • getServerPublicHostKey()

This is intentional. It lets you configure the instance (algorithm preferences, terminal type, quirks toggles) before any wire activity starts. Calling isConnected() before any of the above will return bool(false).

Failure Modes

When the connection itself fails, phpseclib throws one of the following exceptions. All extend \RuntimeException and implement phpseclib4\Exception\BaseException, so a single catch (\RuntimeException $e) covers all of them.

FailureException
TCP connect failedphpseclib4\Exception\UnableToConnectException
Connection dropped mid-handshakephpseclib4\Exception\ConnectionClosedException
No mutually supported KEX / host-key / cipher / MAC / compressionphpseclib4\Exception\NoSupportedAlgorithmsException
Server identification string malformedphpseclib4\Exception\UnexpectedValueException

Note that authentication failure does not throw. $ssh->login() returns bool(false) instead. See Authenticating for more on that.

Using an HTTP Proxy

use phpseclib4\Net\SSH2;

$fsock = fsockopen('127.0.0.1', 80, $errno, $errstr, 1);
if (!$fsock) {
throw new \Exception($errstr);
}
fputs($fsock, "CONNECT localhost:22 HTTP/1.0\r\n");
//fputs($fsock, "Proxy-Authorization: Basic " . base64_encode('user:pass') . "\r\n");
fputs($fsock, "\r\n");
while ($line = fgets($fsock, 1024)) {
if ($line == "\r\n") {
break;
}
//echo $line;
}

$ssh = new SSH2($fsock);
$ssh->login('username', 'password');
echo $ssh->exec('ls -latr');

When you pass a stream resource as the first argument, the $port argument is ignored but the $timeout argument is still honored.

Using a SOCKS5 Proxy

use phpseclib4\Net\SSH2;

// SSH connection info
$port = 22;
$address = 'localhost';

// SOCKS5 connection info
$fsock = fsockopen('127.0.0.1', 1080, $errno, $errstr, 1);
if (!$fsock) {
throw new \Exception($errstr);
}

$port = pack('n', $port);
$address = chr(strlen($address)) . $address;

$request = "\5\1\0";
if (fwrite($fsock, $request) != strlen($request)) {
throw new \Exception('Premature termination');
}

$response = fread($fsock, 2);
if ($response != "\5\0") {
throw new \Exception('Unsupported protocol or unsupported method');
}

$request = "\5\1\0\3$address$port";
if (fwrite($fsock, $request) != strlen($request)) {
throw new \Exception('Premature termination');
}

$response = fread($fsock, strlen($address) + 6);
if (substr($response, 0, 2) != "\5\0") {
echo bin2hex($response) . "\n";
throw new \Exception("Unsupported protocol or connection refused");
}

$ssh = new SSH2($fsock);
$ssh->login('username', 'password');
echo $ssh->exec('ls -latr');

Connecting to an IPv6 address

When specifying a numerical IPv6 address (e.g. fe80::1), you must enclose the IP in square brackets, for example tcp://[fe80::1]:22.

Binding to a Specific IP Address

use phpseclib4\Net\SSH2;

// http://php.net/manual/en/context.socket.php
$opts = [
'socket' => [
'bindto' => '127.255.255.255:0',
],
];
$context = stream_context_create($opts);
$socket = stream_socket_client('tcp://localhost:22', $errno, $errstr, ini_get('default_socket_timeout'), STREAM_CLIENT_CONNECT, $context);

$ssh = new SSH2($socket);
$ssh->login('username', 'password');
echo $ssh->exec('ls -latr');

Using a Custom Cipher Suite

You can tell phpseclib which algorithms you'd like to use by doing $ssh->setPreferredAlgorithms($methods). $methods should be an associative array with any or all of the following parameters (inspired by ssh2_connect):

IndexMeaningSupported Values
kexList of key exchange methods to advertise, comma separated in order of preference.curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group-exchange-sha256, diffie-hellman-group-exchange-sha1, diffie-hellman-group14-sha256, diffie-hellman-group14-sha1, diffie-hellman-group15-sha512, diffie-hellman-group16-sha512, diffie-hellman_group17-sha512, diffie-hellman-group18-sha512, diffie-hellman-group1-sha1. Pretty much anything returned by $ssh->getSupportedKEXAlgorithms()
hostkeyList of hostkey methods to advertise, comma separated in order of preference.ssh-ed25519, ecdsa-sha2-nistp256, ecdsa-sha2-nistp384, ecdsa-sha2-nistp521, rsa-sha2-256, rsa-sha2-512, ssh-rsa, ssh-dss. Pretty much anything returned by $ssh->getSupportedHostKeyAlgorithms()
client_to_serverAssociative array containing crypt, compression, and message authentication code (MAC) method preferences for messages sent from client to server.
server_to_clientAssociative array containing crypt, compression, and message authentication code (MAC) method preferences for messages sent from server to client.

client_to_server and server_to_client should be an associative array with any or all of the following parameters.

IndexMeaningSupported Values
cryptList of crypto methods to advertise, comma separated in order of preference.aes128-gcm@openssh.com, aes256-gcm@openssh.com, arcfour256, arcfour128, aes128-ctr, aes192-ctr, aes256-ctr, chacha20-poly1305@openssh.com, 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. Pretty much anything returned by $ssh->getSupportedEncryptionAlgorithms()
compList of compression methods to advertise, comma separated in order of preference.none, zlib@openssh.com, zlib. Pretty much anything returned by $ssh->getSupportedCompressionAlgorithms(). The latter two require that the zlib extension be installed (until such time that a shim can be written).
macList of MAC methods to advertise, comma separated in order of preference.hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-64-etm@openssh.com, umac-128-etm@openssh.com, hmac-sha1-etm@openssh.com, hmac-sha2-256, hmac-sha2-512, umac-64@openssh.com, umac-128@openssh.com, hmac-sha1-96, hmac-sha1, hmac-md5-96, hmac-md5. Pretty much anything returned by $ssh->getSupportedMACAlgorithms()

Note that a given algorithm will only be used if it's supported by both phpseclib and the server. The algorithms that the server supports can be determined by doing $ssh->getServerAlgorithms(). The algorithms that ultimately wind up being used can be determined by doing $ssh->getAlgorithmsNegotiated().

Using a custom cipher suite is not recommended. phpseclib's prioritization of algorithms is intended to maximize speed and security. For example, if OpenSSL is installed aes128-gcm@openssh.com will be the preferred encryption algorithm. If OpenSSL is not installed but libsodium is, then aes256-gcm@openssh.com will be preferred. If neither OpenSSL nor libsodium is installed the preferred encryption algorithm will be aes128-ctr. You can override this priority and force a slower algorithm to be used, but your connection will be slowed down because the pure-PHP implementations of those algorithms are not nearly as fast as OpenSSL / libsodium.

chacha20-poly1305@openssh.com is the latest hotness in the cryptographic community but it is not prioritized higher because (1) while OpenSSL supports ChaCha20, it doens't support Poly1305 and (2) libsodium doesn't use Poly1305 in the same way that SSH uses it. Despite that, chacha20-poly1305@openssh.com is still pretty fast but not as fast as some of the other available algorithms.

Additional Tweaks

A handful of servers misbehave in protocol corners. phpseclib provides toggles for working around them. Reach for these only when a connection fails against a specific buggy server. Don't apply them prophylactically.

$ssh->sendIdentificationStringFirst();   // send our SSH-2.0-... before reading the server's
$ssh->sendIdentificationStringLast(); // wait for the server's identification string first

$ssh->sendKEXINITFirst(); // send SSH_MSG_KEXINIT before the server does
$ssh->sendKEXINITLast(); // wait for the server to send it first

The SSH protocol allows either side to send their identification string and KEXINIT packet first, so neither order is wrong, but a buggy server may only accept one of the two. If you're seeing handshake failures and the protocol logs (see getLog()) show the connection dying before authentication, one of these toggles may help.