Recipe 9: Encrypting Data at Rest With a Lease-Scoped Key
Situation
You want encrypted storage, but you also want a strong operational property:
decryption capability should exist only while the program is alive and actively renewing access.
Conventional secret management often leaves long-lived keys in:
- config
- environment variables
- secret stores with large blast radii
In a lease-based system, you can scope the key itself as a leased resource with a TTL.
What You Build
An EncryptedLease pattern:
- Ciphertext is stored in a “data lease” (memory or block storage).
- The data-encryption key (DEK) is stored only in a separate short-TTL “key lease”.
- The program renews the key lease while it wants the ciphertext to be decryptable.
When key lease expires, ciphertext becomes inert.
Building Blocks
grafos_securestore::{KeyEpochManager, EncryptedBlobStore}for envelope encryption with epoch-based key rotation — sourceMemBuilderfor the key leaseBlockBuilder(orMemBuilder) for the data leasegrafos_sync::watch()if you need to distribute key epoch metadata
Related API docs:
Threat Model Notes
This pattern is strong against:
- post-mortem recovery of fabric memory or disks after the program stops renewing keys
It does not protect against:
- an attacker who compromises the process while the key lease is active
- a fabric implementation that does not securely zero key memory on reclaim
Be explicit about these assumptions in production documentation.
Design
Envelope Encryption via KeyEpochManager
KeyEpochManager manages a sequence of key epochs, each with its own DEK and TTL. The active epoch’s DEK
is used for new encrypts; old epochs remain readable until they expire.
EncryptedBlobStore wraps a crypto backend and the key manager, providing put/get/remove for
encrypted blobs. Under the hood it performs envelope encryption: each blob is encrypted with the active
epoch’s DEK, and the epoch ID is stored alongside the ciphertext.
Key Epochs and Rotation
use grafos_securestore::{KeyEpochManager, MockCryptoBackend, EpochStatus};
let mut key_mgr = KeyEpochManager::new(Box::new(MockCryptoBackend::new()));let now = 1_000_000u64;let old_epoch_id = key_mgr.create_epoch(now, /* ttl_secs */ 300)?;
// Later, rotate to a new epoch:key_mgr.rotate(now + 200, 300)?;
// Old epoch transitions: Active -> Rotating -> Expiredlet status = key_mgr.get_epoch(old_epoch_id).map(|e| e.status);assert_eq!(status, Some(EpochStatus::Rotating));When an epoch expires, its DEK is gone. Ciphertext encrypted under that epoch becomes inert — fail-closed.
Renewal
Renew the key lease before TTL expiry (e.g. at 60-80% of TTL). If renewal fails, treat it as “key is going away” and stop decrypting.
Walkthrough (Implementation Sketch)
1. Create Key Manager and Blob Store
use grafos_securestore::{KeyEpochManager, EncryptedBlobStore, MockCryptoBackend, BlobId};
let mut key_mgr = KeyEpochManager::new(Box::new(MockCryptoBackend::new()));let now = 1_000_000u64;key_mgr.create_epoch(now, 300)?; // 5-minute TTL
let mut store = EncryptedBlobStore::new(key_mgr);2. Encrypt and Store
let blob_info = store.put(BlobId(42), b"sensitive payload")?;Internally: fetches the active epoch’s DEK, generates a nonce, AEAD-encrypts the plaintext,
stores (epoch_id, nonce, ciphertext).
3. Decrypt and Read
let plaintext = store.get(&blob_info)?;Looks up the epoch ID stored with the blob, retrieves the DEK (if the epoch is still active or rotating), and decrypts. If the epoch has expired, this returns an error — fail-closed.
4. Key Rotation
let now = now + 200; // some time laterstore.key_manager_mut().rotate(now, 300)?;New writes use the new epoch. Old blobs remain readable until their epoch expires.
5. Deletion
store.remove(&blob_info.blob_id)?;Failure Modes
LeaseExpiredfor key lease: key is gone; reads fail; data remains as ciphertext.Disconnected: treat as transient; if you can’t renew, assume key will expire and fail closed.
Observability
Track:
- key lease renewals
- key epoch rotations
- decrypt failures due to expired key
Variations
- Shamir splitting across multiple key leases (more complex)
- Split key material by audience and require multiple capabilities