Skip to content
ecdsa.com

PEM ⇄ DER · .NET

How to convert a certificate and a private key between PEM and DER in C# (.NET)

To convert a certificate between PEM and DER in C#, load it with X509CertificateLoader and use RawData for the DER and ExportCertificatePem() for the PEM. For any other PEM block — a private key, a public key — PemEncoding.Find locates the label and base64 payload and PemEncoding.Write builds the block. This recipe converts both ways on .NET 10 and checks the round trip.

Tested with .NET 10.0.13 (SDK 10.0.402) (System.Security.Cryptography, PemEncoding) and OpenSSL 3.0.13 (reads the DER output).

certificate.cs — certificate PEM ⇄ DER
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
// Certificate: RawData is the DER, ExportCertificatePem() the PEM (no trailing newline)
using X509Certificate2 cert = X509CertificateLoader.LoadCertificateFromFile("cert.pem"); // PEM or DER
File.WriteAllBytes("cert.der", cert.RawData);
using X509Certificate2 fromDer = X509CertificateLoader.LoadCertificate(File.ReadAllBytes("cert.der"));
File.WriteAllText("cert-again.pem", fromDer.ExportCertificatePem() + "\n");
Console.WriteLine($"cert DER: {cert.RawData.Length} bytes, sha256 {Convert.ToHexStringLower(SHA256.HashData(cert.RawData))}");
Console.WriteLine($"cert PEM again identical: {File.ReadAllText("cert-again.pem") == File.ReadAllText("cert.pem")}");
key.cs — any PEM block ⇄ DER with PemEncoding, checked by importing the key
using System.Security.Cryptography;
// Any PEM block: PemEncoding finds the label and the base64 payload
string pem = File.ReadAllText("key.pem");
PemFields fields = PemEncoding.Find(pem);
string label = pem[fields.Label]; // "PRIVATE KEY"
byte[] der = Convert.FromBase64String(pem[fields.Base64Data]); // PKCS#8 DER
File.WriteAllBytes("key.der", der);
// Back to PEM; importing proves the bytes are a valid PKCS#8 key
using ECDsa key = ECDsa.Create();
key.ImportPkcs8PrivateKey(File.ReadAllBytes("key.der"), out _);
string again = new string(PemEncoding.Write("PRIVATE KEY", key.ExportPkcs8PrivateKey())) + "\n";
Console.WriteLine($"{label} DER: {der.Length} bytes, sha256 {Convert.ToHexStringLower(SHA256.HashData(der))}");
Console.WriteLine($"key PEM again identical: {again == pem}");
Console.WriteLine($"ExportECPrivateKey (SEC1) instead: {key.ExportECPrivateKey().Length} bytes");

How it works

  1. X509CertificateLoader.LoadCertificateFromFile reads PEM or DER; RawData is the DER (563 bytes here) and ExportCertificatePem() the PEM without a trailing newline.
  2. PemEncoding.Find returns ranges: pem[fields.Label] is the label (PRIVATE KEY) and Convert.FromBase64String(pem[fields.Base64Data]) the DER — 138 bytes of PKCS#8.
  3. ImportPkcs8PrivateKey proves the bytes are a valid key; ExportPkcs8PrivateKey plus PemEncoding.Write("PRIVATE KEY", …) gives the PEM back, identical to the input.
  4. The run checks the certificate's DER with OpenSSL (openssl x509 -inform DER).

Gotchas

  • ExportCertificatePem() and PemEncoding.Write end without a newline; append \n when the file must be byte-identical to OpenSSL's output, as the run does.
  • ExportECPrivateKey() writes SEC1 (121 bytes here), ExportPkcs8PrivateKey() PKCS#8 (138 bytes) — same key, different structures; pick the one the consumer expects.

Related recipes