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.
Comments
andreas_dangel
Have you searched for an answer on https://stackoverflow.com/s… ?
What does your question has to do with FritzBox?
andreas_dangel
Hm, not sure why disqus is displaying threads on wrong pages... not what I would expect.
> Very strange thing is that if I'm trying to encrypt CipherData.enc, the result I
> get is not equal to CipherData.txt
Encrypting the same text again with the same password typically results in a different cryptext for security reason. This behavior depends on the padding, which pads up the plaintext to a certain length (so that the encryption algorithm can encrypt it) and the padding that is used is usually random. See https://en.wikipedia.org/wi....
> Unfortunately: bad magic number
You can start openssl with the verbose flag (`-v`). Maybe that sheds some light.
In https://unix.stackexchange.... they suggest, to specify the hash algorithm. However, using wrong hash algorithm result for me in a "bad decrypt" error message.
Leave a comment
Your email address will not be published. Required fields are marked *. All comments are held for moderation to avoid spam and abuse.