~~NOTOC~~ ====== Szenario 11.4: Schlüssel-Backup ====== **Kategorie:** [[.:start|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:** * **Verschlüsseltes Backup** - AES-256 oder PQ-hybrid * **Key Splitting** - Shamir's Secret Sharing * **Geografische Verteilung** - Mehrere Standorte * **Offline-Backup** - Air-Gapped Systeme * **HSM-Backup** - Hardware-basiert ---- ===== 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(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(); var shamir = new ShamirsSecretSharing(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 shareIndices) { if (shareIndices.Count < shares.Threshold) { throw new ArgumentException( $"Mindestens {shares.Threshold} Shares benötigt, aber nur {shareIndices.Count} vorhanden" ); } var gf = new ExtendedEuclideanAlgorithm(); var shamir = new ShamirsSecretSharing(gf); // Shares sammeln var selectedShares = shareIndices .Select(i => shares.Shares.First(s => s.Index == i)) .Select(s => new FinitePoint( 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 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 { 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( 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 { 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** | [[.:speicherung|11.2 Schlüsselspeicherung]] | Schlüssel sicher aufbewahren | | **Verwandt** | [[.:rotation|11.3 Schlüssel-Rotation]] | Vor Rotation sichern | | **Nächster Schritt** | [[.:vernichtung|11.5 Schlüsselvernichtung]] | Alte Backups löschen | | **Verwandt** | [[de:int:pqcrypt:szenarien:verwaltung:backup|4.4 Zertifikats-Backup]] | Zertifikate sichern | ---- << [[.:rotation|← 11.3 Schlüssel-Rotation]] | [[.:start|↑ Schlüssel-Übersicht]] | [[.:vernichtung|11.5 Schlüsselvernichtung →]] >> {{tag>szenario schluessel backup shamir disaster-recovery hsm}} ---- //Wolfgang van der Stille @ EMSR DATA d.o.o. - Post-Quantum Cryptography Professional//