Using openssl and java for RSA keys

If you want to use public key encryption, you’ll need public and private keys in some format. OpenSSL and many other tools can generate such key pairs as well as java. However, if it comes to interoperability between these tools, you’ll need to be a bit careful. This post shows, how to generate a key pair with openssl, store it in files and load these key pairs in Java for usage.

Part 1: Generate a fresh key pair with openssl

You start with generating a private key using the genrsa tool from OpenSSL:

openssl genrsa -out privatekey.pem 2048

This creates a new RSA private key with 2048 bits length. The key is stored in the file privatekey.pem and it is in the “PEM” format. The PEM format is essentially a base64-encoded variant of a DER-encoded structure. You can look at the file, it should start with an “BEGIN RSA PRIVATE KEY” header and end with “END RSA PRIVATE KEY” footer:

head -2 privatekey.pem; tail -1 privatekey.pem 
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAth6P/MXUGL1y69Ao9THV16taSeUWnM4FQpmHP0yMDS3hB4V0
-----END RSA PRIVATE KEY-----

This file is now all we need to get started. Although this seems just to be the private key and the public key seems to be missing - it is not: This private key format contains all the information to reconstruct the public key data.

Part 2: Extract the public key

The second tool from OpenSSL we’ll use is rsa. This allows to do some conversions of the key files.

openssl rsa -in privatekey.pem -out publickey.pem -pubout

If we look at the generate file publickey.pem, we see, that is also in the PEM format. The header and footer lines are “BEGIN PUBLIC KEY” and “END PUBLIC KEY” respectively:

head -2 publickey.pem; tail -1 publickey.pem 
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAth6P/MXUGL1y69Ao9THV
-----END PUBLIC KEY-----

Now we have the plain key files available. You could distribute the public key file to allow the other party to encrypt some data while keeping the private key save. Please note, that the private key file is not encrypted and must be secured in some way (like file permissions, etc.).

Part 3: Understanding the key files structure

Java itself cannot directly load the PEM files generated in the above steps. However, the PEM files are actually just the “DER” format base 64 encoded with the additional headers and footers. But what is the “DER” format? The man page explains a bit:

-inform DER|NET|PEM
  This specifies the input format. The DER option uses an ASN1 DER encoded form
  compatible with the PKCS#1 RSAPrivateKey or SubjectPublicKeyInfo format.
  The PEM form is the default format: it consists of the DER format base64
  encoded with additional header and footer lines. On input PKCS#8 format
  private keys are also accepted. The NET form is a format is described
  in the NOTES section.

So there is the standard PKCS#1 which defines the structures RSAPrivateKey and SubjectPublicKeyInfo. The standard has been published also as RFC 3447. The recommendation for interchanging private keys is described in appendix A.1.2:

 RSAPrivateKey ::= SEQUENCE {
     version           Version,
     modulus           INTEGER,  -- n
     publicExponent    INTEGER,  -- e
     privateExponent   INTEGER,  -- d
     prime1            INTEGER,  -- p
     prime2            INTEGER,  -- q
     exponent1         INTEGER,  -- d mod (p-1)
     exponent2         INTEGER,  -- d mod (q-1)
     coefficient       INTEGER,  -- (inverse of q) mod p
     otherPrimeInfos   OtherPrimeInfos OPTIONAL
 }

You can see two things: The structure is basically an list of numbers. And the private key structure contains the modulus - that’s the reason why you can extract the public key from this private key file.

The public key structure SubjectPublicKeyInfo is described in appenx A.1.1:

 RSAPublicKey ::= SEQUENCE {
     modulus           INTEGER,  -- n
     publicExponent    INTEGER   -- e
 }

You can display this info in “human readable” format using OpenSSL, too:

openssl rsa -in privatekey.pem -text
Private-Key: (2048 bit)
modulus:
    00:b6:1e:8f:fc:c5:d4:18:bd:72:eb:d0:28:f5:31:
...
publicExponent: 65537 (0x10001)
privateExponent:
    00:a9:f4:cb:9a:b1:63:c5:d2:c6:b4:9a:86:1e:8c:
... It will display all the fields. The same is possible with the public key:

openssl rsa -in publickey.pem -text -pubin
Public-Key: (2048 bit)
Modulus:
    00:b6:1e:8f:fc:c5:d4:18:bd:72:eb:d0:28:f5:31:
...

Part 4: Converting the key files for usage in Java (Public Key)

Plain Java is able to understand the public key format. However, it can’t read the PEM file directly, but it can understand the DER encoding. The solution is, to decode the file first using Base64 and then let it parse by Java. Here’s a snippet that does this:

public static PublicKey loadPublicKey() throws Exception {
    String publicKeyPEM = FileUtils.readFileToString(new File("publickey.pem"), StandardCharsets.UTF_8);

    // strip of header, footer, newlines, whitespaces
    publicKeyPEM = publicKeyPEM
            .replace("-----BEGIN PUBLIC KEY-----", "")
            .replace("-----END PUBLIC KEY-----", "")
            .replaceAll("\\s", "");

    // decode to get the binary DER representation
    byte[] publicKeyDER = Base64.getDecoder().decode(publicKeyPEM);

    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    PublicKey publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(publicKeyDER));
    return publicKey;
}

It uses X509EncodedKeySpec to load the public key, which is just the recommended SubjectPublicKeyInfo implementation.

Note: I used Apache’s commons-io library for FileUtils. Everything else is include in the standard Java8 JDK.

Part 5: Convert the private key for for usage in Java

Unfortunately we can’t use the exact same trick for the private key. Java has an encoded key spec for the private key: PKCS8EncodedKeySpec - however, it implements “PKCS#8” rather than “PKCS#1” that we used. Luckily, OpenSSL contains also a converter for this format:

openssl pkcs8 -in privatekey.pem -topk8 -nocrypt -out privatekey-pkcs8.pem

If you inspect the generated file, you’ll see again the PEM format, but now with the header “BEGIN PRIVATE KEY”:

head -2 privatekey-pkcs8.pem; tail -1 privatekey-pkcs8.pem
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC2Ho/8xdQYvXLr
-----END PRIVATE KEY-----

Please note, that this private key file is also not encrypted (nocrypt) and must be kept safe.

This format is described in RFC 5208 and the structure in section 5:

 PrivateKeyInfo ::= SEQUENCE {
   version                   Version,
   privateKeyAlgorithm       PrivateKeyAlgorithmIdentifier,
   privateKey                PrivateKey,
   attributes           [0]  IMPLICIT Attributes OPTIONAL }

It is again DER encoded and it is actually just wrapping the RSAPrivateKey structure from above in the privateKey field. However, this format allows for encrypting the private key with a password (which we don’t use in this example).

Now we can load the private key in Java, too:

public static PrivateKey loadPrivateKey() throws Exception {
    String privateKeyPEM = FileUtils.readFileToString(new File("privatekey-pkcs8.pem"), StandardCharsets.UTF_8);

    // strip of header, footer, newlines, whitespaces
    privateKeyPEM = privateKeyPEM
            .replace("-----BEGIN PRIVATE KEY-----", "")
            .replace("-----END PRIVATE KEY-----", "")
            .replaceAll("\\s", "");

    // decode to get the binary DER representation
    byte[] privateKeyDER = Base64.getDecoder().decode(privateKeyPEM);

    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    PrivateKey privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyDER));
    return privateKey;
}

Part 6: Encrypting and Decrypting in Java using RSA

Now we can use Java to encrypt and decrypt like this:

public static void main(String[] args) throws Exception {
    Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
    String clearText = "Sample plain text";

    PublicKey publicKey = loadPublicKey();
    cipher.init(Cipher.ENCRYPT_MODE, publicKey);
    byte[] encrypted = cipher.doFinal(clearText.getBytes(StandardCharsets.UTF_8));

    PrivateKey privateKey = loadPrivateKey();
    cipher.init(Cipher.DECRYPT_MODE, privateKey);
    byte[] decrypted = cipher.doFinal(encrypted);

    System.out.println("ClearText: " + clearText);
    System.out.println("Decrypted: " + new String(decrypted, StandardCharsets.UTF_8));
    System.out.println("ClearText length: " + clearText.getBytes(StandardCharsets.UTF_8).length);
    System.out.println("Encrypted length: " + encrypted.length);
    System.out.println("Encrypted: " + Base64.getEncoder().encodeToString(encrypted));
}

The output looks like this:

ClearText: Sample plain text
Decrypted: Sample plain text
ClearText length: 17
Encrypted length: 256
Encrypted: riHHycTvKaDtX3SkeoZbFCW3KW3vxEIsF3wVQqOKuwAbTtWFyP6yN5essem+jTx16Ggdp6/rzS9r9Wy5O6P8JuOQAKi...

You can see that the clear text has been bloated up to 256 bytes. This is becaue I generated a RSA key with 2048 bits length, which are 256 bytes. The RSA cipher encrypts in blocks and uses padding in the blocks. I used “PKCS1Padding” which uses 11 bytes for padding, which means, you can at most encrypt 256-11=245 bytes of plain data in one block. If you have bigger data to encrypt, you’ll need to chain these blocks. There are different ways to chain blocks: Electronic Codebook (ECB), Cipher Block Chaining (CBC). See Block cipher mode of operation. You could also consider using a hybrid method, which means, you’ll exchange a symmetric key for AES via RSA first and then use this AES key for the bigger data you want to exchange.

See also: mbedtls - ASN.1 key structures in DER and PEM