Scenario 11.4: Backup chiavi

Categoria: Gestione chiavi
Complessità: Alta
Prerequisiti: Chiavi generate, infrastruttura di backup
Tempo stimato: 25-35 minuti


Descrizione

Questo scenario descrive il backup sicuro delle chiavi crittografiche. Un concetto di backup ben pianificato è fondamentale per la Business Continuity e il Disaster Recovery.

Strategie di backup:


Workflow

flowchart TD KEY[Chiave privata] --> ENCRYPT[Crittografare] ENCRYPT --> SPLIT{Splitting?} SPLIT -->|Sì| SHAMIR[Shamir Split n-of-m] SPLIT -->|No| SINGLE[Backup singolo] SHAMIR --> DIST[Distribuire] SINGLE --> STORE[Salvare] DIST --> LOC1[Sede 1] DIST --> LOC2[Sede 2] DIST --> LOC3[Sede 3] STORE --> OFFSITE[Offsite Storage] LOC1 --> VERIFY[Test Restore] LOC2 --> VERIFY LOC3 --> VERIFY OFFSITE --> VERIFY style ENCRYPT fill:#fff3e0 style VERIFY fill:#e8f5e9


Esempio codice: Backup crittografato

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. Esportare chiave privata come PKCS#8
        byte[] pkcs8 = privateKey switch
        {
            RSA rsa => rsa.ExportPkcs8PrivateKey(),
            ECDsa ecdsa => ecdsa.ExportPkcs8PrivateKey(),
            _ => ctx.ExportPrivateKey(privateKey)
        };
 
        // 2. Derivare Key Encryption Key (KEK)
        var salt = RandomNumberGenerator.GetBytes(32);
        var kek = ctx.DeriveKey(
            backupPassword,
            salt,
            outputLength: 32,
            algorithm: KeyDerivationAlgorithm.Argon2id,
            iterations: 4,
            memoryKiB: 65536,
            parallelism: 4
        );
 
        // 3. Crittografare con AES-256-GCM
        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. Creare file backup
        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. Salvare come JSON
        var json = JsonSerializer.Serialize(backup, new JsonSerializerOptions
        {
            WriteIndented = true
        });
        File.WriteAllText(backupPath, json);
 
        // 6. Pulire memoria
        CryptographicOperations.ZeroMemory(pkcs8);
        CryptographicOperations.ZeroMemory(kek);
 
        Console.WriteLine($"Backup creato: {backupPath}");
        Console.WriteLine($"  Checksum: {backup.Checksum}");
    }
 
    public AsymmetricAlgorithm RestoreFromBackup(
        string backupPath,
        string backupPassword)
    {
        using var ctx = PqCryptoContext.Initialize();
 
        // 1. Caricare backup
        var json = File.ReadAllText(backupPath);
        var backup = JsonSerializer.Deserialize<KeyBackup>(json);
 
        // 2. Derivare KEK (stessi parametri del 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. Decrittografare
        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. Verificare checksum
        var checksum = ComputeChecksum(pkcs8);
        if (checksum != backup.Checksum)
        {
            throw new CryptographicException("Checksum backup non valido");
        }
 
        // 5. Importare chiave
        var key = ctx.ImportPrivateKey(pkcs8, backup.Algorithm);
 
        CryptographicOperations.ZeroMemory(pkcs8);
        CryptographicOperations.ZeroMemory(kek);
 
        Console.WriteLine($"Backup ripristinato: {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; }
}

Esempio codice: Shamir Secret Sharing

using SecretSharingDotNet.Cryptography;
 
public class ShamirKeyBackup
{
    public ShamirShares SplitKey(
        byte[] privateKey,
        int totalShares,
        int threshold)
    {
        // Shamir's Secret Sharing: schema n-of-m
        // es. 3-of-5: necessari almeno 3 di 5 share
 
        var gf = new ExtendedEuclideanAlgorithm<BigInteger>();
        var shamir = new ShamirsSecretSharing<BigInteger>(gf);
 
        // Chiave come BigInteger
        var secret = new BigInteger(privateKey, isUnsigned: true);
 
        // Dividere in share
        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($"Chiave divisa in {totalShares} share (Threshold: {threshold})");
        return result;
    }
 
    public byte[] RecombineKey(ShamirShares shares, List<int> shareIndices)
    {
        if (shareIndices.Count < shares.Threshold)
        {
            throw new ArgumentException(
                $"Necessari almeno {shares.Threshold} share, ma presenti solo {shareIndices.Count}"
            );
        }
 
        var gf = new ExtendedEuclideanAlgorithm<BigInteger>();
        var shamir = new ShamirsSecretSharing<BigInteger>(gf);
 
        // Raccogliere share
        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();
 
        // Ricostruire segreto
        var secret = shamir.Reconstruction(selectedShares);
 
        return secret.ToByteArray();
    }
 
    public void DistributeShares(ShamirShares shares, string[] recipients)
    {
        // Distribuire share a diverse persone/sedi
        for (int i = 0; i < shares.Shares.Count && i < recipients.Length; i++)
        {
            var share = shares.Shares[i];
            var recipient = recipients[i];
 
            // In pratica: inviare crittografato al destinatario
            Console.WriteLine($"Share {share.Index} → {recipient}");
 
            // Esempio: come codice QR o chiavetta USB
            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; }
}

Strategia di backup per tipo di chiave

Tipo chiave Metodo backup Copie Sedi Conservazione
Root-CA Shamir 3-of-5 + HSM 5 3 geo-distribuite Permanente
Intermediate-CA Encrypted + HSM 3 2 sedi 10 anni
TLS Server Encrypted 2 Datacenter + Offsite 2 anni
Code Signing Shamir 2-of-3 3 2 sedi 5 anni
User Keys Encrypted 2 Locale + Cloud 1 anno

Requisiti di backup specifici per settore

Settore Requisito Metodo Particolarità
Finanza PCI-DSS HSM + Dual Control Obbligo Audit-Log
Sanità HIPAA Key Escrow Accesso per emergenze
Enti pubblici VS-NfD Cassaforte + 2 persone Responsabile protezione segreti
Energia KRITIS Backup offline Sistema air-gapped

Scenari correlati

Relazione Scenario Descrizione
Prerequisito 11.2 Archiviazione chiavi Conservare chiavi in sicurezza
Correlato 11.3 Rotazione chiavi Backup prima della rotazione
Passo successivo 11.5 Distruzione chiavi Eliminare backup vecchi
Correlato 4.4 Backup certificati Backup certificati

« 11.3 Rotazione chiavi | Panoramica chiavi | 11.5 Distruzione chiavi »


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