Skip to content
ecdsa.com

PEM ⇄ DER · Python

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

To convert a certificate and a private key between PEM and DER in Python, load them with the cryptography package and write them back with Encoding.DER or Encoding.PEM: public_bytes for certificates, private_bytes with PrivateFormat.PKCS8 for keys. This recipe converts both ways and checks that the round trip reproduces the original PEM byte for byte.

Tested with cryptography 50.0.2 (Python 3.14.8) and OpenSSL 3.5.7 (reads the DER output).

convert.py — certificate and private key, PEM ⇄ DER
from cryptography import x509
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.serialization import Encoding, NoEncryption, PrivateFormat
def cert_pem_to_der(pem: bytes) -> bytes:
return x509.load_pem_x509_certificate(pem).public_bytes(Encoding.DER)
def cert_der_to_pem(der: bytes) -> bytes:
return x509.load_der_x509_certificate(der).public_bytes(Encoding.PEM)
def key_pem_to_der(pem: bytes, password: bytes | None = None) -> bytes:
key = serialization.load_pem_private_key(pem, password=password)
return key.private_bytes(Encoding.DER, PrivateFormat.PKCS8, NoEncryption())
def key_der_to_pem(der: bytes, password: bytes | None = None) -> bytes:
key = serialization.load_der_private_key(der, password=password)
return key.private_bytes(Encoding.PEM, PrivateFormat.PKCS8, NoEncryption())

How it works

  1. x509.load_pem_x509_certificate(pem).public_bytes(Encoding.DER) returns the certificate's DER (563 bytes here); load_der_x509_certificate(der).public_bytes(Encoding.PEM) goes back, identical to the original file.
  2. For keys, load_pem_private_key / load_der_private_key read any supported key; private_bytes(Encoding.DER, PrivateFormat.PKCS8, NoEncryption()) writes PKCS#8 DER (138 bytes for this P-256 key).
  3. Pass password= to read an encrypted key, and BestAvailableEncryption(b"…") instead of NoEncryption() to write one.
  4. The run checks the DER with OpenSSL (openssl x509 -inform DER) as well, so the bytes are what other tools expect.

Gotchas

  • PrivateFormat.TraditionalOpenSSL writes an EC key as SEC1 (121 bytes, BEGIN EC PRIVATE KEY in PEM), not PKCS#8 — a different byte string that many importers reject. Ask for PrivateFormat.PKCS8 explicitly.
  • DER carries no label, so the loader must match the content: load_der_private_key on a certificate's DER raises ValueError: Could not deserialize key data….
  • DER holds one object per file; for a PEM bundle, x509.load_pem_x509_certificates (plural) returns every certificate (2 in the run), and each gets its own DER file.

Related recipes