feat: encryption at rest (AES-256-GCM) - #81
Merged
Merged
Conversation
adds an optional `encryption` feature flag backed by AES-256-GCM. when enabled, provides: - EncryptionKey type with from_file (raw 32B or 64 hex chars) - encrypt_record / decrypt_record primitives - FormatError::EncryptionRequired and DecryptionFailed variants - FORMAT_VERSION_ENCRYPTED (v3) and version-gated header reading the feature is off by default — no crypto deps are linked and no encryption types exist in the API when disabled.
AofWriter gains open_encrypted() for v3 (AES-256-GCM) files. each record is written as [nonce: 12B][len: 4B][ciphertext]. AofReader gains open_encrypted() that handles both v2 (plaintext) and v3 (encrypted) files — enabling transparent migration. v3 files opened without a key return FormatError::EncryptionRequired. wrong keys are detected immediately via AEAD authentication.
SnapshotWriter gains create_encrypted() for v3 files. each entry is encrypted independently as [nonce: 12B][len: 4B][ciphertext]. footer CRC covers the encrypted bytes for truncation detection. SnapshotReader gains open_encrypted() that handles both v2 (plaintext) and v3 (encrypted) files transparently. wrong keys and tampered data are detected via AEAD authentication.
recover_shard_encrypted() accepts an optional encryption key and forwards it to snapshot reader and AOF reader. handles both v2 (plaintext) and v3 (encrypted) files transparently.
adds `encryption` feature flag to emberkv-core that forwards to ember-persistence. ShardPersistenceConfig gains an optional encryption_key field (cfg-gated). recovery, AOF writer, and snapshot writer all use encrypted variants when a key is present.
the flag is only available when compiled with --features encryption. reads a 32-byte key (raw or hex) from the given file path and passes it through to shard persistence config. validates that persistence is enabled when encryption is configured. logs when encryption is active.
refactor cfg-gated code to avoid unit_arg/unused_unit clippy lints: - aof: extract open_persistence_file helper, separate open/open_encrypted - snapshot: extract open_tmp helper, separate create/create_encrypted - recovery: use EncryptionKeyRef type alias for cleaner cfg gating - encryption: use io::Error::other instead of deprecated constructor
kacy
added a commit
that referenced
this pull request
Feb 11, 2026
* feat: add encryption module and v3 format support to ember-persistence adds an optional `encryption` feature flag backed by AES-256-GCM. when enabled, provides: - EncryptionKey type with from_file (raw 32B or 64 hex chars) - encrypt_record / decrypt_record primitives - FormatError::EncryptionRequired and DecryptionFailed variants - FORMAT_VERSION_ENCRYPTED (v3) and version-gated header reading the feature is off by default — no crypto deps are linked and no encryption types exist in the API when disabled. * feat: add encryption support to AOF writer and reader AofWriter gains open_encrypted() for v3 (AES-256-GCM) files. each record is written as [nonce: 12B][len: 4B][ciphertext]. AofReader gains open_encrypted() that handles both v2 (plaintext) and v3 (encrypted) files — enabling transparent migration. v3 files opened without a key return FormatError::EncryptionRequired. wrong keys are detected immediately via AEAD authentication. * feat: add encryption support to snapshot writer and reader SnapshotWriter gains create_encrypted() for v3 files. each entry is encrypted independently as [nonce: 12B][len: 4B][ciphertext]. footer CRC covers the encrypted bytes for truncation detection. SnapshotReader gains open_encrypted() that handles both v2 (plaintext) and v3 (encrypted) files transparently. wrong keys and tampered data are detected via AEAD authentication. * feat: pass encryption key through recovery recover_shard_encrypted() accepts an optional encryption key and forwards it to snapshot reader and AOF reader. handles both v2 (plaintext) and v3 (encrypted) files transparently. * feat: forward encryption feature through emberkv-core adds `encryption` feature flag to emberkv-core that forwards to ember-persistence. ShardPersistenceConfig gains an optional encryption_key field (cfg-gated). recovery, AOF writer, and snapshot writer all use encrypted variants when a key is present. * feat: add --encryption-key-file CLI flag to ember-server the flag is only available when compiled with --features encryption. reads a 32-byte key (raw or hex) from the given file path and passes it through to shard persistence config. validates that persistence is enabled when encryption is configured. logs when encryption is active. * fix: resolve clippy and formatting warnings for encryption feature refactor cfg-gated code to avoid unit_arg/unused_unit clippy lints: - aof: extract open_persistence_file helper, separate open/open_encrypted - snapshot: extract open_tmp helper, separate create/create_encrypted - recovery: use EncryptionKeyRef type alias for cleaner cfg gating - encryption: use io::Error::other instead of deprecated constructor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
summary
adds optional encryption at rest for AOF and snapshot files using AES-256-GCM. controlled by a compile-time
encryptioncargo feature (off by default) and a runtime--encryption-key-fileCLI flag. zero cost when the feature is not compiled in — no crypto dependencies linked, no branches on the hot path.each AOF record and snapshot entry is encrypted independently with a random 12-byte nonce, enabling streaming I/O and isolated corruption recovery. encrypted files use a v3 format; existing v2 plaintext files remain readable and are transparently migrated on the next
BGREWRITEAOForBGSAVE.key highlights:
ember-persistence/encryption→emberkv-core/encryption→ember-server/encryptionEncryptionKeytype with redacted Debug output to prevent accidental key loggingFormatError::EncryptionRequiredwhen opening v3 files without a keyFormatError::DecryptionFailedon wrong key or tampered data (AEAD guarantees)what was tested
cargo build --workspace— builds without encryption (default)cargo build --workspace --features encryption— builds with encryptioncargo test --workspace— 886 tests pass (no encryption)cargo test --workspace --features encryption— 906 tests pass (20 new encryption tests)cargo clippy --workspace -- -D warnings— cleancargo clippy --workspace --features encryption -- -D warnings— cleancargo fmt --all --check— cleannew tests cover:
design considerations
[nonce: 12B][len: 4B][ciphertext]per record, where ciphertext includes the auth tagopen()/open_encrypted()methods rather than a single method with anOption<Key>parameter — avoids clippyunit_argwarnings from cfg-gated()arguments and makes the API explicit about encryption intentEncryptionKeyReftype alias in recovery (&EncryptionKeyor&()) provides clean cfg-gated function signatures without runtime overhead