17

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.

divanov
  • 6,173
  • 3
  • 32
  • 51
Carsten
  • 4,204
  • 4
  • 32
  • 49
  • possible duplicate of [Given a Java ssh-rsa PublicKey, how can I build an SSH2 public key?](http://stackoverflow.com/questions/3588120/given-a-java-ssh-rsa-publickey-how-can-i-build-an-ssh2-public-key) – erickson Oct 17 '13 at 19:29

4 Answers4

28

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)
Duncan Jones
  • 67,400
  • 29
  • 193
  • 254
Jcs
  • 13,279
  • 5
  • 53
  • 70
  • 5
    Thanks a lot. I also found jsch (http://www.jcraft.com/jsch/), a free library which offers this functionality. – Carsten Sep 15 '10 at 22:58
  • 1
    it seems it would be simpler to use: MessageDigest.getInstance("MD5").digest(key.getEncoded()). – Pierre Sep 10 '12 at 02:01
  • 1
    is there a way to programmatically convert the [Jsch](http://www.jcraft.com/jsch/) generated ssh rsa-keys back to a format [Java Cipher](http://docs.oracle.com/javase/7/docs/api/javax/crypto/Cipher.html) can use for encryption? As far as I can tell [that requires openssh commands](http://stackoverflow.com/a/7473874/1020470). `openssl pkcs8 -topk8 -inform PEM -outform DER -in private_key_file -nocrypt > pkcs8_key` but what if you don't have access to to openssh? – Mark Mikofski Oct 13 '13 at 04:26
  • @MarkMikofski you should create a new question to give it a better visibility. – Jcs Oct 13 '13 at 07:32
7

gotoalberto's answer (quoted below) for a different question works for both RSA and DSA keys:

If you want reverse the process, i.e. encode a PublicKey Java object to a Linux authorized_keys entry format, one can use this code:

    /**
     * Encode PublicKey (DSA or RSA encoded) to authorized_keys like string
     *
     * @param publicKey DSA or RSA encoded
     * @param user username for output authorized_keys like string
     * @return authorized_keys like string
     * @throws IOException
     */
    public static String encodePublicKey(PublicKey publicKey, String user)
            throws IOException {
        String publicKeyEncoded;
        if(publicKey.getAlgorithm().equals("RSA")){
            RSAPublicKey rsaPublicKey = (RSAPublicKey) publicKey;
            ByteArrayOutputStream byteOs = new ByteArrayOutputStream();
            DataOutputStream dos = new DataOutputStream(byteOs);
            dos.writeInt("ssh-rsa".getBytes().length);
            dos.write("ssh-rsa".getBytes());
            dos.writeInt(rsaPublicKey.getPublicExponent().toByteArray().length);
            dos.write(rsaPublicKey.getPublicExponent().toByteArray());
            dos.writeInt(rsaPublicKey.getModulus().toByteArray().length);
            dos.write(rsaPublicKey.getModulus().toByteArray());
            publicKeyEncoded = new String(
                    Base64.encodeBase64(byteOs.toByteArray()));
            return "ssh-rsa " + publicKeyEncoded + " " + user;
        }
        else if(publicKey.getAlgorithm().equals("DSA")){
            DSAPublicKey dsaPublicKey = (DSAPublicKey) publicKey;
            DSAParams dsaParams = dsaPublicKey.getParams();

            ByteArrayOutputStream byteOs = new ByteArrayOutputStream();
            DataOutputStream dos = new DataOutputStream(byteOs);
            dos.writeInt("ssh-dss".getBytes().length);
            dos.write("ssh-dss".getBytes());
            dos.writeInt(dsaParams.getP().toByteArray().length);
            dos.write(dsaParams.getP().toByteArray());
            dos.writeInt(dsaParams.getQ().toByteArray().length);
            dos.write(dsaParams.getQ().toByteArray());
            dos.writeInt(dsaParams.getG().toByteArray().length);
            dos.write(dsaParams.getG().toByteArray());
            dos.writeInt(dsaPublicKey.getY().toByteArray().length);
            dos.write(dsaPublicKey.getY().toByteArray());
            publicKeyEncoded = new String(
                    Base64.encodeBase64(byteOs.toByteArray()));
            return "ssh-dss " + publicKeyEncoded + " " + user;
        }
        else{
            throw new IllegalArgumentException(
                    "Unknown public key encoding: " + publicKey.getAlgorithm());
        }
    }
Community
  • 1
  • 1
user314104
  • 1,528
  • 14
  • 31
  • Personally, I like @ymnk's answer more, even if gotoalberto's solution is more accessible. Thumbs up for one of the most useful Java libraries! – user314104 Apr 11 '14 at 14:23
4

As Carsten has mentioned, JSch can generate those keypair easily. Refer to its example, KeyGen.java

ymnk
  • 1,145
  • 7
  • 7
2

The generic solution for any PublicKey type (RSA, DSA, etc.) is a one-liner using SSHJ:

byte[] b = new Buffer.PlainBuffer().putPublicKey(key).getCompactData()

and then encode using Base64.getEncoder().encodeToString(b).

Raul Santelices
  • 1,030
  • 11
  • 17