Szenario 11.4: Schlüssel-Backup

Kategorie: Schlüsselmanagement
Komplexität: ⭐⭐⭐⭐ (Hoch)
Voraussetzungen: Generierte Schlüssel, Backup-Infrastruktur
Geschätzte Zeit: 25-35 Minuten


Beschreibung

Dieses Szenario beschreibt das sichere Backup kryptographischer Schlüssel. Ein durchdachtes Backup-Konzept ist entscheidend für Business Continuity und Disaster Recovery.

Backup-Strategien:


Workflow

flowchart TD KEY[Private Key] --> ENCRYPT[Verschlüsseln] ENCRYPT --> SPLIT{Splitting?} SPLIT -->|Ja| SHAMIR[Shamir Split n-of-m] SPLIT -->|Nein| SINGLE[Einzelnes Backup] SHAMIR --> DIST[Verteilen] SINGLE --> STORE[Speichern] DIST --> LOC1[Standort 1] DIST --> LOC2[Standort 2] DIST --> LOC3[Standort 3] STORE --> OFFSITE[Offsite Storage] LOC1 --> VERIFY[Restore-Test] LOC2 --> VERIFY LOC3 --> VERIFY OFFSITE --> VERIFY style ENCRYPT fill:#fff3e0 style VERIFY fill:#e8f5e9


Code-Beispiel: Verschlüsseltes Backup

using WvdS.Security.Cryptography.X509Certificates.Extensions.PQ;
using System.Security.Cryptography;
 
public class KeyBackupService
{
    public void CreateEncryptedBackup(
        AsymmetricAlgorithm privateKey,
        string backupPath,
        string backupPassword,
        BackupOptions options = null)
    {
        options ??= BackupOptions.Default;
        using var ctx = PqCryptoContext.Initialize();
 
        // 1. Private Key als PKCS#8 exportieren
        byte[] pkcs8 = privateKey switch
        {
            RSA rsa => rsa.ExportPkcs8PrivateKey(),
            ECDsa ecdsa => ecdsa.ExportPkcs8PrivateKey(),
            _ => ctx.ExportPrivateKey(privateKey)
        };
 
        // 2. Key Encryption Key (KEK) ableiten
        var salt = RandomNumberGenerator.GetBytes(32);
        var kek = ctx.DeriveKey(
            backupPassword,
            salt,
            outputLength: 32,
            algorithm: KeyDerivationAlgorithm.Argon2id,
            iterations: 4,
            memoryKiB: 65536,
            parallelism: 4
        );
 
        // 3. Mit AES-256-GCM verschlüsseln
        var nonce = RandomNumberGenerator.GetBytes(12);
        var ciphertext = new byte[pkcs8.Length];
        var tag = new byte[16];
 
        using var aes = new OpenSslAesGcm(kek);
        aes.Encrypt(nonce, pkcs8, ciphertext, tag);
 
        // 4. Backup-Datei erstellen
        var backup = new KeyBackup
        {
            Version = 1,
            Algorithm = "ML-DSA-65",
            CreatedAt = DateTime.UtcNow,
            KdfAlgorithm = "Argon2id",
            KdfSalt = Convert.ToBase64String(salt),
            KdfIterations = 4,
            KdfMemoryKiB = 65536,
            EncryptionAlgorithm = "AES-256-GCM",
            Nonce = Convert.ToBase64String(nonce),
            Tag = Convert.ToBase64String(tag),
            EncryptedKey = Convert.ToBase64String(ciphertext),
            Checksum = ComputeChecksum(pkcs8)
        };
 
        // 5. Als JSON speichern
        var json = JsonSerializer.Serialize(backup, new JsonSerializerOptions
        {
            WriteIndented = true
        });
        File.WriteAllText(backupPath, json);
 
        // 6. Speicher bereinigen
        CryptographicOperations.ZeroMemory(pkcs8);
        CryptographicOperations.ZeroMemory(kek);
 
        Console.WriteLine($"Backup erstellt: {backupPath}");
        Console.WriteLine($"  Checksum: {backup.Checksum}");
    }
 
    public AsymmetricAlgorithm RestoreFromBackup(
        string backupPath,
        string backupPassword)
    {
        using var ctx = PqCryptoContext.Initialize();
 
        // 1. Backup laden
        var json = File.ReadAllText(backupPath);
        var backup = JsonSerializer.Deserialize<KeyBackup>(json);
 
        // 2. KEK ableiten (gleiche Parameter wie beim Backup)
        var salt = Convert.FromBase64String(backup.KdfSalt);
        var kek = ctx.DeriveKey(
            backupPassword,
            salt,
            outputLength: 32,
            algorithm: KeyDerivationAlgorithm.Argon2id,
            iterations: backup.KdfIterations,
            memoryKiB: backup.KdfMemoryKiB,
            parallelism: 4
        );
 
        // 3. Entschlüsseln
        var nonce = Convert.FromBase64String(backup.Nonce);
        var tag = Convert.FromBase64String(backup.Tag);
        var ciphertext = Convert.FromBase64String(backup.EncryptedKey);
        var pkcs8 = new byte[ciphertext.Length];
 
        using var aes = new OpenSslAesGcm(kek);
        aes.Decrypt(nonce, ciphertext, tag, pkcs8);
 
        // 4. Checksum verifizieren
        var checksum = ComputeChecksum(pkcs8);
        if (checksum != backup.Checksum)
        {
            throw new CryptographicException("Backup-Checksum ungültig");
        }
 
        // 5. Key importieren
        var key = ctx.ImportPrivateKey(pkcs8, backup.Algorithm);
 
        CryptographicOperations.ZeroMemory(pkcs8);
        CryptographicOperations.ZeroMemory(kek);
 
        Console.WriteLine($"Backup wiederhergestellt: {backup.Algorithm}");
        return key;
    }
 
    private string ComputeChecksum(byte[] data)
    {
        var hash = SHA256.HashData(data);
        return Convert.ToHexString(hash).Substring(0, 16);
    }
}
 
public class KeyBackup
{
    public int Version { get; set; }
    public string Algorithm { get; set; }
    public DateTime CreatedAt { get; set; }
    public string KdfAlgorithm { get; set; }
    public string KdfSalt { get; set; }
    public int KdfIterations { get; set; }
    public int KdfMemoryKiB { get; set; }
    public string EncryptionAlgorithm { get; set; }
    public string Nonce { get; set; }
    public string Tag { get; set; }
    public string EncryptedKey { get; set; }
    public string Checksum { get; set; }
}

Code-Beispiel: Shamir Secret Sharing

using SecretSharingDotNet.Cryptography;
 
public class ShamirKeyBackup
{
    public ShamirShares SplitKey(
        byte[] privateKey,
        int totalShares,
        int threshold)
    {
        // Shamir's Secret Sharing: n-of-m Schema
        // z.B. 3-of-5: Mindestens 3 von 5 Shares benötigt
 
        var gf = new ExtendedEuclideanAlgorithm<BigInteger>();
        var shamir = new ShamirsSecretSharing<BigInteger>(gf);
 
        // Key als BigInteger
        var secret = new BigInteger(privateKey, isUnsigned: true);
 
        // In Shares aufteilen
        var shares = shamir.MakeShares(
            (uint)threshold,
            (uint)totalShares,
            secret
        );
 
        var result = new ShamirShares
        {
            Threshold = threshold,
            TotalShares = totalShares,
            Shares = shares.Select((s, i) => new Share
            {
                Index = i + 1,
                Value = Convert.ToBase64String(s.Y.ToByteArray())
            }).ToList()
        };
 
        Console.WriteLine($"Key in {totalShares} Shares aufgeteilt (Threshold: {threshold})");
        return result;
    }
 
    public byte[] RecombineKey(ShamirShares shares, List<int> shareIndices)
    {
        if (shareIndices.Count < shares.Threshold)
        {
            throw new ArgumentException(
                $"Mindestens {shares.Threshold} Shares benötigt, aber nur {shareIndices.Count} vorhanden"
            );
        }
 
        var gf = new ExtendedEuclideanAlgorithm<BigInteger>();
        var shamir = new ShamirsSecretSharing<BigInteger>(gf);
 
        // Shares sammeln
        var selectedShares = shareIndices
            .Select(i => shares.Shares.First(s => s.Index == i))
            .Select(s => new FinitePoint<BigInteger>(
                s.Index,
                new BigInteger(Convert.FromBase64String(s.Value), isUnsigned: true)
            ))
            .ToArray();
 
        // Secret rekonstruieren
        var secret = shamir.Reconstruction(selectedShares);
 
        return secret.ToByteArray();
    }
 
    public void DistributeShares(ShamirShares shares, string[] recipients)
    {
        // Shares an verschiedene Personen/Standorte verteilen
        for (int i = 0; i < shares.Shares.Count && i < recipients.Length; i++)
        {
            var share = shares.Shares[i];
            var recipient = recipients[i];
 
            // In der Praxis: Verschlüsselt an Empfänger senden
            Console.WriteLine($"Share {share.Index} → {recipient}");
 
            // Beispiel: Als QR-Code oder USB-Stick
            var shareFile = $"share-{share.Index}-{recipient.Replace(" ", "_")}.json";
            File.WriteAllText(shareFile, JsonSerializer.Serialize(share));
        }
    }
}
 
public class ShamirShares
{
    public int Threshold { get; set; }
    public int TotalShares { get; set; }
    public List<Share> Shares { get; set; }
}
 
public class Share
{
    public int Index { get; set; }
    public string Value { get; set; }
}

Code-Beispiel: HSM-Backup

public class HsmBackupService
{
    public void BackupHsmKey(string keyLabel, string backupPin)
    {
        // HSM-Keys sind oft nicht exportierbar!
        // Alternative: Key Wrapping mit Master Backup Key
 
        using var pkcs11 = new Pkcs11Library("softhsm2.dll");
        var slot = pkcs11.GetSlotList(SlotsType.WithTokenPresent).First();
 
        using var session = slot.OpenSession(SessionType.ReadWrite);
        session.Login(CKU.CKU_USER, backupPin);
 
        try
        {
            // 1. Zu sichernden Key finden
            var keyToBackup = session.FindAllObjects(new List<ObjectAttribute>
            {
                new ObjectAttribute(CKA.CKA_LABEL, keyLabel),
                new ObjectAttribute(CKA.CKA_CLASS, CKO.CKO_PRIVATE_KEY)
            }).FirstOrDefault();
 
            if (keyToBackup == null)
                throw new Exception($"Key nicht gefunden: {keyLabel}");
 
            // 2. Backup Wrapping Key finden/erstellen
            var wrappingKey = GetOrCreateWrappingKey(session);
 
            // 3. Key wrappen (verschlüsselt exportieren)
            var mechanism = new Mechanism(CKM.CKM_AES_KEY_WRAP);
            var wrappedKey = session.WrapKey(mechanism, wrappingKey, keyToBackup);
 
            // 4. Wrapped Key speichern
            var backup = new HsmKeyBackup
            {
                KeyLabel = keyLabel,
                WrappingKeyId = GetKeyId(wrappingKey),
                WrappedKeyData = Convert.ToBase64String(wrappedKey),
                Mechanism = "AES-KEY-WRAP",
                CreatedAt = DateTime.UtcNow
            };
 
            File.WriteAllText(
                $"hsm-backup-{keyLabel}.json",
                JsonSerializer.Serialize(backup)
            );
 
            Console.WriteLine($"HSM Key Backup erstellt: {keyLabel}");
        }
        finally
        {
            session.Logout();
        }
    }
 
    public void RestoreHsmKey(string backupFile, string userPin)
    {
        var backup = JsonSerializer.Deserialize<HsmKeyBackup>(
            File.ReadAllText(backupFile)
        );
 
        using var pkcs11 = new Pkcs11Library("softhsm2.dll");
        var slot = pkcs11.GetSlotList(SlotsType.WithTokenPresent).First();
 
        using var session = slot.OpenSession(SessionType.ReadWrite);
        session.Login(CKU.CKU_USER, userPin);
 
        try
        {
            // Wrapping Key laden
            var wrappingKey = FindKeyById(session, backup.WrappingKeyId);
 
            // Key unwrappen
            var mechanism = new Mechanism(CKM.CKM_AES_KEY_WRAP);
            var wrappedData = Convert.FromBase64String(backup.WrappedKeyData);
 
            var keyAttributes = new List<ObjectAttribute>
            {
                new ObjectAttribute(CKA.CKA_CLASS, CKO.CKO_PRIVATE_KEY),
                new ObjectAttribute(CKA.CKA_LABEL, $"{backup.KeyLabel}-restored"),
                new ObjectAttribute(CKA.CKA_TOKEN, true),
                new ObjectAttribute(CKA.CKA_PRIVATE, true),
                new ObjectAttribute(CKA.CKA_SENSITIVE, true)
            };
 
            var restoredKey = session.UnwrapKey(mechanism, wrappingKey, wrappedData, keyAttributes);
 
            Console.WriteLine($"HSM Key wiederhergestellt: {backup.KeyLabel}");
        }
        finally
        {
            session.Logout();
        }
    }
}

Backup-Strategie nach Schlüsseltyp

Schlüsseltyp Backup-Methode Copies Standorte Retention
Root-CA Shamir 3-of-5 + HSM 5 3 geo-verteilt Permanent
Intermediate-CA Encrypted + HSM 3 2 Standorte 10 Jahre
TLS Server Encrypted 2 Datacenter + Offsite 2 Jahre
Code Signing Shamir 2-of-3 3 2 Standorte 5 Jahre
User Keys Encrypted 2 Local + Cloud 1 Jahr

Branchenspezifische Backup-Anforderungen

Branche Anforderung Methode Besonderheit
Finanzsektor PCI-DSS HSM + Dual Control Audit-Log Pflicht
Healthcare HIPAA Key Escrow Zugriff für Notfälle
Behörden VS-NfD Tresor + 2-Personen Geheimschutzbeauftragter
Energie KRITIS Offline-Backup Air-Gapped System

Verwandte Szenarien

Beziehung Szenario Beschreibung
Voraussetzung 11.2 Schlüsselspeicherung Schlüssel sicher aufbewahren
Verwandt 11.3 Schlüssel-Rotation Vor Rotation sichern
Nächster Schritt 11.5 Schlüsselvernichtung Alte Backups löschen
Verwandt 4.4 Zertifikats-Backup Zertifikate sichern

« ← 11.3 Schlüssel-Rotation | ↑ Schlüssel-Übersicht | 11.5 Schlüsselvernichtung → »


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