In a work project that heavily focused on asymmetric crypto, certificates, and digital signatures, we had to switch from PEM-formatted keys and certificates to DER-encoded data. Many of the examples I found on the internet always focused on reading PEM data with Bouncy Castle. I wanted to determine how much you can do without an additional library.
Spoiler: Not everything. But, let’s say, the stuff you likely care about.
A Story About OpenSSL & Formats
The starting point of this is a key pair, and you are likely to create one with OpenSSL. Its default output is PEM, so we start from there. You can also instruct OpenSSL to write DER when you generate the key by passing the command line argument -outform DER (or lowercase, it does not matter). This option is also used to convert from PEM to DER.
RSA
Let us start with RSA keys, which are still the most prevalent. Afterward, I will show you how to handle Elliptic Curve keys.
openssl genpkey -algorithm RSA -out genpkey_rsa_private_key.pem -pkeyopt rsa_keygen_bits:2048
You can also use the following command. However, according to a comment on StackExchange, genpkey is the recommended way to go.
openssl genrsa -out genrsa_private_key.pem 2048
Depending on your OpenSSL version, there may be differences though. I could not narrow down the exact version, so you must look at the generated PEM. I am using OpenSSL 3.2.1. If the PEM starts with -----BEGIN PRIVATE KEY-----, you are golden. If it is -----BEGIN RSA PRIVATE KEY-----, a conversion is necessary. That is because key information can be encoded in different ways. Java requires PKCS8, which is represented by the first one. From what I understood, the second one is PKCS1.
(Much data formats. Many confusing.)
We will return to this when we get to the Elliptic Curve keys. Assuming the RSA key is in the correct format, you can get a DER representation the following way.
openssl rsa -inform PEM -in genpkey_rsa_private_key.pem -outform DER -out genpkey_rsa_private_key.der
This command style works a surprising amount of times to convert from one representation to the next. Certificates are another example.
Finally, the following is how you get the public key from the private key. It works similarly with the DER-encoded private key as the source.
openssl rsa -pubout -inform PEM -in genpkey_rsa_private_key.pem -outform DER -out genpkey_rsa_public_key.der
Elliptic Curve
The approach is mostly the same, except for the one part I already hinted at. You cannot simply convert from PEM to DER, as that would not encode the key information in PKCS8. But let us take one step at a time. First, you need to create the keys.
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:prime256v1 -out genpkey_ec_private_key.pem
The following would be the alternative.
openssl ecparam -name prime256v1 -genkey -noout -out ecparam_private_key.pem
Lastly, the public key.
openssl ec -pubout -inform PEM -in genpkey_ec_private_key.pem -outform DER -out genpkey_ec_public_key.der
Now, we can get to the fun part.
(Actually, I lied. It’s not fun, but it is essential to understand.)
When you look at the PEM output of both private key variants, you will notice the different PEM headers I pointed out in the RSA section. It would be safe to assume that the genpkey private key is ready to go because it is already in PKCS8. Well, that would be too easy, now would it? Using the same conversion command as we did with RSA does not write PKCS8 when the source is an Elliptic Curve private key. Therefore, we must use something else to do the correct conversion. After researching this, I understand better why I had issues adding certificates to Azure Key Vault a while back. The reason and solution were precisely the same.
Instead of doing the -inform PEM -outform DER dance, the following statement is required for Elliptic Curve keys – or at least for EC keys using the P-256 curve.
openssl pkcs8 -topk8 -nocrypt -inform PEM -in genpkey_ec_private_key.pem -outform DER -out genpkey_ec_private_key.der
Note that you may have to use different commands if your private key is password protected. For simplicity, I stuck with unprotected private keys. You have to take shortcuts somewhere to keep things manageable 😉.
Bouncy Castles Are For Kids
Two Word pages in, we can finally get to the Java code. As I said in the introduction, I tried to do everything with only the APIs included in the JDK. There will be Bouncy code, but it is one particular case I did not need in my big project at work. Your mileage may vary.
The Easy One
The good thing about the Java Crypto API is that it can handle both types of keys with just one implementation. It requires a single toggle to inform it about the expected algorithm, but that’s it.
To keep things simple and short, let’s assume you already loaded the key into memory and have a byte array in your hands. This reduces the code to three simple lines.
var keyFactory = KeyFactory.getInstance("RSA or EC");
var pkcs8KeySpec = new PKCS8EncodedKeySpec(privateKeyByteArray);
var privateKey keyFactory.generatePrivate(pkcs8KeySpec);
The privateKey variable can either be cast to an instance of RSAPrivateKey or ECPrivateKey. Reading a public key is just as simple.
var keyFactory = KeyFactory.getInstance("RSA or EC");
var encodedKeySpec = new X509EncodedKeySpec(publicKeyByteArray);
var publicKey keyFactory.generatePublic(encodedKeySpec);
The publicKey variable can be cast to an instance of RSAPublicKey or ECPublicKey.
The Tricky One
If your use case includes deriving the public key from the private key, things can become complicated. The RSA case is just as easy as before, only requiring one additional line of code for the cast.
if (privateKey instanceof RSAPrivateCrtKey rsaKey) {
var rsaKeyFactory = KeyFactory.getInstance("RSA");
var rsaPublicKeySpec = new RSAPublicKeySpec(
rsaKey.getModulus(), rsaKey.getPublicExponent());
var publicKey = rsaKeyFactory.generatePublic(rsaPublicKeySpec);
}
Note that I had to go one level deeper into the PrivateKey hierarchy and use RSAPrivateCrtKey instead of RSAPrivateKey.
Achieving the same result for Elliptic Curve keys is more involved, and something I could not figure out with mere JDK API calls. In this case, I resorted to Bouncy Castle.
The first part of the code converts from the Java ECPrivateKey to a Bouncy Castle ECPrivateKey. You can get the public key and build a SubjectPublicKeyInfo object from that. It can output a PKCS8 byte array that Java’s X509EncodedKeySpec accepts. From there, the workflow is just like reading a byte array from a file.
Putting it all together, the code is as follows.
if (privateKey instanceof ECPrivateKey ecKey) {
var privateKeyInfo = PrivateKeyInfo.getInstance(ecKey.getEncoded());
var bcPrivateKey = org.bouncycastle.asn1.sec.ECPrivateKey.getInstance(
privateKeyInfo.parsePrivateKey());
var publicKeyBit = bcPrivateKey.getPublicKey();
var publicKeyInfo = new SubjectPublicKeyInfo(
privateKeyInfo.getPrivateKeyAlgorithm(), publicKeyBit);
var keyFactory = KeyFactory.getInstance(ecKey.getAlgorithm());
var keySpec = new X509EncodedKeySpec(publicKeyInfo.getEncoded());
var publicKey = keyFactory.generatePublic(keySpec);
}
You can find the fully functioning example source code on GitHub.
Famous Last Words
The “normal” use cases are easily covered by relying solely on Java’s built-in APIs. It is also much easier to read DER data than PEM in pure Java. Java’s crypto APIs do not provide a parser for the PEM format, and for that, you’d either have to do it yourself or utilize Bouncy Castle’s PEMParser. And then you might as well stay in the Bouncy world to avoid converting between JCE and Bouncy Castle objects.
Do not be alarmed, though. Creating a parser for PEM only involves stripping the header and footer and then Base64-decoding the remainder. I’ll leave this as an exercise for you to figure out😉.
I hope this was helpful or slightly insightful.
Thank you for reading.
AI Usage Disclosure
Since this is a controversial topic, I’d like to mention that a part of the blog’s image was generated by Microsoft’s copilot and edited by me to fit my logo style. I am not a skilled artist and do not make money off my blog to afford a real artist. However, I’d like something a little fancier than my Paint-skills allow, so I use the tools available.