Encryption
.NET C#
Key Management
Security Practices
Software Development

Best way to store encryption keys in .NET C

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Storing encryption keys securely is one of the most critical aspects of building a secure .NET application. The encryption algorithm itself may be strong, but if the keys are stored in plaintext in source code or configuration files, the entire system is compromised. This article covers the best approaches for key storage in .NET C#, from cloud-based solutions to OS-level APIs, with working code examples for each.

Why Key Storage Matters

Encryption keys are the single point of failure in any cryptographic system. A leaked key means all data encrypted with that key is exposed. Hardcoding keys in source code is especially dangerous because they end up in version control history, build artifacts, and deployment logs. Even removing a key from the current code does not erase it from Git history.

The goal is to keep keys out of source code entirely and store them in a system that provides access control, audit logging, and automatic rotation.

Azure Key Vault is a cloud-hosted HSM-backed service that stores keys, secrets, and certificates. It integrates natively with .NET through the Azure.Security.KeyVault.Secrets package.

Install the required packages:

bash
dotnet add package Azure.Security.KeyVault.Secrets
dotnet add package Azure.Identity

Retrieve a secret at runtime:

csharp
1using Azure.Identity;
2using Azure.Security.KeyVault.Secrets;
3
4var client = new SecretClient(
5    new Uri("https://my-vault.vault.azure.net/"),
6    new DefaultAzureCredential()
7);
8
9KeyVaultSecret secret = await client.GetSecretAsync("EncryptionKey");
10string encryptionKey = secret.Value;

DefaultAzureCredential automatically picks up credentials from environment variables, managed identity, Visual Studio, or the Azure CLI, so no credentials need to be hardcoded. In production, use a managed identity assigned to your App Service or VM so the application authenticates to Key Vault without any secrets at all.

Option 2: AWS Secrets Manager (For AWS Deployments)

If you are on AWS, Secrets Manager provides similar functionality:

csharp
1using Amazon.SecretsManager;
2using Amazon.SecretsManager.Model;
3
4var client = new AmazonSecretsManagerClient();
5var request = new GetSecretValueRequest
6{
7    SecretId = "prod/myapp/encryption-key"
8};
9
10var response = await client.GetSecretValueAsync(request);
11string encryptionKey = response.SecretString;

AWS Secrets Manager supports automatic key rotation, versioning, and fine-grained IAM access policies.

Option 3: Windows DPAPI (For Windows-Only Applications)

The Data Protection API (DPAPI) is built into Windows and encrypts data using credentials tied to the current user or machine. This is suitable for desktop applications or Windows services that do not run in the cloud.

csharp
1using System.Security.Cryptography;
2using System.Text;
3
4public class DpapiKeyStore
5{
6    private static readonly string KeyFilePath = @"C:\SecureKeys\app-key.dat";
7
8    public static void StoreKey(string key)
9    {
10        byte[] keyBytes = Encoding.UTF8.GetBytes(key);
11        byte[] encrypted = ProtectedData.Protect(
12            keyBytes,
13            null,
14            DataProtectionScope.CurrentUser
15        );
16        File.WriteAllBytes(KeyFilePath, encrypted);
17    }
18
19    public static string RetrieveKey()
20    {
21        byte[] encrypted = File.ReadAllBytes(KeyFilePath);
22        byte[] decrypted = ProtectedData.Unprotect(
23            encrypted,
24            null,
25            DataProtectionScope.CurrentUser
26        );
27        return Encoding.UTF8.GetString(decrypted);
28    }
29}

DataProtectionScope.CurrentUser means only the same Windows user can decrypt the data. DataProtectionScope.LocalMachine allows any user on the same machine to decrypt it, which is less secure but necessary for some service scenarios.

Option 4: ASP.NET Core Data Protection API

ASP.NET Core includes its own Data Protection system designed for protecting sensitive data like authentication tokens. You can also use it for custom key storage:

csharp
1using Microsoft.AspNetCore.DataProtection;
2
3public class SecureKeyStore
4{
5    private readonly IDataProtector _protector;
6
7    public SecureKeyStore(IDataProtectionProvider provider)
8    {
9        _protector = provider.CreateProtector("KeyStorage.v1");
10    }
11
12    public string Protect(string plaintext)
13    {
14        return _protector.Protect(plaintext);
15    }
16
17    public string Unprotect(string ciphertext)
18    {
19        return _protector.Unprotect(ciphertext);
20    }
21}

Register it in Program.cs:

csharp
builder.Services.AddDataProtection()
    .PersistKeysToFileSystem(new DirectoryInfo(@"/var/keys"))
    .ProtectKeysWithCertificate(certificate);

Option 5: Environment Variables (Minimum Viable Approach)

For simple deployments where a cloud KMS is not available, environment variables keep keys out of source code. This is the minimum acceptable approach:

csharp
1string encryptionKey = Environment.GetEnvironmentVariable("APP_ENCRYPTION_KEY")
2    ?? throw new InvalidOperationException(
3        "APP_ENCRYPTION_KEY environment variable is not set"
4    );

In ASP.NET Core, use the configuration system with user secrets during development:

bash
dotnet user-secrets set "Encryption:Key" "your-key-here"
csharp
string key = builder.Configuration["Encryption:Key"];

User secrets are stored outside the project directory in %APPDATA%\Microsoft\UserSecrets\ on Windows, so they never end up in version control.

Key Rotation Strategy

Regardless of which storage method you choose, implement key rotation. Old keys should remain available to decrypt existing data, while new data is encrypted with the current key:

csharp
1public class RotatingKeyProvider
2{
3    private readonly List<string> _keys;
4    public string CurrentKey => _keys[^1];
5
6    public RotatingKeyProvider(IConfiguration config)
7    {
8        _keys = config.GetSection("Encryption:Keys").Get<List<string>>();
9    }
10
11    public string Decrypt(string ciphertext)
12    {
13        foreach (var key in _keys.AsEnumerable().Reverse())
14        {
15            try { return DecryptWithKey(ciphertext, key); }
16            catch { continue; }
17        }
18        throw new CryptographicException("No valid key found for decryption");
19    }
20}

Common Pitfalls

Hardcoding keys in appsettings.json. This file is committed to source control by default. Even if you add it to .gitignore later, the key may already be in the Git history. Use user secrets for development and environment variables or a KMS for production.

Logging keys accidentally. Structured logging frameworks can capture entire configuration objects. Make sure your key values are never included in log output. Mark key properties with [JsonIgnore] or use a custom serializer that redacts sensitive fields.

Using the same key for all environments. Development, staging, and production should each have separate encryption keys. If a development key is compromised, production data should not be affected.

Not setting proper file permissions on key files. If you store encrypted keys on disk (DPAPI approach), ensure the file permissions restrict access to only the application's service account.

Summary

For cloud deployments, use Azure Key Vault or AWS Secrets Manager. These provide HSM-backed storage, access control, audit logging, and automatic rotation. For Windows desktop applications, DPAPI provides machine-level or user-level encryption without external dependencies. As a minimum baseline, use environment variables and ASP.NET Core user secrets to keep keys out of source code. Never hardcode keys in source files, configuration files committed to version control, or log output.


Course illustration
Course illustration

All Rights Reserved.