Key Retention
Tayra includes a background service that automatically enforces data retention policies by shredding encryption keys past their configured time-to-live (TTL). This helps you comply with GDPR Article 5(1)(e), which requires that personal data is kept no longer than necessary.
How It Works
The KeyRetentionBackgroundService runs as a .NET BackgroundService and periodically:
- Queries the key store for keys created before the earliest possible expiry cutoff.
- Evaluates each candidate key against the
IKeyRetentionPolicyto determine if it has expired. - Deletes expired keys, effectively crypto-shredding all data encrypted with those keys.
- Emits
KeyExpiredaudit events for each deleted key.
Setup
Register the key retention service with AddTayraKeyRetention:
// Register the key retention background service.
// It periodically scans for and deletes expired encryption keys.
var retentionServices = new ServiceCollection();
retentionServices.AddTayra(opts => opts.LicenseKey = licenseKey);
retentionServices.AddTayraKeyRetention(options =>
{
options.DefaultRetentionPeriod = TimeSpan.FromDays(365);
options.ScanInterval = TimeSpan.FromHours(1);
options.BatchSize = 100;
options.PrefixRetentionPeriods = new Dictionary<string, TimeSpan>
{
["temp-"] = TimeSpan.FromDays(30),
["session-"] = TimeSpan.FromHours(24),
};
});This registers:
IKeyRetentionPolicy(default implementation:DefaultKeyRetentionPolicy)KeyRetentionBackgroundServiceas a hosted service
INFO
The background service requires a host to run (e.g., ASP.NET Core, .NET Generic Host). It will not execute in a simple console application without a host.
Configuration Options
The KeyRetentionOptions class controls all aspects of the retention service:
// KeyRetentionOptions properties:
var retentionOptions = new KeyRetentionOptions
{
// Default retention period for keys that don't match any prefix rule.
// Null means indefinite retention (no auto-expiry).
DefaultRetentionPeriod = TimeSpan.FromDays(365),
// How often the background service scans for expired keys. Default: 1 hour.
ScanInterval = TimeSpan.FromHours(1),
// Maximum number of keys to delete per scan cycle. Default: 100.
BatchSize = 100,
// Prefix-specific retention periods.
// Keys whose IDs start with a matching prefix use the corresponding period.
PrefixRetentionPeriods = new Dictionary<string, TimeSpan>
{
["temp-"] = TimeSpan.FromDays(30), // Temporary data: 30 days
["session-"] = TimeSpan.FromHours(24), // Session data: 24 hours
["patient-"] = TimeSpan.FromDays(3650), // Medical data: 10 years
},
};
Console.WriteLine($"Default retention: {retentionOptions.DefaultRetentionPeriod}");
Console.WriteLine($"Scan interval: {retentionOptions.ScanInterval}");
Console.WriteLine($"Batch size: {retentionOptions.BatchSize}");Options Reference
| Property | Type | Default | Description |
|---|---|---|---|
DefaultRetentionPeriod | TimeSpan? | null | Default TTL for keys that don't match any prefix rule. Null means indefinite retention. |
ScanInterval | TimeSpan | 1 hour | How often the background service scans for expired keys. |
BatchSize | int | 100 | Maximum number of keys to delete per scan cycle. |
PrefixRetentionPeriods | Dictionary<string, TimeSpan> | Empty | Prefix-specific TTLs. Keys matching a prefix use that TTL instead of the default. |
Prefix-Specific TTLs
Different data categories often have different retention requirements. Use PrefixRetentionPeriods to set TTLs per key prefix:
options.PrefixRetentionPeriods = new Dictionary<string, TimeSpan>
{
["temp-"] = TimeSpan.FromDays(30), // Temporary data: 30 days
["session-"] = TimeSpan.FromHours(24), // Session tokens: 24 hours
["patient-"] = TimeSpan.FromDays(3650), // Medical records: 10 years
["cust-"] = TimeSpan.FromDays(730), // Customer data: 2 years
};When multiple prefixes match a key ID, the longest matching prefix wins. For example, a key ID of "cust-premium-001" would match "cust-" but also "cust-premium-" if both are configured — the longer prefix takes precedence.
WARNING
Key retention is irreversible. Once a key is deleted by the retention service, all data encrypted with that key becomes permanently unreadable. There is no undo. Test your retention configuration thoroughly in a staging environment before deploying to production.
Key Store Requirements
The retention service requires the key store to support the GetKeysCreatedBeforeAsync method, which queries keys by creation timestamp. Not all key stores support this:
| Key Store | Supported |
|---|---|
| PostgreSQL | Yes |
| In-Memory | For testing only |
| HashiCorp Vault | No (metadata limitations) |
| Azure Key Vault | No (metadata limitations) |
| AWS KMS | No (metadata limitations) |
If the key store does not support creation-time queries, the retention service will log a warning and stop gracefully.
Custom Retention Policy
You can implement IKeyRetentionPolicy to create custom retention logic beyond prefix matching:
public class CustomRetentionPolicy : IKeyRetentionPolicy
{
public TimeSpan? GetRetentionPeriod(string keyId)
{
// Custom logic based on key ID patterns, external rules, etc.
if (keyId.Contains(":session:"))
return TimeSpan.FromHours(1);
return TimeSpan.FromDays(365); // Default: 1 year
}
}Register it before calling AddTayraKeyRetention to replace the default:
services.AddTayra(opts => opts.LicenseKey = licenseKey)
.UsePostgreSqlKeyStore(connectionString);
services.AddSingleton<IKeyRetentionPolicy, CustomRetentionPolicy>();
services.AddTayraKeyRetention();See Also
- Crypto-Shredding — Manual key deletion for erasure requests
- Key Rotation — Rotate keys before they expire
- Key Stores — Key store configuration and capabilities
