Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Converting an OpenSSL generated RSA public key to OpenSSH format (PHP)

I've been trying for a while to generate an RSA keypair using PHP's openssl extension and save the result as an OpenSSH compatible keypair - meaning the private key is PEM encoded (which is easy) and the public key is stored in an OpenSSH specific format of the following form:

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABA...more base64 encoded stuff...

As far as I could gather this format consists of:

  • the key type in clear text, followed by space (i.e. "openssh-rsa ")
  • A base64 encoded string representing the following data:
    • the length of the algorithm name in bytes (in this case 7) encoded as 32 bit unsigned long big endian
    • the algorithm name, in this case 'ssh-rsa'
    • the length of the RSA 'e' number in bytes, encoded as 32 bit unsigned long big endian
    • the RSA 'e' number
    • the length of the RSA 'n' number in bytes, encoded as 32 bit unsigned long big endian
    • the RSA 'n' number

I've tried implementing this using PHP's pack() function, but no matter what I try the result is never equivalent to what I get from using the ssh-keygen -y -f command on the same RSA private key generated by openssl.

Here is a simplified version of my code:

<?php

// generate private key
$privKey = openssl_pkey_new(array(
    'private_key_bits' => 1024,
    'private_key_type' => OPENSSL_KEYTYPE_RSA
));

// convert public key to OpenSSH format
$keyInfo = openssl_pkey_get_details($privKey);
$data = pack("Na*", 7, 'ssh-rsa');
$data .= pack("Na*", strlen($keyInfo['rsa']['e']), $keyInfo['rsa']['e']);
$data .= pack("Na*", strlen($keyInfo['rsa']['n']), $keyInfo['rsa']['n']);

$pubKey = "ssh-rsa " . base64_encode($data);

echo "PHP generated RSA public key:\n$pubKey\n\n";

// For comparison, generate public key using ssh-keygen
openssl_pkey_export($privKey, $pem);
$umask = umask(0066); // this is needed for ssh-keygen to work properly
file_put_contents('/tmp/ssh-keygen-test', $pem);
umask($umask); 

exec('ssh-keygen -y -f /tmp/ssh-keygen-test', $out, $ret);
$otherPubKey = $out[0];
echo "ssh-keygen generated RSA public key:\n$otherPubKey\n\n";

echo ($pubKey == $otherPubKey ? "yes! they are the same\n" : "FAIL! they are different\n");

?>

Any tips on how I can do this without relying on ssh-keygen?

like image 286
shevron Avatar asked Apr 02 '11 15:04

shevron


People also ask

How do I change a public key to OpenSSH?

Convert the public key format from SSH2 to OpenSSH Try to find the original SSH2 public key that was provided from the user. Otherwise, use the sshldap command to output the SSH2 public key. You may need to manually insert line-breaks at the appropriate places. Save the SSH2 public key to a file (e.g. ssh2.

How do I convert SSH keys to different formats?

To convert a SSH client key to an OpenSSH format: Install the OpenSSH tool set, available under a BSD-style license: http://www.openssh.com/ The ssh-keygen utility is used to covert SSH keys between the different formats required by MessageWay or any other secure file transfer application.

What is OpenSSH public key format?

An SSH2 public key in OpenSSH format will start with "ssh-rsa". The idea behind all of this is that once you have keys on the remote server and your local host, access will be simpler since the server will only grant access to someone who has the matching private key.


2 Answers

Ok, I have just solved my own problem by taking a closer look at the C implementation references from Convert pem key to ssh-rsa format (which I did before but apparently I missed some important stuff). I needed to AND the first character of N and e with 0x80 and if it matches add another NULL character at the beginning of the number and increase the size by 1 respectively.

I am not sure why this is done (I didn't find any reference to this in on-line searching I did) but it works.

I've only done basic tests on this but it seems to work well, and here my code:

<?php

$privKey = openssl_pkey_get_private($rsaKey);
$pubKey = sshEncodePublicKey($privKey);

echo "PHP generated RSA public key:\n$pubKey\n\n";

function sshEncodePublicKey($privKey)
{
    $keyInfo = openssl_pkey_get_details($privKey);

    $buffer  = pack("N", 7) . "ssh-rsa" . 
               sshEncodeBuffer($keyInfo['rsa']['e']) . 
               sshEncodeBuffer($keyInfo['rsa']['n']);

    return "ssh-rsa " . base64_encode($buffer); 
}

function sshEncodeBuffer($buffer)
{
    $len = strlen($buffer);
    if (ord($buffer[0]) & 0x80) {
        $len++;
        $buffer = "\x00" . $buffer;
    }

    return pack("Na*", $len, $buffer);
}
?>
like image 188
shevron Avatar answered Sep 30 '22 10:09

shevron


Much simpler method, using phpseclib, a pure-PHP RSA implementation:

<?php
include('Crypt/RSA.php');

$rsa = new Crypt_RSA();
$rsa->loadKey('-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0
FPqri0cb2JZfXJ/DgYSF6vUpwmJG8wVQZKjeGcjDOL5UlsuusFncCzWBQ7RKNUSesmQRMSGkVb1/
3j+skZ6UtW+5u09lHNsj6tQ51s1SPrCBkedbNf0Tp0GbMJDyR4e9T04ZZwIDAQAB
-----END PUBLIC KEY-----');
$rsa->setPublicKey();

$publickey = $rsa->getPublicKey(CRYPT_RSA_PUBLIC_FORMAT_OPENSSH);
?>

Output:

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0FPqri0cb2JZfXJ/DgYSF6vUpwmJG8wVQZKjeGcjDOL5UlsuusFncCzWBQ7RKNUSesmQRMSGkVb1/3j+skZ6UtW+5u09lHNsj6tQ51s1SPrCBkedbNf0Tp0GbMJDyR4e9T04ZZw== phpseclib-generated-key

Source:

http://phpseclib.sourceforge.net/rsa/examples.html#public,openssh

like image 28
neubert Avatar answered Sep 30 '22 10:09

neubert