Skip to content
ecdsa.com

Create a PKCS#12 bundle · .NET

How to create a PKCS#12 (.p12/.pfx) file from a key and certificate chain in C# (.NET)

To create a PKCS#12 (.p12/.pfx) file from a key and certificate chain in C#, load the certificate with its key from PEM (X509Certificate2.CreateFromPemFile), put it and the chain into an X509Certificate2Collection and call ExportPkcs12 with explicit protection (.NET 9+). X509CertificateLoader.LoadPkcs12CollectionFromFile reads it back. This recipe does both on .NET 10 and checks the file with OpenSSL.

Tested with .NET 10.0.13 (SDK 10.0.402) (Linux, X509CertificateLoader and ExportPkcs12) and OpenSSL 3.0.13 (reads the bundle back (inspect.sh)).

create.cs — certificate + key + chain into bundle.p12 with AES-256
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
string password = Environment.GetEnvironmentVariable("P12_PASSWORD")!;
// Certificate + its private key from PEM files in one call
using X509Certificate2 leaf = X509Certificate2.CreateFromPemFile("leaf.pem", "key.pem");
using X509Certificate2 intermediate = X509CertificateLoader.LoadCertificateFromFile("intermediate.pem");
var bundle = new X509Certificate2Collection { leaf, intermediate };
// .NET 9+: choose the protection explicitly; Export(X509ContentType.Pkcs12, ...) uses 3DES and SHA-1
byte[] pfx = bundle.ExportPkcs12(Pkcs12ExportPbeParameters.Pbes2Aes256Sha256, password);
File.WriteAllBytes("bundle.p12", pfx);
read.cs — load every certificate of the bundle, find the key
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
string password = Environment.GetEnvironmentVariable("P12_PASSWORD")!;
// Every certificate of the file; the one with a private key is the leaf
X509Certificate2Collection all = X509CertificateLoader.LoadPkcs12CollectionFromFile("bundle.p12", password);
foreach (X509Certificate2 cert in all)
{
Console.WriteLine($"{cert.Subject} | private key: {cert.HasPrivateKey}");
}
using ECDsa? key = all.First(c => c.HasPrivateKey).GetECDsaPrivateKey();
Console.WriteLine($"key: ECDSA {key!.KeySize}-bit");
try
{
X509CertificateLoader.LoadPkcs12FromFile("bundle.p12", "wrong-password");
}
catch (CryptographicException e)
{
Console.WriteLine($"wrong password: {e.Message}");
}

How it works

  1. X509Certificate2.CreateFromPemFile("leaf.pem", "key.pem") pairs the certificate with its private key; the intermediate is loaded on its own.
  2. collection.ExportPkcs12(Pkcs12ExportPbeParameters.Pbes2Aes256Sha256, password) writes AES-256-CBC with PBKDF2-HMAC-SHA256 and an HMAC-SHA-256 MAC (2000 iterations in the run).
  3. X509CertificateLoader.LoadPkcs12CollectionFromFile returns every certificate; the one with HasPrivateKey is the leaf, and GetECDsaPrivateKey() gives its key.
  4. LoadPkcs12FromFile (singular) returns only the certificate that has the key — CN=api.example.com with HasPrivateKey true in the run.

Gotchas

  • Export(X509ContentType.Pkcs12, password) — the classic method — protects the key with 3DES and SHA-1 (pbeWithSHA1And3-KeyTripleDES-CBC in the run). Use ExportPkcs12 with Pbes2Aes256Sha256 for new files.
  • Setting FriendlyName outside Windows throws PlatformNotSupportedException: The FriendlyName value cannot be set on Unix., so bundles made on Linux carry no friendly name (OpenSSL shows it empty).
  • A wrong password throws CryptographicException: The certificate data cannot be read with the provided password, the password may be incorrect.

Related recipes