Skip to content

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 MethodWhat 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

csharp
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 key

The 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 logic

Cache Behavior

  • Cache duration is controlled by TayraOptions.KeyCacheDuration (default: 5 minutes).
  • ShredAsync evicts 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.
  • ShredByPrefixAsync delegates to the engine's DeleteAllKeysAsync, which lists matching key IDs (where the store supports ListKeyIdsAsync) and evicts them from the cache before calling IKeyStore.DeleteByPrefixAsync. If the store cannot list keys, cached copies expire naturally.

Key Rotation

RotateKeyAsync creates a new versioned key:

  1. Discovers existing versioned keys via IKeyStore.ListKeyIdsAsync.
  2. Determines the current maximum version number.
  3. Generates a new key with the next version (e.g., cust-abc123:v2).
  4. Stores and caches the new key.
  5. Returns a KeyRotationResult with 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 TayraActivitySource for each operation.
  • Metrics via TayraMetrics: cache hits, cache misses, key store latency, keys created, keys deleted.
  • Audit events via ITayraAuditLogger for 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