Feb
6
2013
PHP + SSH Authentication using a public key
<?php
$connection = ssh2_connect(‘shell.example.com’, 22, array(‘hostkey’=>’ssh-rsa’));
if (ssh2_auth_pubkey_file($connection, ’username’,
‘/home/username/.ssh/id_rsa.pub’,
‘/home/username/.ssh/id_rsa’, ’secret’)) {
echo ”Public Key Authentication Successful\n”;
} else {
die(‘Public Key Authentication Failed’);
}
?>
This is a known bug in php: password protected private key cannot be used on certain combinaisons.
See: https://bugs.php.net/bug.php?id=58573
ssh2_auth_pubkey_file() is broken when the public key file is protected with a password AND libssh2 is compiled with libgcrypt, which is what debian/ubuntu and probably others do. I’m working on a solution for this bug, but if you need this working rebuild libssh2 yourself with OpenSSL.
A workaround may be to store the private key unencrypted. To decrypt the key:
openssl rsa -in id_rsa -out id_rsaNOPASSWORD
and then use the file id_rsaNOPASSWORD without supplying the fifth parameter ‘passphrase’. It works, but you’ll have to be careful with your decrypted key file. Anyway, the level of security is not really terribly affected, because even with an encrypted key, you would still need to pass the passphrase unencrypted to the ssh2_auth_pubkey_file function …
Hope it helps.
===================
Establishing a Connection
Let’s begin by connecting to an SSH service. Establishing a connection is as simple as:
2 |
$conn = ssh2_connect('example.com', 22); |
3 |
ssh2_auth_password($conn, 'username', 'password'); |
Some administrators prefer using public and private keys to authenticate logins. If the service is configured and you want to connect in this way, you would use the following instead:
2 |
$conn = ssh2_connect('example.com', 22); |
6 |
'/home/username/.ssh/id_rsa.pub', |
7 |
'/home/username/.ssh/id_rsa' |
Whether you use username/password or public/private key authentication,ssh2_auth_password() and ssh2_auth_pubkey_file() both return a Boolean value indicating whether authentication was successful.
Performing Basic Commands
Once you have successfully authenticated with the server, you can perform your file transfer operations. The SCP functions let you send or receive a file(s) like so:
3 |
ssh2_scp_send($conn, '/local/filename', '/remote/filename', 0644); |
6 |
ssh2_scp_recv($conn, '/remote/filename', '/local/filename'); |
ssh2_scp_send() has an additional parameter which you can specify what the file permission should be on the remote server when the file is copied.
More functionality is available with the SFTP functions; you can change file or directory permissions, fetch information about a file, create directories, rename items, remove items, etc. They work quite similar to the SCP functions above, but an additional connect via ssh2_sftp()must be made prior to using the functions:
02 |
$sftp = ssh2_sftp($conn); |
04 |
// Create a new folder |
05 |
ssh2_sftp_mkdir($sftp, '/home/username/newdir'); |
08 |
ssh2_sftp_rename($sftp, '/home/username/newdir','/home/username/newnamedir'); |
10 |
// Remove the new folder |
11 |
ssh2_sftp_rmdir($sftp, '/home/username/newnamedir'); |
13 |
// Create a symbolic link |
14 |
ssh2_sftp_symlink($sftp, '/home/username/myfile', '/var/www/myfile'); |
17 |
ssh2_sftp_unlink($sftp, '/home/username/myfile'); |
ssh2_sftp() accepts the connection resource and returns an SFTP resource which is used in future ssh2_sftp_* calls. The calls then return a Boolean which allows you to determine whether the action was successful.
Using Wrapper Functions
When a specific file management function doesn’t exist for SFTP or SCP, generally the core file system function will work using a stream wrapper. Below are a few examples:
3 |
mkdir('ssh2.sftp://' . $sftp . '/home/username/newdir'); |
5 |
// Remove the new folder |
6 |
rmdir('ssh2.sftp://' . $sftp . '/home/username/newdir'); |
8 |
// Retrieve a list of files |
9 |
$files = scandir('ssh2.sftp://' . $sftp . '/home/username'); |
Before performing any of these calls, the connection to the SSH and SFTP server must be made as it uses the previously created $sftp variable.
Bring It All Together
Now that you are able to connect, authenticate, and run commands on an SSH server, we can create a few helper classes to simplify the process of executing these commands: one for performing SCP calls and one for SFTP calls, a parent class for common functionality, and a couple classes for encapsulating authentication information (password and keys).
Let’s create the authentication classes first since they will be used the other classes.
2 |
class SSH2Authentication |
02 |
class SSH2Password extends SSH2Authentication |
07 |
public function __construct($username, $password) { |
08 |
$this->username = $username; |
09 |
$this->password = $password; |
12 |
public function getUsername() { |
13 |
return $this->username; |
16 |
public function getPassword() { |
17 |
return $this->password; |
02 |
class SSH2Key extends SSH2Authentication |
06 |
protected $privateKey; |
08 |
public function __construct($username, $publicKey, $privateKey) { |
09 |
$this->username = $username; |
10 |
$this->password = $password; |
13 |
public function getUsername() { |
14 |
return $this->username; |
17 |
public function getPublicKey() { |
18 |
return $this->publicKey; |
21 |
public function getPrivateKey() { |
22 |
return $this->privateKey; |
SSH2Password and SSH2Key simply wrap their respective authentication information. They share a common base class so we can take advantage of PHP’s type hinting when we pass instances to their consumers.
Moving on, let’s create an SSH2 to connect and authenticate with the SSH server.
06 |
public function __construct($host, SSH2Authentication $auth, $port= 22) { |
07 |
$this->conn = ssh2_connect($host, $port); |
08 |
switch(get_class($auth)) { |
10 |
$username = $auth->getUsername(); |
11 |
$password = $auth->getPassword(); |
12 |
if (ssh2_auth_password($this->conn, $username,$password) === false) { |
13 |
throw new Exception('SSH2 login is invalid'); |
17 |
$username = $auth->getUsername(); |
18 |
$publicKey = $auth->getPublicKey(); |
19 |
$privateKey = $auth->getPrivateKey(); |
20 |
if (ssh2_auth_pubkey_file($this->conn, $username,$publicKey, $privateKey) === false) { |
21 |
throw new Exception('SSH2 login is invalid'); |
25 |
throw new Exception('Unknown SSH2 login type'); |
A very simple SCP class will be created that extends SSH2 and will make use of the magic method __call(). This allows us to do two important things: automatically prepend “ssh_scp_” to the function call and supply the connection variable to the call.
02 |
class SSH2SCP extends SSH2 |
04 |
public function __call($func, $args) { |
05 |
$func = 'ssh2_scp_' . $func; |
06 |
if (function_exists($func)) { |
07 |
array_unshift($args, $this->conn); |
08 |
return call_user_func_array($func, $args); |
12 |
$func . ' is not a valid SCP function'); |
The SFTP class is quite similar, although its constructor is overloaded to also execute thessh2_sftp() function. The results are stored in a protected variable and automatically prepended to all SFTP function calls.
02 |
class SSH2SFTP extends SSH2 { |
05 |
public function __construct($host, ISSH2Authentication $auth,$port = 22) { |
06 |
parent::__construct($host, $auth, $port); |
07 |
$this->sftp = ssh2_ftp($this->conn); |
09 |
public function __call($func, $args) { |
10 |
$func = 'ssh2_sftp_' . $func; |
11 |
if (function_exists($func)) { |
12 |
array_unshift($args, $this->sftp); |
13 |
return call_user_func_array($func, $args); |
17 |
$func . ' is not a valid SFTP function'); |
Once these classes are created they can be used to execute SCP and SFTP function calls. Thanks to the useful __call methods in both classes, we don’t need to pass the open connection or repeatedly type “ssh2_scp_” or “ssh2_ftp_” with each call.
02 |
// Create SCP connection using a username and password |
05 |
new SSH2Password('username', 'password') |
07 |
// Receive a file via SCP |
08 |
if ($scp->recv('remote/file', 'local/file')) { |
09 |
echo 'Successfully received file'; |
12 |
// Create SFTP connection using a public/private key |
15 |
new SSH2Key('username', 'public_key', 'private_key') |
17 |
// Create a directory via SFTP |
18 |
if ($sftp->mkdir('directory/name')) { |
19 |
echo 'Successfully created directory'; |
Summary
Installing the SSH2 PHP extension, it provides your scripts with the ability to connect to SSH2 servers. You can either leverage the handy classes that simplify the code for performing SFTP or SCP calls, or if a specific function isn’t provided by the library, most core file system operations can be used by leveraging the SSH2 wrapper functionality.
Did you enjoy this article? Share it!
I prefer it when things work out of the box without any work-arounds. Like how it works with phpseclib, a pure PHP SFTP implementation:
http://phpseclib.sourceforge.net/
There are a number of other reasons to use it over what you’re using:
http://stackoverflow.com/a/14305875/569976
That is a very good idea.. Thank you