Skip to content

Key Stores

Tayra uses a pluggable key store architecture to persist encryption keys. Every key store implements the IKeyStore interface, giving you freedom to choose the backend that best fits your infrastructure.

Available Providers

ProviderPackageUse CasePersistenceEncryption at Rest
In-MemoryTayra.Core (built-in)Unit tests, throwaway demosNoNo
SQLiteTayra.KeyStore.SqliteLocal development (zero-config)YesNo (raw key bytes)
PostgreSQLTayra.KeyStore.PostgreSqlLocal dev or self-managed productionYesNo (requires hardening)
HashiCorp VaultTayra.KeyStore.VaultProductionYesYes (seal)
Azure Key VaultTayra.KeyStore.AzureKeyVaultProductionYesYes (HSM-backed)
AWS Parameter StoreTayra.KeyStore.AwsParameterStoreProductionYesYes (KMS)

Choosing a Key Store

EnvironmentRecommended ProviderWhy
Unit/integration testsIn-MemoryZero configuration, no external dependencies
Local developmentSQLiteZero-config file-based persistence, no server needed
Local development (with PG)PostgreSQLPersistent keys, useful if you already run PostgreSQL locally
Self-managed productionPostgreSQL (with hardening)For teams that don't use cloud secrets managers
ProductionVault, Azure Key Vault, or AWS KMSHSM-backed encryption, access auditing, key wrapping

SQLite — Development Only

The SQLite key store stores raw key bytes without envelope encryption. It is for local development only.

PostgreSQL — Requires Hardening for Production

The PostgreSQL key store stores raw key bytes in a database table. It can be used in production only with proper hardening (TDE, TLS, pgAudit, least-privilege access). See the Production Security Guide for the full checklist. Without hardening, use it for development only.

The IKeyStore Interface

All key stores implement IKeyStore, which defines these operations:

csharp
public interface IKeyStore
{
    Task StoreAsync(string keyId, byte[] key, CancellationToken ct = default);
    Task<byte[]?> GetAsync(string keyId, CancellationToken ct = default);
    Task DeleteAsync(string keyId, CancellationToken ct = default);
    Task<bool> ExistsAsync(string keyId, CancellationToken ct = default);
    Task DeleteByPrefixAsync(string prefix, CancellationToken ct = default);
    Task<IReadOnlyList<string>> ListKeyIdsAsync(string prefix, CancellationToken ct = default);
    Task<IReadOnlyList<KeyInfo>> GetKeysCreatedBeforeAsync(
        DateTimeOffset cutoff, int limit = 100, CancellationToken ct = default);
}
  • StoreAsync -- Stores a key. Implementations should be idempotent (no-op if the key already exists).
  • GetAsync -- Retrieves a key by ID. Returns null if the key has been deleted (crypto-shredded).
  • DeleteAsync -- Deletes a key. This is the crypto-shredding operation.
  • ExistsAsync -- Checks whether a key exists without retrieving its value.
  • DeleteByPrefixAsync -- Bulk crypto-shredding by key prefix (e.g., delete all keys for a data subject).
  • ListKeyIdsAsync -- Lists key IDs matching a prefix. Used for key rotation discovery.
  • GetKeysCreatedBeforeAsync -- Finds keys older than a cutoff time. Used for data retention policies.

INFO

ListKeyIdsAsync and GetKeysCreatedBeforeAsync have default implementations that throw NotSupportedException. Not all providers support these operations.

Registration Pattern

Every key store follows the same chained registration pattern. Call services.AddTayra() to register core services and chain the provider-specific Use* method:

cs
var services = new ServiceCollection();
services.AddTayra(opts => opts.LicenseKey = licenseKey);
anchor

You can also build a custom key store if none of the built-in providers fit your needs.

See Also