Skip to content
ecdsa.com

PEM ⇄ DER · Rust

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

To convert a certificate or a key between PEM and DER in Rust, use the RustCrypto pem-rfc7468 crate: decode_vec returns the label and the DER bytes of a PEM document, and encode_string wraps DER in a labelled block. This recipe converts a certificate and a PKCS#8 key both ways and shows how strict an RFC 7468 decoder is about what it accepts.

Tested with pem-rfc7468 1.0.0 (Rust 1.99.1, sha2 0.11.1) and OpenSSL 3.5.7 (reads the DER output).

Cargo.toml — pem-rfc7468 (RustCrypto), sha2 and hex for the check
[dependencies]
pem-rfc7468 = { version = "=1.0.0", features = ["alloc"] }
sha2 = "=0.11.1"
hex = "=0.4.3"
src/convert.rs — PEM ⇄ DER with the label
use pem_rfc7468::LineEnding;
/// The label ("CERTIFICATE", "PRIVATE KEY", ...) and the DER bytes of a PEM document.
pub fn pem_to_der(pem: &str) -> Result<(String, Vec<u8>), pem_rfc7468::Error> {
let (label, der) = pem_rfc7468::decode_vec(pem.as_bytes())?;
Ok((label.to_owned(), der))
}
/// DER bytes as PEM: base64 in 64-character lines between BEGIN and END.
pub fn der_to_pem(label: &str, der: &[u8]) -> Result<String, pem_rfc7468::Error> {
pem_rfc7468::encode_string(label, LineEnding::LF, der)
}

How it works

  1. pem_rfc7468::decode_vec returns (label, der): CERTIFICATE and 563 bytes for the certificate, PRIVATE KEY and 138 bytes for the PKCS#8 key.
  2. encode_string(label, LineEnding::LF, der) writes 64-character lines; the round trip reproduces the original files exactly.
  3. Parsers take the DER: x509-parser's parse_x509_certificate, or the pkcs8 crate's DecodePrivateKey::from_pkcs8_der for keys.
  4. The run checks the DER with OpenSSL as well (openssl x509 -inform DER, openssl pkey -inform DER).

Gotchas

  • The decoder follows RFC 7468's strict form: a PEM re-wrapped at 76 characters (as base64 does) fails with Base64(InvalidEncoding). Normalise such files with openssl x509 first.
  • decode_vec takes exactly one document: a two-certificate bundle also fails with Base64(InvalidEncoding). Split bundles before decoding.
  • Legacy encrypted PEM (Proc-Type: 4,ENCRYPTED headers, as openssl ec -aes128 writes) is rejected with HeaderDisallowed; convert it to encrypted PKCS#8 (openssl pkcs8 -topk8).

Related recipes