Status: Draft / Work in progress This reference is currently under development. Examples and recommendations may be updated as additional validation and testing are completed.
A practical, single-page reference for X.509 certificate formats (PEM, DER, PKCS#7, PKCS#12, PKCS#8, JKS) and how to convert between them.
| From ↓ \ To → | PEM | DER | P7B | P12/PFX | JKS |
|---|---|---|---|---|---|
| PEM | — | → | → | → | via P12 |
| DER | → | — | via PEM | via PEM | via P12 |
| P7B | → | via PEM | — | via PEM | via P12 |
| P12/PFX | → | via PEM | via PEM | — | → |
| JKS | via P12 | via P12 | via P12 | → | — |
Note: PKCS#12 ↔ JKS conversion currently has no tool example populated below — that section is a placeholder pending a reliable, verified method.
| Format | Encoding | Contains | Typical extension |
|---|---|---|---|
| PEM | Base64 (ASCII, -----BEGIN...-----) |
cert, key, chain, or all combined | .pem .crt .cer .key |
| DER | Raw binary ASN.1 | certificate, key, CSR, PKCS#7, etc. | .der .cer |
| PKCS#7 | Base64 or binary | cert chain only, no private key | .p7b .p7c |
| PKCS#12 | Binary, password-protected | cert + private key + chain, bundled | .p12 .pfx |
| PKCS#8 | Base64 or binary | private key, standard format | .key .pem |
| JKS | Proprietary binary, password-protected | Java-specific keystore (certs + keys) | .jks |
PEM/DER are encodings (how bytes are represented). PKCS#7/PKCS#12/JKS are containers (what's bundled together). A .crt file could be PEM or DER inside - the extension doesn't guarantee it.
OpenSSL
openssl x509 -in cert.pem -outform der -out cert.derWindows certutil
certutil -decode cert.pem cert.der
certutil -decodeBase64-decodes a PEM/Base64-encoded certificate to its binary DER representation. PEM headers are ignored during decoding.
Python (cryptography)
from cryptography import x509
from cryptography.hazmat.primitives import serialization
with open("cert.pem", "rb") as f:
cert = x509.load_pem_x509_certificate(f.read())
with open("cert.der", "wb") as f:
f.write(cert.public_bytes(serialization.Encoding.DER))OpenSSL
openssl x509 -in cert.der -inform der -out cert.pem -outform pemWindows certutil
certutil -encode cert.der cert.pem
certutil -encodeBase64-encodes the DER certificate and wraps it in standard-----BEGIN CERTIFICATE-----/-----END CERTIFICATE-----PEM headers.
Python (cryptography)
from cryptography import x509
from cryptography.hazmat.primitives import serialization
with open("cert.der", "rb") as f:
cert = x509.load_der_x509_certificate(f.read())
with open("cert.pem", "wb") as f:
f.write(cert.public_bytes(serialization.Encoding.PEM))PKCS#7 holds certificates/chains only - no private key.
OpenSSL - single certificate
openssl crl2pkcs7 -nocrl -certfile cert.pem -out cert.p7bOpenSSL - full chain
openssl crl2pkcs7 -nocrl -certfile cert.pem -certfile intermediate.pem -certfile root.pem -out chain.p7bBy default, OpenSSL outputs a PEM-encoded PKCS#7. Add
-outform DERto generate a binary PKCS#7 (.p7b), which is commonly used on Windows.
Windows certutil
Not directly supported by a single
certutilcommand. In practice, this is done via the Certificates MMC snap-in (certmgr.msc) → Export → Cryptographic Message Syntax Standard – PKCS #7, or scripted via PowerShell'sExport-Certificate/CMS cmdlets rather thancertutil.
Python (cryptography, v37+)
from cryptography import x509
from cryptography.hazmat.primitives.serialization import pkcs7, Encoding
certs = []
for filename in ("cert.pem", "intermediate.pem"):
with open(filename, "rb") as f:
certs.append(x509.load_pem_x509_certificate(f.read()))
with open("chain.p7b", "wb") as f:
f.write(pkcs7.serialize_certificates(certs, Encoding.DER))OpenSSL
openssl pkcs7 -in cert.p7b -print_certs -out certs.pemExtracts all certificates from the PKCS#7 container as individual
-----BEGIN CERTIFICATE-----PEM blocks.
Windows certutil
There is no direct
certutilequivalent that extracts all certificates as individual PEM files.
certutil -dump cert.p7b
certutil -dumpdisplays the certificate(s) contained in the PKCS#7 container for inspection, but it does not export them as PEM certificates. Use OpenSSL when you need PEM-encoded certificates.
Python (cryptography, v3.1+)
from cryptography.hazmat.primitives.serialization import pkcs7, Encoding
with open("cert.p7b", "rb") as f:
certs = pkcs7.load_der_pkcs7_certificates(f.read())
with open("certs.pem", "wb") as f:
for cert in certs:
f.write(cert.public_bytes(Encoding.PEM))OpenSSL - certificate + private key
openssl pkcs12 -export -in cert.pem -inkey key.pem -out cert.p12 -name "my-cert"OpenSSL — certificate + private key + chain
openssl pkcs12 -export -in cert.pem -inkey key.pem -certfile chain.pem -out cert.p12 -name "my-cert"
chain.pemtypically contains one or more intermediate CA certificates (and optionally the root CA). The private key specified by-inkeymust match the certificate.
Windows certutil
Not directly supported for building a PKCS#12 from a standalone PEM certificate and private key.
certutil -mergePFXonly combines existing PKCS#12 (PFX) files. The typical Windows-native approach is to import the certificate and private key into the certificate store, then use PowerShell'sExport-PfxCertificate. For direct PEM certificate + key → PKCS#12 conversion, OpenSSL is the standard tool, including on Windows.
Python (cryptography, v3.1+)
from cryptography import x509
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.serialization import pkcs12, NoEncryption
cert = x509.load_pem_x509_certificate(open("cert.pem", "rb").read())
key = serialization.load_pem_private_key(open("key.pem", "rb").read(), password=None)
data = pkcs12.serialize_key_and_certificates(
name=b"my-cert",
key=key,
cert=cert,
cas=None,
encryption_algorithm=NoEncryption(),
)
open("cert.p12", "wb").write(data)OpenSSL - everything (cert + key + chain) into one file
openssl pkcs12 -in cert.p12 -out bundle.pem -nodesExports the private key, leaf certificate, and CA certificates (if present) into a single PEM file. Remove -nodes (or use -noenc in newer OpenSSL versions) to keep the private key encrypted.
OpenSSL - certificate only
openssl pkcs12 -in cert.p12 -clcerts -nokeys -out cert.pemExtracts the leaf certificate and excludes the private key and CA certificates.
OpenSSL - private key only
openssl pkcs12 -in cert.p12 -nocerts -nodes -out key.pemExtracts the private key. Remove -nodes (or use -noenc in newer OpenSSL versions) to keep the exported private key encrypted.
OpenSSL - CA chain only (excludes leaf certificate)
openssl pkcs12 -in cert.p12 -cacerts -nokeys -out chain.pemExtracts only CA certificates stored in the PKCS#12 container. The leaf certificate is excluded.
OpenSSL 3.x - legacy PKCS#12 files
openssl pkcs12 -legacy -in cert.p12 -cacerts -nokeys -out chain.pemOpenSSL 3.x disables some legacy PKCS#12 algorithms by default. If the .p12/.pfx file was created by older tools (for example, older Windows certificate exports) and uses algorithms such as RC2 or 3DES, OpenSSL may fail with an algorithm error. Add -legacy to enable support for legacy PKCS#12 algorithms.
Windows certutil
certutil -exportPFX -p "yourpassword" My "CertCommonNameOrSerial" cert.p12certutil -exportPFX exports a certificate and private key already stored in the Windows certificate store (for example, the My store). It does not decode or convert an existing standalone .p12/.pfx file into PEM. For standalone PKCS#12 → PEM conversion, use OpenSSL.
Python (cryptography, v2.5+)
from cryptography.hazmat.primitives.serialization import (
pkcs12,
Encoding,
PrivateFormat,
NoEncryption,
)
private_key, cert, additional_certs = pkcs12.load_key_and_certificates(
open("cert.p12", "rb").read(),
password=b"yourpassword",
)
with open("bundle.pem", "wb") as f:
f.write(private_key.private_bytes(Encoding.PEM, PrivateFormat.PKCS8, NoEncryption()))
f.write(cert.public_bytes(Encoding.PEM))PKCS#1 (BEGIN RSA PRIVATE KEY) is RSA-only, legacy.
PKCS#8 (-----BEGIN PRIVATE KEY----- or -----BEGIN ENCRYPTED PRIVATE KEY-----) is a standard algorithm-independent private key format that supports RSA, EC, Ed25519, and other algorithms. Modern tools and libraries commonly prefer PKCS#8.
OpenSSL - PKCS#1 → PKCS#8, unencrypted
openssl pkcs8 -topk8 -nocrypt -in rsa_pkcs1.pem -out pkcs8.pemOpenSSL - PKCS#1 → PKCS#8, encrypted output
openssl pkcs8 -topk8 -in rsa_pkcs1.pem -out pkcs8_encrypted.pemCreates an encrypted PKCS#8 private key (-----BEGIN ENCRYPTED PRIVATE KEY-----) protected with a passphrase.
OpenSSL - PKCS#8 → PKCS#1 (RSA only)
openssl rsa -in pkcs8.pem -out pkcs1.pemConverts an RSA private key from PKCS#8 to traditional PKCS#1 format (-----BEGIN RSA PRIVATE KEY-----). Not applicable to EC, Ed25519, or other non-RSA keys.
Windows certutil
Not applicable — Windows CryptoAPI/CNG manages private keys independently of PEM container formats and does not provide a direct PKCS#1 ↔ PKCS#8 conversion workflow. This distinction is mainly relevant in OpenSSL and PEM-based environments.
Python (cryptography)
from cryptography.hazmat.primitives.serialization import (
load_pem_private_key,
Encoding,
PrivateFormat,
NoEncryption,
)
with open("rsa_pkcs1.pem", "rb") as f:
key = load_pem_private_key(f.read(), password=None)
with open("pkcs8.pem", "wb") as f:
f.write(
key.private_bytes(
Encoding.PEM,
PrivateFormat.PKCS8,
NoEncryption(),
)
)OpenSSL
Not applicable - OpenSSL has no JKS support; JKS is a Java-specific keystore format.
Windows certutil
Not applicable - JKS isn't used by Windows CryptoAPI/CNG; it's exclusively a Java Keystore format.
Python
Not applicable - JKS is a Java-specific keystore format and is not supported by the cryptography library.
Order matters: leaf → intermediate(s) → root, in that sequence, in one file.
cat leaf.pem intermediate.pem root.pem > fullchain.pemFor servers (nginx/Apache), the root is usually omitted - clients already trust it via their OS/browser trust store:
cat leaf.pem intermediate.pem > fullchain.pemFor a P12 that includes the chain:
openssl pkcs12 -export -in leaf.pem -inkey key.pem -certfile intermediate.pem -out bundle.p12