Category: Key Management
Complexity: ⭐⭐⭐⭐ (High)
Prerequisites: Generated keys
Estimated Time: 20-30 Minutes
This scenario describes the secure storage of cryptographic keys. Protection of private keys is essential - compromised keys endanger all certificates and signatures based on them.
Storage Options:
| Option | Security | Cost | Application |
|---|---|---|---|
| File System (encrypted) | Medium | Low | Development, Test |
| Windows DPAPI | Good | Low | Windows Server |
| Azure Key Vault | High | Medium | Cloud |
| HashiCorp Vault | High | Medium | On-Premise/Cloud |
| HSM | Very High | High | CA, Critical Systems |
using WvdS.Security.Cryptography.X509Certificates.Extensions.PQ; using var ctx = PqCryptoContext.Initialize(); // Generate key using var keyPair = ctx.GenerateKeyPair(PqAlgorithm.MlDsa65); // Save as encrypted PEM (PKCS#8) keyPair.ToEncryptedPemFile( path: "private-key.pem", password: "StrongPassword123!", pbeAlgorithm: PbeAlgorithm.Aes256Cbc, prf: PbePrf.HmacSha256, iterations: 100000 // PBKDF2 iterations ); // Public key (unencrypted, shareable) keyPair.PublicKey.ToPemFile("public-key.pem"); Console.WriteLine("Keys saved:"); Console.WriteLine(" Private: private-key.pem (AES-256-CBC encrypted)"); Console.WriteLine(" Public: public-key.pem");
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) { // Encrypt with DPAPI var protectedData = ProtectedData.Protect( privateKey, entropy: Encoding.UTF8.GetBytes(keyId), // Additional entropy scope: scope ); var keyPath = Path.Combine(_keyStorePath, $"{keyId}.dpapi"); File.WriteAllBytes(keyPath, protectedData); // Set ACL (current user only) var fileSecurity = new FileSecurity(); fileSecurity.SetAccessRuleProtection(true, false); // Disable inheritance fileSecurity.AddAccessRule(new FileSystemAccessRule( Environment.UserName, FileSystemRights.Read | FileSystemRights.Write, AccessControlType.Allow )); var fileInfo = new FileInfo(keyPath); fileInfo.SetAccessControl(fileSecurity); Console.WriteLine($"Key stored with 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($"Key not found: {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)) { // Secure deletion: Overwrite before delete 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($"Key deleted: {keyId}"); } } }
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) { // Store key as secret (for PQ keys) var secretClient = new SecretClient( new Uri(_vaultUrl), new DefaultAzureCredential() ); var base64Key = Convert.ToBase64String(privateKeyPkcs8); await secretClient.SetSecretAsync(keyName, base64Key); Console.WriteLine($"Key stored in 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) { // Create new secret (versioned) await StoreKeyAsync(keyName, newPrivateKey); // Old version remains available as backup Console.WriteLine($"Key rotated: {keyName}"); } }
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($"Key stored in 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; } }
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), // Not exportable! new ObjectAttribute(CKA.CKA_LABEL, keyLabel), new ObjectAttribute(CKA.CKA_VALUE, privateKey) }; session.CreateObject(keyAttributes); Console.WriteLine($"Key stored in HSM: {keyLabel}"); } finally { // Overwrite key data in memory Array.Clear(privateKey, 0, privateKey.Length); session.Logout(); } } }
| Industry | Storage Location | Encryption | Access Control |
|---|---|---|---|
| Development | File System | PKCS#8 | File Permissions |
| Enterprise | Key Vault / Vault | AES-256 | RBAC |
| Financial Sector | HSM (FIPS 140-3) | Hardware | Multi-Person |
| Healthcare | HSM or Vault | AES-256 | Audit Log |
| CA/PKI | HSM | Hardware | n-of-m Quorum |
| Relationship | Scenario | Description |
|---|---|---|
| Prerequisite | 11.1 Key Generation | Create keys |
| Next Step | 11.3 Key Rotation | Regular exchange |
| Related | 11.4 Key Backup | Backup copies |
| Related | 11.5 Key Destruction | Secure deletion |
« ← 11.1 Key Generation | ↑ Key Overview | 11.3 Key Rotation → »
Wolfgang van der Stille @ EMSR DATA d.o.o. - Post-Quantum Cryptography Professional