Scenarij 11.2: Pohrana ključeva

Kategorija: Upravljanje ključevima
Složenost: ⭐⭐⭐⭐ (Visoka)
Preduvjeti: Generirani ključevi
Procijenjeno vrijeme: 20-30 minuta


Opis

Ovaj scenarij opisuje sigurnu pohranu kriptografskih ključeva. Zaštita privatnih ključeva je esencijalna - kompromitirani ključevi ugrožavaju sve certifikate i potpise koji se na njima temelje.

Opcije pohrane:

Opcija Sigurnost Trošak Primjena
Datotečni sustav (šifrirano) Srednja Niska Razvoj, Testiranje
Windows DPAPI Dobra Niska Windows Server
Azure Key Vault Visoka Srednja Cloud
HashiCorp Vault Visoka Srednja On-Premise/Cloud
HSM Vrlo visoka Visoka CA, Kritični sustavi

Tijek rada

flowchart TD KEY[Privatni ključ] --> ENCRYPT[Šifriranje] ENCRYPT --> ACL[Kontrola pristupa] ACL --> STORE{Mjesto pohrane} STORE --> FILE[Datotečni sustav] STORE --> VAULT[Key Vault] STORE --> HSM[HSM] FILE --> BACKUP[Sigurnosna kopija] VAULT --> BACKUP HSM --> BACKUP style ENCRYPT fill:#fff3e0 style ACL fill:#e8f5e9


Primjer koda: Šifrirana datoteka (PEM)

using WvdS.Security.Cryptography.X509Certificates.Extensions.PQ;
 
using var ctx = PqCryptoContext.Initialize();
 
// Generiranje ključa
using var keyPair = ctx.GenerateKeyPair(PqAlgorithm.MlDsa65);
 
// Spremanje kao šifrirani PEM (PKCS#8)
keyPair.ToEncryptedPemFile(
    path: "private-key.pem",
    password: "StrongPassword123!",
    pbeAlgorithm: PbeAlgorithm.Aes256Cbc,
    prf: PbePrf.HmacSha256,
    iterations: 100000  // PBKDF2 iteracije
);
 
// Javni ključ (nešifriran, može se javno dijeliti)
keyPair.PublicKey.ToPemFile("public-key.pem");
 
Console.WriteLine("Ključevi spremljeni:");
Console.WriteLine("  Privatni: private-key.pem (AES-256-CBC šifrirano)");
Console.WriteLine("  Javni: public-key.pem");

Primjer koda: Windows DPAPI

using System.Security.Cryptography;
 
public class DpapiKeyStorage
{
    private readonly string _keyStorePath;
 
    public DpapiKeyStorage(string basePath = null)
    {
        _keyStorePath = basePath ?? Path.Combine(
            Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
            "WvdS", "Keys"
        );
        Directory.CreateDirectory(_keyStorePath);
    }
 
    public void StoreKey(string keyId, byte[] privateKey, DataProtectionScope scope = DataProtectionScope.CurrentUser)
    {
        // Šifriranje s DPAPI
        var protectedData = ProtectedData.Protect(
            privateKey,
            entropy: Encoding.UTF8.GetBytes(keyId),  // Dodatna entropija
            scope: scope
        );
 
        var keyPath = Path.Combine(_keyStorePath, $"{keyId}.dpapi");
        File.WriteAllBytes(keyPath, protectedData);
 
        // Postavljanje ACL-a (samo trenutni korisnik)
        var fileSecurity = new FileSecurity();
        fileSecurity.SetAccessRuleProtection(true, false);  // Deaktivacija nasljeđivanja
        fileSecurity.AddAccessRule(new FileSystemAccessRule(
            Environment.UserName,
            FileSystemRights.Read | FileSystemRights.Write,
            AccessControlType.Allow
        ));
 
        var fileInfo = new FileInfo(keyPath);
        fileInfo.SetAccessControl(fileSecurity);
 
        Console.WriteLine($"Ključ pohranjen s DPAPI: {keyPath}");
    }
 
    public byte[] LoadKey(string keyId, DataProtectionScope scope = DataProtectionScope.CurrentUser)
    {
        var keyPath = Path.Combine(_keyStorePath, $"{keyId}.dpapi");
 
        if (!File.Exists(keyPath))
        {
            throw new FileNotFoundException($"Ključ nije pronađen: {keyId}");
        }
 
        var protectedData = File.ReadAllBytes(keyPath);
 
        return ProtectedData.Unprotect(
            protectedData,
            entropy: Encoding.UTF8.GetBytes(keyId),
            scope: scope
        );
    }
 
    public void DeleteKey(string keyId)
    {
        var keyPath = Path.Combine(_keyStorePath, $"{keyId}.dpapi");
 
        if (File.Exists(keyPath))
        {
            // Sigurno brisanje: Prepisivanje prije brisanja
            var fileLength = new FileInfo(keyPath).Length;
            using (var fs = new FileStream(keyPath, FileMode.Open, FileAccess.Write))
            {
                var zeros = new byte[fileLength];
                fs.Write(zeros, 0, zeros.Length);
                fs.Flush();
            }
 
            File.Delete(keyPath);
            Console.WriteLine($"Ključ izbrisan: {keyId}");
        }
    }
}

Primjer koda: Azure Key Vault

using Azure.Security.KeyVault.Keys;
using Azure.Security.KeyVault.Keys.Cryptography;
using Azure.Identity;
 
public class AzureKeyVaultStorage
{
    private readonly KeyClient _keyClient;
    private readonly string _vaultUrl;
 
    public AzureKeyVaultStorage(string vaultUrl)
    {
        _vaultUrl = vaultUrl;
        _keyClient = new KeyClient(
            new Uri(vaultUrl),
            new DefaultAzureCredential()
        );
    }
 
    public async Task<string> StoreKeyAsync(string keyName, byte[] privateKeyPkcs8)
    {
        // Pohrana ključa kao Secret (za PQ ključeve)
        var secretClient = new SecretClient(
            new Uri(_vaultUrl),
            new DefaultAzureCredential()
        );
 
        var base64Key = Convert.ToBase64String(privateKeyPkcs8);
 
        await secretClient.SetSecretAsync(keyName, base64Key);
 
        Console.WriteLine($"Ključ pohranjen u Key Vault: {keyName}");
        return keyName;
    }
 
    public async Task<byte[]> LoadKeyAsync(string keyName)
    {
        var secretClient = new SecretClient(
            new Uri(_vaultUrl),
            new DefaultAzureCredential()
        );
 
        var secret = await secretClient.GetSecretAsync(keyName);
        return Convert.FromBase64String(secret.Value.Value);
    }
 
    public async Task RotateKeyAsync(string keyName, byte[] newPrivateKey)
    {
        // Kreiranje novog Secreta (verzioniran)
        await StoreKeyAsync(keyName, newPrivateKey);
 
        // Stara verzija ostaje dostupna kao sigurnosna kopija
        Console.WriteLine($"Ključ rotiran: {keyName}");
    }
}

Primjer koda: HashiCorp Vault

using VaultSharp;
using VaultSharp.V1.AuthMethods.Token;
 
public class HashiCorpVaultStorage
{
    private readonly IVaultClient _vaultClient;
    private readonly string _mountPath;
 
    public HashiCorpVaultStorage(string vaultAddress, string token, string mountPath = "secret")
    {
        var authMethod = new TokenAuthMethodInfo(token);
        var settings = new VaultClientSettings(vaultAddress, authMethod);
        _vaultClient = new VaultClient(settings);
        _mountPath = mountPath;
    }
 
    public async Task StoreKeyAsync(string keyPath, byte[] privateKey, Dictionary<string, string> metadata = null)
    {
        var secretData = new Dictionary<string, object>
        {
            ["privateKey"] = Convert.ToBase64String(privateKey),
            ["algorithm"] = "ML-DSA-65",
            ["createdAt"] = DateTime.UtcNow.ToString("O")
        };
 
        if (metadata != null)
        {
            foreach (var (key, value) in metadata)
            {
                secretData[key] = value;
            }
        }
 
        await _vaultClient.V1.Secrets.KeyValue.V2.WriteSecretAsync(
            path: keyPath,
            data: secretData,
            mountPoint: _mountPath
        );
 
        Console.WriteLine($"Ključ pohranjen u Vault: {keyPath}");
    }
 
    public async Task<byte[]> LoadKeyAsync(string keyPath)
    {
        var secret = await _vaultClient.V1.Secrets.KeyValue.V2.ReadSecretAsync(
            path: keyPath,
            mountPoint: _mountPath
        );
 
        var base64Key = secret.Data.Data["privateKey"].ToString();
        return Convert.FromBase64String(base64Key);
    }
 
    public async Task<IEnumerable<string>> ListKeysAsync(string prefix = "")
    {
        var list = await _vaultClient.V1.Secrets.KeyValue.V2.ReadSecretPathsAsync(
            path: prefix,
            mountPoint: _mountPath
        );
 
        return list.Data.Keys;
    }
}

HSM integracija (PKCS#11)

public class HsmKeyStorage
{
    private readonly string _pkcs11LibPath;
    private readonly string _tokenLabel;
 
    public HsmKeyStorage(string pkcs11LibPath, string tokenLabel)
    {
        _pkcs11LibPath = pkcs11LibPath;
        _tokenLabel = tokenLabel;
    }
 
    public void StoreKey(string keyLabel, byte[] privateKey, string userPin)
    {
        using var pkcs11Library = new Pkcs11Library(_pkcs11LibPath);
 
        var slot = pkcs11Library.GetSlotList(SlotsType.WithTokenPresent)
            .First(s => s.GetTokenInfo().Label.Trim() == _tokenLabel);
 
        using var session = slot.OpenSession(SessionType.ReadWrite);
        session.Login(CKU.CKU_USER, userPin);
 
        try
        {
            var keyAttributes = new List<ObjectAttribute>
            {
                new ObjectAttribute(CKA.CKA_CLASS, CKO.CKO_PRIVATE_KEY),
                new ObjectAttribute(CKA.CKA_KEY_TYPE, CKK.CKK_GENERIC_SECRET),
                new ObjectAttribute(CKA.CKA_TOKEN, true),
                new ObjectAttribute(CKA.CKA_PRIVATE, true),
                new ObjectAttribute(CKA.CKA_SENSITIVE, true),
                new ObjectAttribute(CKA.CKA_EXTRACTABLE, false),  // Nije izvoziv!
                new ObjectAttribute(CKA.CKA_LABEL, keyLabel),
                new ObjectAttribute(CKA.CKA_VALUE, privateKey)
            };
 
            session.CreateObject(keyAttributes);
            Console.WriteLine($"Ključ pohranjen u HSM: {keyLabel}");
        }
        finally
        {
            // Prepisivanje podataka o ključu u memoriji
            Array.Clear(privateKey, 0, privateKey.Length);
            session.Logout();
        }
    }
}

Zahtjevi za pohranu specifični za industriju

Industrija Mjesto pohrane Šifriranje Kontrola pristupa
Razvoj Datotečni sustav PKCS#8 Dozvole datoteka
Enterprise Key Vault / Vault AES-256 RBAC
Financijski sektor HSM (FIPS 140-3) Hardversko Višeosobna kontrola
Zdravstvo HSM ili Vault AES-256 Audit-Log
CA/PKI HSM Hardversko n-of-m Kvorum

Povezani scenariji

Povezanost Scenarij Opis
Preduvjet 11.1 Generiranje ključeva Kreiranje ključeva
Sljedeći korak 11.3 Rotacija ključeva Redovita zamjena
Povezano 11.4 Sigurnosna kopija ključeva Sigurnosne kopije
Povezano 11.5 Uništavanje ključeva Sigurno brisanje

« ← 11.1 Generiranje ključeva | ↑ Pregled ključeva | 11.3 Rotacija ključeva → »


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