Search code examples
javasshpublic-key-encryptionopenssh

How to generate ssh compatible id_rsa(.pub) from Java


I'm looking for a way to programmatically create ssh compatible id_rsa and id_rsa.pub files in Java.

I got as far as creating the KeyPair:

KeyPairGenerator generator;
generator = KeyPairGenerator.getInstance("RSA");
// or: generator = KeyPairGenerator.getInstance("DSA");
generator.initialize(2048);
keyPair = generator.genKeyPair();

I can't figure out however how to create the String representation of the PrivateKey and PublicKey in the KeyPair.


Solution

  • The key format used by ssh is defined in the RFC #4253. The format for RSA public key is the following :

      string    "ssh-rsa"
      mpint     e /* key public exponent */
      mpint     n /* key modulus */
    

    All data type encoding is defined in the section #5 of RFC #4251. string and mpint (multiple precision integer) types are encoded this way :

      4-bytes word: data length (unsigned big-endian 32 bits integer)
      n bytes     : binary representation of the data
    

    for instance, the encoding of the string "ssh-rsa" is:

      byte[] data = new byte[] {0, 0, 0, 7, 's', 's', 'h', '-', 'r', 's', 'a'};
    

    To encode the public :

       public byte[] encodePublicKey(RSAPublicKey key) throws IOException
       {
           ByteArrayOutputStream out = new ByteArrayOutputStream();
           /* encode the "ssh-rsa" string */
           byte[] sshrsa = new byte[] {0, 0, 0, 7, 's', 's', 'h', '-', 'r', 's', 'a'};
           out.write(sshrsa);
           /* Encode the public exponent */
           BigInteger e = key.getPublicExponent();
           byte[] data = e.toByteArray();
           encodeUInt32(data.length, out);
           out.write(data);
           /* Encode the modulus */
           BigInteger m = key.getModulus();
           data = m.toByteArray();
           encodeUInt32(data.length, out);
           out.write(data);
           return out.toByteArray();
       }
    
       public void encodeUInt32(int value, OutputStream out) throws IOException
       {
           byte[] tmp = new byte[4];
           tmp[0] = (byte)((value >>> 24) & 0xff);
           tmp[1] = (byte)((value >>> 16) & 0xff);
           tmp[2] = (byte)((value >>> 8) & 0xff);
           tmp[3] = (byte)(value & 0xff);
           out.write(tmp);
       }
    

    To have a string représentation of the key just encode the returned byte array in Base64.

    For the private key encoding there is two cases:

    1. the private key is not protected by a password. In that case the private key is encoded according to the PKCS#8 standard and then encoded with Base64. It is possible to get the PKCS8 encoding of the private key by calling getEncoded on RSAPrivateKey.
    2. the private key is protected by a password. In that case the key encoding is an OpenSSH dedicated format. I don't know if there is any documentation on this format (except the OpenSSH source code of course)