Enterprise-grade key management library with versioned encryption format (v2.3.0) and asymmetric key support (RSA, ECDSA). Features version-aware auto-decryption, automatic key rotation, multi-platform storage (Windows DPAPI, Linux AES-256-GCM, Azure Key Vault), distributed caching, health monitoring, and comprehensive audit logging. Fully tested with 117/117 tests passing (100% success rate).
$ dotnet add package Feedemy.KeyManagementA production-ready, high-performance key management library for .NET 9.0 applications with built-in caching, versioning, automatic rotation, and comprehensive health monitoring.
Feedemy.KeyManagement provides a secure, scalable solution for managing cryptographic keys and sensitive configuration values in enterprise .NET applications. It combines platform-specific secure storage with advanced features like automatic key rotation, version management, distributed caching, and full audit trails.
dotnet test tests/Feedemy.KeyManagement.IntegrationTests run (2025‑02‑14) completed in ~64s with 411 passing and 1 known failure (see below).EndToEndTests.EncryptedKeyWorkflow_WithMasterKeyEncryption_ShouldHandleEncryptiontests/Feedemy.KeyManagement.IntegrationTests/EndToEndTests.cs:223IKeyEncryptionOrchestratorRetrieveEncryptedKeyAsyncMonitor key management system health using ASP.NET Core health checks:
// In Program.cs
builder.Services.AddHealthChecks()
.AddKeyManagementHealthCheck(
name: "key_management",
failureStatus: HealthStatus.Unhealthy,
tags: new[] { "security", "keys" });
app.MapHealthChecks("/health");Health Status Levels:
Healthy: All keys operational, storage availableDegraded: Warning keys detected or storage issuesUnhealthy: Critical keys or system failureResponse Example:
{
"status": "Healthy",
"totalKeys": 15,
"healthyKeys": 13,
"warningKeys": 2,
"criticalKeys": 0,
"storageProvider": "Windows DPAPI",
"storageAvailable": true
}Enable distributed cache invalidation for multi-server deployments:
// In Program.cs
builder.Services.AddSingleton<IConnectionMultiplexer>(
ConnectionMultiplexer.Connect("localhost:6379"));
builder.Services.AddKeyManagement(options =>
{
options.Cache.ProviderType = CacheProviderType.Redis;
options.Cache.RedisConnectionString = "localhost:6379";
options.Cache.EnableDistributedInvalidation = true;
options.Cache.InvalidationChannel = "feedemy:keymanagement:invalidation";
});How it Works:
The library automatically selects the best storage provider for your platform:
%LOCALAPPDATA%\Feedemy\Keys~/.feedemy-keys//etc/machine-idConfiguration:
builder.Services.AddKeyManagement(options =>
{
// Auto-detect best provider for current OS
options.Storage.ProviderType = StorageProviderType.Auto;
// OR explicitly choose:
// options.Storage.ProviderType = StorageProviderType.WindowsCredentialManager;
// options.Storage.ProviderType = StorageProviderType.LinuxKeyring;
// options.Storage.ProviderType = StorageProviderType.AzureKeyVault;
// options.Storage.AzureKeyVaultUrl = "https://your-vault.vault.azure.net/";
});v1.0 Security Enhancements:
EncryptionKeyBenchmark Results (BenchmarkDotNet):
| Method | Mean | Allocated |
|------------------------|----------|-----------|
| RetrieveKey_Cached | 0.85 μs | 96 B |
| GetCurrentVersion | 0.62 μs | 48 B |
| GetMetadata | 1.20 μs | 128 B |
Run benchmarks:
cd benchmarks/Feedemy.KeyManagement.Benchmarks
dotnet run -c ReleaseIf migrating from FeedemyBackend's built-in key management:
Install Package:
dotnet add package Feedemy.KeyManagement
Update DI Registration:
// Old (FeedemyBackend):
services.AddKeyManagementServices();
// New (v1.0):
services.AddKeyManagement(options => {
options.Persistence.ProviderType = PersistenceProviderType.EntityFramework;
});
No Code Changes: Interfaces remain compatible (IKeyManagementService, IKeyManagementAdminService)
Data Migration: Keys automatically migrated on first retrieval
See Migration Guide for detailed step-by-step instructions.
Install the core package:
dotnet add package Feedemy.KeyManagementInstall provider packages based on your needs:
# For Entity Framework Core persistence
dotnet add package Feedemy.KeyManagement.Providers.EntityFramework
# For Windows DPAPI storage
dotnet add package Feedemy.KeyManagement.Providers.Windows
# For Linux KeyRing storage
dotnet add package Feedemy.KeyManagement.Providers.Linux
# For Azure Key Vault storage
dotnet add package Feedemy.KeyManagement.Providers.Azureusing Feedemy.KeyManagement.Extensions;
using Microsoft.Extensions.DependencyInjection;
// Program.cs or Startup.cs
services.AddKeyManagement(options =>
{
options.EnableAutoRotation = true;
options.RotationCheckInterval = TimeSpan.FromHours(6);
options.DefaultRotationDays = 90;
})
.UseInMemoryStorage() // Development only
.UseMemoryCache() // Development only
.UseInMemoryPersistence() // Development only
.AddConsoleNotifications();services.AddKeyManagement(options =>
{
options.EnableAutoRotation = true;
options.RotationCheckInterval = TimeSpan.FromHours(6);
options.DefaultRotationDays = 90;
})
.UseWindowsStorage() // Or .UseLinuxStorage() or .UseAzureKeyVault()
.UseRedisCache(Configuration.GetConnectionString("Redis"))
.UseEntityFramework() // Uses existing DbContext
.AddConsoleNotifications();public class MyService
{
private readonly IKeyManagementService _keyManagement;
private readonly IKeyManagementAdminService _adminService;
public MyService(
IKeyManagementService keyManagement,
IKeyManagementAdminService adminService)
{
_keyManagement = keyManagement;
_adminService = adminService;
}
// Create a new key (admin operation)
public async Task CreateApiKeyAsync()
{
var result = await _adminService.CreateKeyAsync(new CreateKeyRequest
{
KeyName = "ApiKey",
Description = "External API authentication key",
RotationIntervalDays = 90,
AutoRotationEnabled = true,
MaxVersionsToRetain = 5,
SourceType = KeySourceType.Generated,
Category = KeyCategory.ApiKey,
CreatedBy = "AdminUser"
});
if (result.Success)
{
Console.WriteLine($"Key created: {result.Data.KeyName} v{result.Data.CurrentVersion}");
}
}
// Retrieve a key (read operation)
public async Task<byte[]> GetApiKeyAsync()
{
// Cache-first retrieval - extremely fast after first access
var key = await _keyManagement.RetrieveKeyAsync("ApiKey");
return key; // Returns null if key doesn't exist or is deactivated
}
// Get key metadata
public async Task<KeyMetadata> GetKeyInfoAsync()
{
var metadata = await _keyManagement.GetKeyMetadataAsync("ApiKey");
Console.WriteLine($"Current Version: {metadata.CurrentVersion}");
Console.WriteLine($"Next Rotation: {metadata.NextRotationDue}");
Console.WriteLine($"Auto Rotation: {metadata.AutoRotationEnabled}");
return metadata;
}
// Check key health
public async Task MonitorKeyHealthAsync()
{
var health = await _keyManagement.GetKeyHealthAsync("ApiKey");
Console.WriteLine($"Health Status: {health.Status}");
Console.WriteLine($"Days Until Rotation: {health.DaysUntilRotation}");
if (health.Status == KeyHealthStatus.NeedsRotation)
{
// Trigger alert or automatic rotation
}
}
}Configure the core behavior of the key management system:
services.AddKeyManagement(options =>
{
// Enable automatic key rotation background service
options.EnableAutoRotation = true;
// How often to check for keys needing rotation (default: 6 hours)
options.RotationCheckInterval = TimeSpan.FromHours(6);
// Default rotation interval for new keys (default: 90 days)
options.DefaultRotationDays = 90;
// Storage provider configuration
options.Storage.ProviderType = StorageProviderType.Auto; // Auto-detect based on OS
// Cache provider configuration
options.Cache.ProviderType = CacheProviderType.Redis;
options.Cache.RedisConnectionString = "localhost:6379";
// Persistence provider configuration
options.Persistence.ProviderType = PersistenceProviderType.EntityFramework;
// Notification configuration
options.Notifications.EnableNotifications = true;
// Initialization configuration
options.Initialization.EnableAutoInitialization = true;
options.Initialization.ExternalKeysJsonPath = "keys.json"; // Optional
});Configure advanced caching behavior:
services.Configure<KeyManagementCacheOptions>(options =>
{
// Enable distributed cache invalidation (required for multi-server)
options.EnableDistributedInvalidation = true;
// Pub/sub channel for cache invalidation messages
options.InvalidationChannel = "feedemy:keymanagement:invalidation";
// Timeout for pub/sub operations
options.PubSubTimeoutSeconds = 5;
// Retry configuration
options.MaxInvalidationRetries = 3;
options.RetryDelayMilliseconds = 100;
// Enable cache warming (pre-load after invalidation)
options.EnableCacheWarming = true;
options.MaxConcurrentWarmingTasks = 5;
});All configuration can be overridden via environment variables:
# Core settings
KeyManagement__EnableAutoRotation=true
KeyManagement__RotationCheckInterval=06:00:00
KeyManagement__DefaultRotationDays=90
# Storage provider
KeyManagement__Storage__ProviderType=WindowsCredentialManager
# Cache provider
KeyManagement__Cache__ProviderType=Redis
KeyManagement__Cache__RedisConnectionString=localhost:6379
# Persistence provider
KeyManagement__Persistence__ProviderType=EntityFramework
# Cache options
KeyManagementCache__EnableDistributedInvalidation=true
KeyManagementCache__InvalidationChannel=feedemy:keymanagement:invalidation
KeyManagementCache__EnableCacheWarming=trueStorage providers securely store the actual key content:
.UseInMemoryStorage()Stores keys in memory. Not suitable for production - keys are lost on restart.
.UseWindowsStorage()Uses Windows Data Protection API (DPAPI) via Credential Manager. Requires Windows OS.
.UseLinuxStorage()Uses Linux Secret Service API (KeyRing). Requires Linux OS with GNOME Keyring or KWallet.
.UseAzureKeyVault(options =>
{
options.VaultUri = "https://your-vault.vault.azure.net/";
options.TenantId = "your-tenant-id";
options.ClientId = "your-client-id";
options.ClientSecret = "your-client-secret";
})Uses Azure Key Vault for cloud-based secure storage. Best for cloud deployments.
.UseAutoStorage() // Automatically selects Windows/Linux based on OSCache providers accelerate key retrieval with multi-layer caching:
.UseMemoryCache()In-memory caching using IMemoryCache. Fast but not distributed.
.UseRedisCache("localhost:6379")Distributed caching with Redis. Supports pub/sub for cache invalidation across multiple servers.
.UseNullCache()No caching - always reads from storage. Useful for testing cache-miss scenarios.
Persistence providers store key metadata, versions, and audit logs:
.UseInMemoryPersistence()In-memory storage. Lost on restart. Development only.
.UseEntityFramework()Stores data in SQL Server (or any EF Core provider) using your existing DbContext.
Migration Setup:
# Add migration
dotnet ef migrations add AddKeyManagement
# Apply migration
dotnet ef database updateRequired entities automatically added:
KeyMetadata - Key configuration and current versionKeyVersion - Version history with content hashesKeyAuditLog - Full audit trail of admin operationsThe retrieval system uses a three-tier cache strategy for maximum performance:
// Fast path: Cache hit (< 1ms)
var key = await _keyManagement.RetrieveKeyAsync("MyKey");
// Cache layers checked in order:
// 1. Content cache (key content)
// 2. Metadata cache (key metadata)
// 3. Version cache (version info)
// 4. Storage layer (if cache miss)Performance characteristics:
Retrieve by version:
// Get specific version
var key = await _keyManagement.RetrieveKeyByVersionAsync("MyKey", version: 3);Retrieve using enum (type-safe):
// Define in your code
public enum KeyName
{
EncryptionKey,
ApiKey,
JwtSigningKey
}
// Retrieve
var key = await _keyManagement.RetrieveKeyAsync(KeyName.EncryptionKey);// Update key content (creates new version)
var result = await _adminService.UpdateKeyContentAsync(
keyName: "ApiKey",
newContent: newKeyBytes,
reason: "Scheduled rotation",
updatedBy: "RotationService"
);
Console.WriteLine($"Rotated to version {result.Data.CurrentVersion}");The background service automatically rotates keys based on configured intervals:
// Enable in configuration
services.AddKeyManagement(options =>
{
options.EnableAutoRotation = true;
options.RotationCheckInterval = TimeSpan.FromHours(6); // Check every 6 hours
});
// Per-key configuration
await _adminService.CreateKeyAsync(new CreateKeyRequest
{
KeyName = "ApiKey",
RotationIntervalDays = 90, // Rotate every 90 days
AutoRotationEnabled = true, // Enable auto-rotation for this key
// ...
});Rotation behavior:
RotationCheckIntervalAutoRotationEnabled = trueNextRotationDue <= Nowvar status = await _adminService.GetRotationServiceStatusAsync();
Console.WriteLine($"Service Running: {status.IsRunning}");
Console.WriteLine($"Last Check: {status.LastCheckTime}");
Console.WriteLine($"Next Check: {status.NextCheckTime}");
Console.WriteLine($"Keys Pending Rotation: {status.KeysPendingRotation.Count}");
foreach (var key in status.KeysPendingRotation)
{
Console.WriteLine($" - {key.KeyName}: Due {key.NextRotationDue}");
}// Get all versions
var versions = await _keyManagement.GetKeyVersionsAsync("ApiKey");
foreach (var version in versions)
{
Console.WriteLine($"Version {version.Version}: Created {version.CreatedAt}");
Console.WriteLine($" Status: {version.Status}");
Console.WriteLine($" Active: {version.IsActive}");
}
// Get current version number
var currentVersion = await _keyManagement.GetCurrentVersionAsync("ApiKey");Rollback to a previous version when needed:
// Rollback to version 3
var result = await _adminService.RollbackToVersionAsync(
keyName: "ApiKey",
targetVersion: 3,
reason: "Revert to stable version after incident",
performedBy: "AdminUser"
);
if (result.Success)
{
Console.WriteLine($"Rolled back from v{result.Data.FromVersion} to v{result.Data.ToVersion}");
Console.WriteLine($"Deactivated versions: {string.Join(", ", result.Data.DeactivatedVersions)}");
}Rollback behavior:
// Deactivate old version (cannot deactivate current version)
await _adminService.DeactivateVersionAsync(
keyName: "ApiKey",
version: 1,
reason: "Old version no longer needed",
performedBy: "AdminUser"
);
// Reactivate version if needed
await _adminService.ReactivateVersionAsync(
keyName: "ApiKey",
version: 1,
performedBy: "AdminUser"
);var health = await _keyManagement.GetKeyHealthAsync("ApiKey");
switch (health.Status)
{
case KeyHealthStatus.Healthy:
// Key is fresh (> 7 days from rotation)
break;
case KeyHealthStatus.Warning:
// Key approaching rotation (< 7 days)
Console.WriteLine($"Warning: Rotation due in {health.DaysUntilRotation} days");
break;
case KeyHealthStatus.NeedsRotation:
// Key is overdue for rotation
Console.WriteLine("Critical: Key needs immediate rotation");
break;
case KeyHealthStatus.Inactive:
// Key is deactivated
break;
}var systemHealth = await _keyManagement.GetSystemHealthAsync();
Console.WriteLine($"Overall Status: {systemHealth.OverallStatus}");
Console.WriteLine($"Total Keys: {systemHealth.TotalKeys}");
Console.WriteLine($"Healthy: {systemHealth.HealthyKeys}");
Console.WriteLine($"Warning: {systemHealth.WarningKeys}");
Console.WriteLine($"Critical: {systemHealth.CriticalKeys}");
Console.WriteLine($"Inactive: {systemHealth.InactiveKeys}");
foreach (var keyHealth in systemHealth.KeyHealthDetails)
{
if (keyHealth.Status != KeyHealthStatus.Healthy)
{
Console.WriteLine($" - {keyHealth.KeyName}: {keyHealth.Status} (v{keyHealth.CurrentVersion})");
}
}After cache invalidation (rotation, rollback, update), critical keys are automatically preloaded:
// Manually trigger cache warming for specific keys
await _keyManagement.WarmCacheAsync(new[] { "ApiKey", "JwtSigningKey" });
// Configure warming behavior
services.Configure<KeyManagementCacheOptions>(options =>
{
options.EnableCacheWarming = true;
options.MaxConcurrentWarmingTasks = 5; // Warm up to 5 keys concurrently
});Warming strategy:
var result = await _adminService.CreateKeyAsync(new CreateKeyRequest
{
KeyName = "JwtSigningKey",
Description = "JWT token signing key for authentication",
// Auto-generate content or provide custom content
AutoGenerate = true, // Auto-generate random bytes
KeySize = 64, // Size in bytes (if auto-generating)
// Content = customBytes, // Or provide custom content
// Rotation configuration
RotationIntervalDays = 90,
AutoRotationEnabled = true,
// Version retention
MaxVersionsToRetain = 5, // Keep last 5 versions
// Master key encryption (optional)
RequiresMasterKeyEncryption = true, // Encrypt with EncryptionKey (ENV)
// Metadata
Category = KeyCategory.Signing,
SourceType = KeySourceType.Generated,
Tags = new[] { "jwt", "auth", "production" },
// Audit
CreatedBy = "AdminUser"
});// Update rotation policy and retention without changing content
var result = await _adminService.UpdateKeyConfigurationAsync(
keyName: "ApiKey",
request: new UpdateKeyConfigRequest
{
RotationIntervalDays = 60, // Change from 90 to 60 days
AutoRotationEnabled = true,
MaxVersionsToRetain = 10, // Increase retention
UpdatedBy = "AdminUser"
}
);// Deactivate key (prevents further use)
await _adminService.DeactivateKeyAsync(
keyName: "OldApiKey",
reason: "Migrated to new authentication system",
performedBy: "AdminUser"
);
// Reactivate key
await _adminService.ReactivateKeyAsync(
keyName: "OldApiKey",
performedBy: "AdminUser"
);// Get audit history
var logs = await _adminService.GetAuditLogsAsync(
keyName: "ApiKey",
fromDate: DateTime.UtcNow.AddMonths(-1)
);
foreach (var log in logs)
{
Console.WriteLine($"{log.PerformedAt}: {log.Operation} by {log.PerformedBy}");
Console.WriteLine($" Reason: {log.Reason}");
Console.WriteLine($" Details: {log.Details}");
}Audit operations tracked:
See Architecture Documentation for detailed architecture diagrams, component descriptions, and data flow.
┌─────────────────────────────────────────────────────────────┐
│ Application Layer │
│ Controllers / Services / Background Jobs │
└──────────────────┬──────────────────┬──────────────────────┘
│ │
┌────────────▼──────────┐ ┌───▼────────────────────┐
│ IKeyManagementService │ │ IKeyManagementAdmin │
│ (Read Operations) │ │ Service │
│ - RetrieveKey │ │ (Write Operations) │
│ - GetMetadata │ │ - CreateKey │
│ - GetHealth │ │ - UpdateKey │
└────────────┬──────────┘ │ - RollbackVersion │
│ │ - DeactivateKey │
│ └───┬────────────────────┘
│ │
┌───────────▼─────────────────▼──────────────────────┐
│ Domain Services │
│ - KeyHealthCalculator │
│ - KeyValidationService │
│ - KeyVersionService │
│ - KeyEncryptionOrchestrator │
│ - KeyRotationService │
└───────────┬────────────────────────────────────────┘
│
┌───────────▼────────────────────────────────────────┐
│ Infrastructure Services │
├────────────────────┬──────────────┬───────────────┤
│ Cache Layer │ Storage │ Persistence │
│ - IKeyCacheService│ - IKeyStorage│ - IKeyMetadata│
│ - Multi-layer │ - Platform- │ Repository │
│ - Invalidation │ specific │ - IKeyVersion │
│ - Warming │ providers │ Repository │
│ - Pub/sub │ │ - IKeyAudit │
│ │ │ Repository │
└────────────────────┴──────────────┴───────────────┘
The system uses repository pattern for data access:
Implementations:
Request Flow:
1. Content Cache → "key:ApiKey:content:v5" (1-hour TTL)
2. Metadata Cache → "key:ApiKey:metadata" (5-min TTL)
3. Version Cache → "key:ApiKey:versions" (5-min TTL)
4. Storage Layer → Windows DPAPI / Azure KV
5. Database → SQL Server (metadata only)
For multi-server deployments:
// Server 1: Updates key
await _adminService.UpdateKeyContentAsync("ApiKey", newContent, ...);
// Internally publishes to Redis pub/sub:
// Channel: "feedemy:keymanagement:invalidation"
// Message: {"KeyName": "ApiKey", "Operation": "Update"}
// Server 2, 3, 4, ...: Automatically invalidate local cache
// All servers receive pub/sub message and clear cacheCalculates health status based on rotation schedule:
Validates all admin operations:
Manages version lifecycle:
Handles master key encryption (envelope encryption):
Orchestrates rotation process:
cd tests/Feedemy.KeyManagement.Tests
dotnet test149 unit tests covering:
cd tests/Feedemy.KeyManagement.IntegrationTests
dotnet test30 integration tests covering:
cd samples/Feedemy.KeyManagement.Sample
dotnet runDemonstrates:
If you're migrating from the embedded KeyManagement in FeedemyBackend, follow these steps:
Remove old embedded code and install packages:
# Remove old KeyManagement folder from project
rm -rf Services/KeyManagement
# Install new packages
dotnet add package Feedemy.KeyManagement
dotnet add package Feedemy.KeyManagement.Providers.EntityFramework
dotnet add package Feedemy.KeyManagement.Providers.Windows # Or Linux/AzureBefore (FeedemyBackend):
services.AddScoped<IKeyManagementService, KeyManagementService>();
services.AddScoped<IKeyRepository, KeyRepository>();
// ... manual registrationAfter (Feedemy.KeyManagement):
services.AddKeyManagement(options =>
{
options.EnableAutoRotation = true;
options.RotationCheckInterval = TimeSpan.FromHours(6);
})
.UseWindowsStorage()
.UseRedisCache(Configuration.GetConnectionString("Redis"))
.UseEntityFramework();Add to your ApplicationDbContext:
using Feedemy.KeyManagement.Persistence.EntityFramework;
public class ApplicationDbContext : DbContext
{
// ... existing DbSets
// Add KeyManagement entities
public DbSet<KeyMetadata> KeyMetadata { get; set; }
public DbSet<KeyVersion> KeyVersions { get; set; }
public DbSet<KeyAuditLog> KeyAuditLogs { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Apply KeyManagement configurations
modelBuilder.ApplyConfigurationsFromAssembly(
typeof(KeyManagementDbContext).Assembly);
}
}dotnet ef migrations add AddKeyManagement
dotnet ef database updateKey retrieval:
// Before
var key = await _keyRepository.GetKeyAsync("EncryptionKey");
// After
var key = await _keyManagement.RetrieveKeyAsync("EncryptionKey");Admin operations:
// Before
await _keyRepository.CreateKeyAsync(new Key { ... });
// After
await _adminService.CreateKeyAsync(new CreateKeyRequest { ... });If you need backward compatibility during migration:
public class KeyManagementAdapter : IKeyRepository // Old interface
{
private readonly IKeyManagementService _keyManagement;
private readonly IKeyManagementAdminService _adminService;
public async Task<byte[]> GetKeyAsync(string keyName)
{
return await _keyManagement.RetrieveKeyAsync(keyName);
}
public async Task CreateKeyAsync(Key key)
{
await _adminService.CreateKeyAsync(new CreateKeyRequest
{
KeyName = key.Name,
Content = key.Content,
// ... map properties
});
}
// ... implement other methods
}RequiresMasterKeyEncryption)# Clone repository
git clone https://github.com/feedemy/keymanagement.git
cd keymanagement
# Restore packages
dotnet restore
# Build solution
dotnet build
# Run all tests
dotnet testmaindotnet testMIT License
Copyright (c) 2025 Feedemy
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Feedemy.KeyManagement v2.0 adds RSA and ECDSA asymmetric key support for digital signatures, JWT signing, and public key encryption.
var createResult = await keyManagementAdmin.CreateKeyAsync(new CreateKeyRequest
{
KeyName = "JwtSigningKey",
KeyType = KeyType.AsymmetricRSA,
RsaKeySize = RsaKeySize.Rsa2048,
Category = KeyCategory.Signing,
Description = "RSA key for JWT RS256 tokens",
AutoRotationEnabled = true,
RotationIntervalDays = 90,
CreatedBy = "System"
});var createResult = await keyManagementAdmin.CreateKeyAsync(new CreateKeyRequest
{
KeyName = "DocumentSigningKey",
KeyType = KeyType.AsymmetricECDSA,
EcdsaCurve = EcdsaCurve.P256,
Category = KeyCategory.Signing,
Description = "ECDSA key for document signatures",
CreatedBy = "Admin"
});Inject IAsymmetricKeyOperations service:
public class JwtService
{
private readonly IAsymmetricKeyOperations _asymmetricOps;
public JwtService(IAsymmetricKeyOperations asymmetricOps)
{
_asymmetricOps = asymmetricOps;
}
public async Task<string> CreateJwtToken(Dictionary<string, object> claims)
{
var payload = JsonSerializer.SerializeToUtf8Bytes(claims);
// Sign with RS256
var signature = await _asymmetricOps.SignAsync(
"JwtSigningKey",
payload,
HashAlgorithmName.SHA256);
return EncodeJwt(payload, signature);
}
public async Task<bool> VerifyJwtToken(string token)
{
var (payload, signature) = DecodeJwt(token);
// Verify using cached public key (very fast - ~0.15ms)
return await _asymmetricOps.VerifyAsync(
"JwtSigningKey",
payload,
signature,
HashAlgorithmName.SHA256);
}
public async Task<string> GetPublicKeyForClients()
{
// Export public key in PEM format (safe to share)
return await _asymmetricOps.GetPublicKeyPemAsync("JwtSigningKey");
}
}| Algorithm | Key Sizes | Use Case | Performance |
|---|---|---|---|
| RSA | 2048, 3072, 4096-bit | Signing, Encryption | Sign: ~1.2ms, Verify: ~0.15ms |
| ECDSA | P-256, P-384, P-521 | Signing (compact) | Sign: ~0.35ms, Verify: ~0.12ms |
Asymmetric keys rotate automatically:
// Auto-rotation (background service)
services.AddKeyManagement(options =>
{
options.EnableAutoRotation = true;
options.DefaultRotationDays = 90;
});
// Manual rotation
await rotationService.RotateKeyAsync(
"JwtSigningKey",
"Scheduled rotation",
"Admin");Note: After rotation, old signatures remain verifiable if you keep old key versions. Use version-aware verification for backward compatibility.
All storage providers support asymmetric keys:
Private keys are always encrypted at rest using master key encryption.