Szenario 12.2: PFX/PKCS#12 Export

Kategorie: Import/Export
Komplexität: ⭐⭐⭐ (Mittel)
Voraussetzungen: Zertifikat mit Private Key
Geschätzte Zeit: 15-20 Minuten


Beschreibung

Dieses Szenario beschreibt den Export und Import im PFX/PKCS#12-Format. PFX (Personal Information Exchange) ist das Standardformat für Windows und .NET, um Zertifikate zusammen mit Private Keys und optional der Zertifikatskette in einer passwortgeschützten Datei zu speichern.

PFX/PKCS#12 Eigenschaften:


Workflow

flowchart LR subgraph Input CERT[Zertifikat] KEY[Private Key] CHAIN[Chain] end subgraph PFX["PFX-Datei"] BAG1[Cert Bag] BAG2[Key Bag] BAG3[CA Bags] end CERT --> BAG1 KEY --> BAG2 CHAIN --> BAG3 PFX --> ENC[Verschlüsselt]


Code-Beispiel: PFX erstellen

using WvdS.Security.Cryptography.X509Certificates.Extensions.PQ;
using System.Security.Cryptography.X509Certificates;
 
using var ctx = PqCryptoContext.Initialize();
 
// Zertifikat und Key laden
var cert = ctx.LoadCertificate("server.crt.pem");
var privateKey = ctx.LoadPrivateKey("server.key.pem", "KeyPassword!");
 
// Zertifikat mit Key kombinieren
var certWithKey = ctx.CombineCertificateAndKey(cert, privateKey);
 
// PFX exportieren
byte[] pfxBytes = certWithKey.Export(X509ContentType.Pfx, "PfxPassword123!");
File.WriteAllBytes("server.pfx", pfxBytes);
 
Console.WriteLine("PFX erstellt: server.pfx");

Code-Beispiel: PFX mit Chain

public class PfxExporter
{
    public byte[] ExportWithChain(
        X509Certificate2 certificate,
        X509Certificate2Collection chain,
        string password,
        PfxExportOptions options = null)
    {
        options ??= PfxExportOptions.Default;
        using var ctx = PqCryptoContext.Initialize();
 
        // Collection für Export erstellen
        var exportCollection = new X509Certificate2Collection();
        exportCollection.Add(certificate);
 
        // Chain hinzufügen (ohne Root, wenn gewünscht)
        foreach (var caCert in chain)
        {
            if (options.IncludeRoot || !IsSelfSigned(caCert))
            {
                exportCollection.Add(caCert);
            }
        }
 
        // PFX exportieren
        var pfxBytes = exportCollection.Export(X509ContentType.Pfx, password);
 
        Console.WriteLine($"PFX erstellt mit {exportCollection.Count} Zertifikaten");
        return pfxBytes;
    }
 
    public void ExportToFile(
        X509Certificate2 certificate,
        X509Certificate2Collection chain,
        string outputPath,
        string password)
    {
        var pfxBytes = ExportWithChain(certificate, chain, password);
        File.WriteAllBytes(outputPath, pfxBytes);
 
        // Berechtigungen setzen (nur Eigentümer)
        if (OperatingSystem.IsWindows())
        {
            var fileInfo = new FileInfo(outputPath);
            var security = fileInfo.GetAccessControl();
            security.SetAccessRuleProtection(true, false);
            fileInfo.SetAccessControl(security);
        }
    }
 
    private bool IsSelfSigned(X509Certificate2 cert)
    {
        return cert.Subject == cert.Issuer;
    }
}
 
public class PfxExportOptions
{
    public bool IncludeRoot { get; set; } = false;
    public bool IncludeChain { get; set; } = true;
 
    public static PfxExportOptions Default => new PfxExportOptions();
}

Code-Beispiel: PFX importieren

using var ctx = PqCryptoContext.Initialize();
 
// PFX laden
var pfxBytes = File.ReadAllBytes("server.pfx");
var cert = new X509Certificate2(
    pfxBytes,
    "PfxPassword123!",
    X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet
);
 
Console.WriteLine($"Subject: {cert.Subject}");
Console.WriteLine($"Hat Private Key: {cert.HasPrivateKey}");
 
// Private Key extrahieren
if (cert.HasPrivateKey)
{
    var privateKey = cert.GetRSAPrivateKey()
        ?? cert.GetECDsaPrivateKey()
        ?? (AsymmetricAlgorithm)ctx.GetPqPrivateKey(cert);
 
    Console.WriteLine($"Key-Typ: {privateKey.GetType().Name}");
}
 
// Chain aus PFX extrahieren
var collection = new X509Certificate2Collection();
collection.Import(pfxBytes, "PfxPassword123!", X509KeyStorageFlags.DefaultKeySet);
 
Console.WriteLine($"Zertifikate in PFX: {collection.Count}");
foreach (var c in collection)
{
    Console.WriteLine($"  - {c.Subject}");
}

Code-Beispiel: Secure PFX mit AES-256

public class SecurePfxExporter
{
    public byte[] ExportSecure(
        X509Certificate2 certificate,
        string password)
    {
        // .NET 5+ unterstützt moderne PFX-Verschlüsselung
        var exportParams = new PbeParameters(
            PbeEncryptionAlgorithm.Aes256Cbc,
            HashAlgorithmName.SHA256,
            iterations: 100000
        );
 
        return certificate.Export(X509ContentType.Pfx, password);
    }
 
    public byte[] ExportWithOpenSsl(
        X509Certificate2 certificate,
        AsymmetricAlgorithm privateKey,
        X509Certificate2Collection chain,
        string password)
    {
        using var ctx = PqCryptoContext.Initialize();
 
        // OpenSSL-basierter Export für maximale Kompatibilität
        var pfxBuilder = new Pkcs12Builder();
 
        // Key Bag hinzufügen
        var keyBag = new Pkcs12KeyBag(
            privateKey.ExportPkcs8PrivateKey(),
            skipCopy: false
        );
 
        // Cert Bag hinzufügen
        var certBag = new Pkcs12CertBag(certificate);
 
        // Safe Contents erstellen
        var safeContents = new Pkcs12SafeContents();
        safeContents.AddBag(keyBag);
        safeContents.AddBag(certBag);
 
        foreach (var caCert in chain)
        {
            safeContents.AddBag(new Pkcs12CertBag(caCert));
        }
 
        // Mit Passwort verschlüsseln
        pfxBuilder.AddSafeContentsEncrypted(
            safeContents,
            password,
            new PbeParameters(
                PbeEncryptionAlgorithm.Aes256Cbc,
                HashAlgorithmName.SHA256,
                100000
            )
        );
 
        pfxBuilder.SealWithMac(password, HashAlgorithmName.SHA256, 100000);
 
        return pfxBuilder.Encode();
    }
}

Code-Beispiel: PFX für Windows Certificate Store

public class WindowsCertificateInstaller
{
    public void InstallPfxToStore(
        string pfxPath,
        string password,
        StoreName storeName = StoreName.My,
        StoreLocation location = StoreLocation.LocalMachine)
    {
        var pfxBytes = File.ReadAllBytes(pfxPath);
 
        var cert = new X509Certificate2(
            pfxBytes,
            password,
            X509KeyStorageFlags.MachineKeySet |
            X509KeyStorageFlags.PersistKeySet |
            X509KeyStorageFlags.Exportable
        );
 
        using var store = new X509Store(storeName, location);
        store.Open(OpenFlags.ReadWrite);
 
        // Prüfen ob bereits vorhanden
        var existing = store.Certificates.Find(
            X509FindType.FindByThumbprint,
            cert.Thumbprint,
            validOnly: false
        );
 
        if (existing.Count > 0)
        {
            Console.WriteLine("Zertifikat bereits im Store vorhanden");
            return;
        }
 
        store.Add(cert);
        store.Close();
 
        Console.WriteLine($"Zertifikat installiert: {cert.Subject}");
        Console.WriteLine($"Store: {storeName} ({location})");
        Console.WriteLine($"Thumbprint: {cert.Thumbprint}");
    }
 
    public void ExportFromStore(
        string thumbprint,
        string outputPath,
        string password,
        StoreName storeName = StoreName.My,
        StoreLocation location = StoreLocation.LocalMachine)
    {
        using var store = new X509Store(storeName, location);
        store.Open(OpenFlags.ReadOnly);
 
        var certs = store.Certificates.Find(
            X509FindType.FindByThumbprint,
            thumbprint,
            validOnly: false
        );
 
        if (certs.Count == 0)
        {
            throw new Exception($"Zertifikat nicht gefunden: {thumbprint}");
        }
 
        var cert = certs[0];
 
        if (!cert.HasPrivateKey)
        {
            throw new Exception("Zertifikat hat keinen Private Key");
        }
 
        var pfxBytes = cert.Export(X509ContentType.Pfx, password);
        File.WriteAllBytes(outputPath, pfxBytes);
 
        Console.WriteLine($"PFX exportiert: {outputPath}");
    }
}

PFX mit OpenSSL erstellen

# PFX aus PEM-Dateien erstellen
openssl pkcs12 -export \
    -out server.pfx \
    -inkey server.key \
    -in server.crt \
    -certfile chain.pem \
    -passout pass:MyPassword
 
# PFX mit modernem Algorithmus (AES-256)
openssl pkcs12 -export \
    -out server.pfx \
    -inkey server.key \
    -in server.crt \
    -certfile chain.pem \
    -aes256 \
    -passout pass:MyPassword
 
# PFX inspizieren
openssl pkcs12 -info -in server.pfx -passin pass:MyPassword
 
# PFX entpacken
openssl pkcs12 -in server.pfx \
    -out combined.pem \
    -nodes \
    -passin pass:MyPassword

Branchenspezifische PFX-Anforderungen

Branche Key Storage Export Besonderheit
Windows Server MachineKeySet Exportable IIS SSL-Binding
Azure UserKeySet Non-Exportable App Service
Code Signing MachineKeySet Non-Exportable Authenticode
Smart Card Hardware Non-Exportable PIV-Zertifikate

Verwandte Szenarien

Beziehung Szenario Beschreibung
Alternative 12.1 PEM Export Linux-Format
Verwandt 12.3 PKCS#7 Chain Nur Zertifikate
Voraussetzung 3.1 Server-Zertifikat Zertifikat erstellen

« ← 12.1 PEM Export | ↑ Import/Export | 12.3 PKCS#7 Chain → »


Wolfgang van der Stille @ EMSR DATA d.o.o. - Post-Quantum Cryptography Professional