Scenarij 12.2: PFX/PKCS#12 izvoz

Kategorija: Uvoz/Izvoz
Složenost: ⭐⭐⭐ (Srednja)
Preduvjeti: Certifikat s privatnim ključem
Procijenjeno vrijeme: 15-20 minuta


Opis

Ovaj scenarij opisuje izvoz i uvoz u PFX/PKCS#12 formatu. PFX (Personal Information Exchange) je standardni format za Windows i .NET za pohranu certifikata zajedno s privatnim ključevima i opcionalno lancem certifikata u datoteci zaštićenoj lozinkom.

PFX/PKCS#12 svojstva:

  • Sadržaj: Certifikat + privatni ključ + lanac
  • Kodiranje: Binary (ASN.1/DER)
  • Šifriranje: Zaštićeno lozinkom
  • Ekstenzije: .pfx, .p12

Tijek rada

flowchart LR subgraph Ulaz CERT[Certifikat] KEY[Privatni ključ] CHAIN[Lanac] end subgraph PFX["PFX datoteka"] BAG1[Cert Bag] BAG2[Key Bag] BAG3[CA Bags] end CERT --> BAG1 KEY --> BAG2 CHAIN --> BAG3 PFX --> ENC[Šifrirano]


Primjer koda: Kreiranje PFX

using WvdS.Security.Cryptography.X509Certificates.Extensions.PQ;
using System.Security.Cryptography.X509Certificates;
 
using var ctx = PqCryptoContext.Initialize();
 
// Učitavanje certifikata i ključa
var cert = ctx.LoadCertificate("server.crt.pem");
var privateKey = ctx.LoadPrivateKey("server.key.pem", "KeyPassword!");
 
// Kombiniranje certifikata s ključem
var certWithKey = ctx.CombineCertificateAndKey(cert, privateKey);
 
// Izvoz PFX
byte[] pfxBytes = certWithKey.Export(X509ContentType.Pfx, "PfxPassword123!");
File.WriteAllBytes("server.pfx", pfxBytes);
 
Console.WriteLine("PFX kreiran: server.pfx");

Primjer koda: PFX s lancem

public class PfxExporter
{
    public byte[] ExportWithChain(
        X509Certificate2 certificate,
        X509Certificate2Collection chain,
        string password,
        PfxExportOptions options = null)
    {
        options ??= PfxExportOptions.Default;
        using var ctx = PqCryptoContext.Initialize();
 
        // Kreiranje kolekcije za izvoz
        var exportCollection = new X509Certificate2Collection();
        exportCollection.Add(certificate);
 
        // Dodavanje lanca (bez Root ako je željeno)
        foreach (var caCert in chain)
        {
            if (options.IncludeRoot || !IsSelfSigned(caCert))
            {
                exportCollection.Add(caCert);
            }
        }
 
        // Izvoz PFX
        var pfxBytes = exportCollection.Export(X509ContentType.Pfx, password);
 
        Console.WriteLine($"PFX kreiran s {exportCollection.Count} certifikata");
        return pfxBytes;
    }
 
    public void ExportToFile(
        X509Certificate2 certificate,
        X509Certificate2Collection chain,
        string outputPath,
        string password)
    {
        var pfxBytes = ExportWithChain(certificate, chain, password);
        File.WriteAllBytes(outputPath, pfxBytes);
 
        // Postavljanje dozvola (samo vlasnik)
        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();
}

Primjer koda: Uvoz PFX

using var ctx = PqCryptoContext.Initialize();
 
// Učitavanje PFX
var pfxBytes = File.ReadAllBytes("server.pfx");
var cert = new X509Certificate2(
    pfxBytes,
    "PfxPassword123!",
    X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet
);
 
Console.WriteLine($"Subject: {cert.Subject}");
Console.WriteLine($"Ima privatni ključ: {cert.HasPrivateKey}");
 
// Ekstrakcija privatnog ključa
if (cert.HasPrivateKey)
{
    var privateKey = cert.GetRSAPrivateKey()
        ?? cert.GetECDsaPrivateKey()
        ?? (AsymmetricAlgorithm)ctx.GetPqPrivateKey(cert);
 
    Console.WriteLine($"Tip ključa: {privateKey.GetType().Name}");
}
 
// Ekstrakcija lanca iz PFX
var collection = new X509Certificate2Collection();
collection.Import(pfxBytes, "PfxPassword123!", X509KeyStorageFlags.DefaultKeySet);
 
Console.WriteLine($"Certifikati u PFX: {collection.Count}");
foreach (var c in collection)
{
    Console.WriteLine($"  - {c.Subject}");
}

Primjer koda: Sigurni PFX s AES-256

public class SecurePfxExporter
{
    public byte[] ExportSecure(
        X509Certificate2 certificate,
        string password)
    {
        // .NET 5+ podržava modernu PFX enkripciju
        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 baziran izvoz za maksimalnu kompatibilnost
        var pfxBuilder = new Pkcs12Builder();
 
        // Dodavanje Key Bag
        var keyBag = new Pkcs12KeyBag(
            privateKey.ExportPkcs8PrivateKey(),
            skipCopy: false
        );
 
        // Dodavanje Cert Bag
        var certBag = new Pkcs12CertBag(certificate);
 
        // Kreiranje Safe Contents
        var safeContents = new Pkcs12SafeContents();
        safeContents.AddBag(keyBag);
        safeContents.AddBag(certBag);
 
        foreach (var caCert in chain)
        {
            safeContents.AddBag(new Pkcs12CertBag(caCert));
        }
 
        // Šifriranje lozinkom
        pfxBuilder.AddSafeContentsEncrypted(
            safeContents,
            password,
            new PbeParameters(
                PbeEncryptionAlgorithm.Aes256Cbc,
                HashAlgorithmName.SHA256,
                100000
            )
        );
 
        pfxBuilder.SealWithMac(password, HashAlgorithmName.SHA256, 100000);
 
        return pfxBuilder.Encode();
    }
}

Primjer koda: PFX za 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);
 
        // Provjera postoji li već
        var existing = store.Certificates.Find(
            X509FindType.FindByThumbprint,
            cert.Thumbprint,
            validOnly: false
        );
 
        if (existing.Count > 0)
        {
            Console.WriteLine("Certifikat već postoji u Store");
            return;
        }
 
        store.Add(cert);
        store.Close();
 
        Console.WriteLine($"Certifikat instaliran: {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($"Certifikat nije pronađen: {thumbprint}");
        }
 
        var cert = certs[0];
 
        if (!cert.HasPrivateKey)
        {
            throw new Exception("Certifikat nema privatni ključ");
        }
 
        var pfxBytes = cert.Export(X509ContentType.Pfx, password);
        File.WriteAllBytes(outputPath, pfxBytes);
 
        Console.WriteLine($"PFX izvezen: {outputPath}");
    }
}

PFX kreiranje s OpenSSL

# Kreiranje PFX iz PEM datoteka
openssl pkcs12 -export \
    -out server.pfx \
    -inkey server.key \
    -in server.crt \
    -certfile chain.pem \
    -passout pass:MyPassword
 
# PFX s modernim algoritmom (AES-256)
openssl pkcs12 -export \
    -out server.pfx \
    -inkey server.key \
    -in server.crt \
    -certfile chain.pem \
    -aes256 \
    -passout pass:MyPassword
 
# Pregled PFX
openssl pkcs12 -info -in server.pfx -passin pass:MyPassword
 
# Raspakiranje PFX
openssl pkcs12 -in server.pfx \
    -out combined.pem \
    -nodes \
    -passin pass:MyPassword

PFX zahtjevi specifični za industriju

Industrija Pohrana ključa Izvoz Posebnost
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 certifikati

Povezani scenariji

Povezanost Scenarij Opis
Alternativa 12.1 PEM izvoz Linux format
Povezano 12.3 PKCS#7 lanac Samo certifikati
Preduvjet 3.1 Serverski certifikat Kreiranje certifikata

« ← 12.1 PEM izvoz | ↑ Uvoz/Izvoz | 12.3 PKCS#7 lanac → »


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

Zuletzt geändert: 30.01.2026. u 00:22