Skip to content
ecdsa.com

PEM ⇄ DER · Java

How to convert a certificate and a private key between PEM and DER in Java

To convert a certificate or a key between PEM and DER in Java, strip the BEGIN/END lines and decode the base64 with Base64.getMimeDecoder(); to go back, encode with a 64-character MIME encoder and add the lines again. The JCA parsers then take the DER directly. This recipe converts both ways with the JDK only and checks the round trip byte for byte.

Tested with OpenJDK 25.0.4.1 (Eclipse Temurin 25.0.4.1+1, java.util.Base64) and OpenSSL 3.0.13 (reads the DER output).

Pem.java — PEM ⇄ DER for any labelled block
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public final class Pem {
private Pem() {}
/** The DER bytes inside the PEM block with the given label ("CERTIFICATE", "PRIVATE KEY", ...). */
public static byte[] toDer(String pem, String label) {
String begin = "-----BEGIN " + label + "-----";
String end = "-----END " + label + "-----";
int from = pem.indexOf(begin);
int to = pem.indexOf(end, from + 1);
if (from < 0 || to < 0) throw new IllegalArgumentException("no " + label + " block");
return Base64.getMimeDecoder().decode(pem.substring(from + begin.length(), to)); // skips line breaks
}
/** DER bytes as a PEM block: base64 in 64-character lines between BEGIN and END. */
public static String fromDer(byte[] der, String label) {
String body = Base64.getMimeEncoder(64, "\n".getBytes(StandardCharsets.US_ASCII)).encodeToString(der);
return "-----BEGIN " + label + "-----\n" + body + "\n-----END " + label + "-----\n";
}
}

How it works

  1. toDer finds the block with the expected label and decodes its body with Base64.getMimeDecoder(), which skips the line breaks: 563 bytes for the certificate, 138 for the PKCS#8 key.
  2. fromDer encodes with Base64.getMimeEncoder(64, "\n") — PEM's 64-character lines with LF endings — so the round trip reproduces the original files exactly.
  3. The DER goes straight into the JCA: CertificateFactory.generateCertificate (whose getEncoded() returns the same bytes) and KeyFactory.generatePrivate(new PKCS8EncodedKeySpec(der)).
  4. The run checks the certificate's DER with OpenSSL (openssl x509 -inform DER) too.

Gotchas

  • Base64.getDecoder() rejects the line breaks inside PEM: Illegal base64 character a (0x0a, the newline). Use getMimeDecoder().
  • getMimeEncoder() without arguments writes 76-character lines with CRLF (checked in the run) — valid MIME, but not PEM's 64 characters, and not byte-identical to OpenSSL's output.
  • PKCS8EncodedKeySpec needs PKCS#8 (BEGIN PRIVATE KEY); the same key as SEC1 DER (BEGIN EC PRIVATE KEY) fails with InvalidKeySpecException. Convert it once with openssl pkcs8 -topk8 -nocrypt.

Related recipes