Crypto Engine
The crypto engine manages the key lifecycle - creation, retrieval, caching, rotation, and deletion. Users interact with these operations through ITayra; the crypto engine is an internal implementation detail.
User-Facing Operations on ITayra
| ITayra Method | What Happens Internally |
|---|---|
EncryptAsync<T>() | Gets or creates a key via the engine, then encrypts |
DecryptAsync<T>() | Retrieves a key (returns replacements if shredded) |
ShredAsync(subjectId) | Deletes the subject's base key plus all rotated versions and group keys - data becomes permanently unreadable |
ShredByPrefixAsync(prefix) | Bulk deletes all matching keys |
RotateKeyAsync(keyIdBase) | Creates a new versioned key, preserves the old one |
ReEncryptAsync<T>() | Decrypts with old key, re-encrypts with new key |
KeyExistsAsync(keyId) | Checks if a key is still active |
ListKeysAsync(prefix) | Lists all key IDs matching a prefix |
How It Works
Internal implementation - provided for clarity
ICryptoEngine Interface
internal interface ICryptoEngine
{
Task<byte[]> GetOrCreateKeyAsync(string keyId, CancellationToken ct = default);
Task<byte[]?> GetKeyAsync(string keyId, CancellationToken ct = default);
Task DeleteKeyAsync(string keyId, CancellationToken ct = default);
Task<bool> KeyExistsAsync(string keyId, CancellationToken ct = default);
Task DeleteAllKeysAsync(string subjectPrefix, CancellationToken ct = default);
Task<string> RotateKeyAsync(string keyIdBase, CancellationToken ct = default);
}The built-in DefaultCryptoEngine wraps an IKeyStore with an in-memory MemoryCache.
Key Retrieval Flow
GetOrCreateKeyAsync("cust-abc123")
│
├─ Check MemoryCache
│ ├─ HIT → return cached key
│ └─ MISS ↓
│
├─ Acquire per-key-id lock (serializes in-process creation)
│
├─ Call IKeyStore.GetAsync("cust-abc123")
│ ├─ Key exists → cache it, return
│ └─ Key not found ↓
│
├─ Generate new AES key (RandomNumberGenerator)
├─ Call IKeyStore.StoreAsync("cust-abc123", newKey)
├─ Read back: IKeyStore.GetAsync("cust-abc123")
│ └─ StoreAsync is first-writer-wins, so the store's bytes
│ are authoritative (a concurrent creator may have won)
├─ Cache the stored key
└─ Return the stored keyThe miss path is serialized per key ID within the process, and the post-store read-back covers cross-process races: if another instance stored the key first, the engine uses that key rather than the one it generated locally. This guarantees data is never encrypted with a key that was not actually persisted. The same store-then-read-back pattern is applied during key rotation and by the blind-index HMAC key provider.
Decryption Flow
GetKeyAsync("cust-abc123")
│
├─ Check MemoryCache
│ ├─ HIT → return cached key
│ └─ MISS ↓
│
├─ Call IKeyStore.GetAsync("cust-abc123")
│ ├─ Key exists → cache it, return
│ └─ Key not found → return null (key was shredded)
│
└─ null triggers replacement value logicCache Behavior
- Cache duration is controlled by
TayraOptions.KeyCacheDuration(default: 5 minutes). ShredAsyncevicts the subject's keys from the local cache immediately, then deletes them from the key store. It performs two deletes: an exact-match delete of the base key, followed by a prefix delete of{subjectId}:that removes rotated key versions and group keys.ShredByPrefixAsyncdelegates to the engine'sDeleteAllKeysAsync, which lists matching key IDs (where the store supportsListKeyIdsAsync) and evicts them from the cache before callingIKeyStore.DeleteByPrefixAsync. If the store cannot list keys, cached copies expire naturally.
Key Rotation
RotateKeyAsync creates a new versioned key:
- Discovers existing versioned keys via
IKeyStore.ListKeyIdsAsync. - Determines the current maximum version number.
- Generates a new key with the next version (e.g.,
cust-abc123:v2). - Stores and caches the new key.
- Returns a
KeyRotationResultwith old and new key IDs.
The old key is preserved so that existing data can still be decrypted. Call ReEncryptAsync<T>() to migrate data to the new key.
Telemetry
The engine emits:
- OpenTelemetry activities via
TayraActivitySourcefor each operation. - Metrics via
TayraMetrics: cache hits, cache misses, key store latency, keys created, keys deleted. - Audit events via
ITayraAuditLoggerfor key creation, access, deletion, and bulk deletion.
Distributed Deployments
In a multi-instance deployment, calling ShredAsync on one instance evicts the key from that instance's cache. Other instances will continue using their cached copy until it expires. Keep KeyCacheDuration short (1-5 minutes) to minimize this window.
See Also
- Key Store - The persistence layer (public extension point)
- Field Encryption - How the engine is used during encryption
- Encryption - AES-256-GCM details and wire format
- Options - Cache duration and key size settings
