Skip to content

feat: Add KeyringEncryptionWrapper for system keychain-based encryption#296

Open
strawgate wants to merge 1 commit intomainfrom
claude/issue-293-20260203-0844
Open

feat: Add KeyringEncryptionWrapper for system keychain-based encryption#296
strawgate wants to merge 1 commit intomainfrom
claude/issue-293-20260203-0844

Conversation

@strawgate
Copy link
Owner

@strawgate strawgate commented Feb 3, 2026

Introduce a new encryption wrapper that automatically generates and stores Fernet encryption keys in the system keychain using the keyring library.

Features

  • Auto-generates encryption keys on first use
  • Retrieves existing keys on subsequent instantiations
  • Configurable service_name and key_name for keychain storage
  • Supports key rotation via old_keys parameter
  • Follows existing FernetEncryptionWrapper patterns

Closes #293

Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added KeyringEncryptionWrapper for secure encryption key storage and management via system keychain
    • Enables automatic key generation, storage, and configuration with custom service and key names
    • Supports key rotation with old keys and configurable decryption error handling
  • Tests

    • Added comprehensive test suite covering encryption, decryption, key generation, rotation, and error handling scenarios

Introduce a new encryption wrapper that automatically generates and stores
Fernet encryption keys in the system keychain using the keyring library.

Features:
- Auto-generates encryption keys on first use
- Retrieves existing keys on subsequent instantiations
- Configurable service_name and key_name for keychain storage
- Supports key rotation via old_keys parameter
- Follows existing FernetEncryptionWrapper patterns

Closes #293

Co-authored-by: Bill Easton <strawgate@users.noreply.github.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 3, 2026

📝 Walkthrough

Walkthrough

The PR introduces a new KeyringEncryptionWrapper class that manages Fernet encryption keys via the system keychain, enabling persistent key storage across application restarts. The implementation includes key rotation support, decryption version validation, and comprehensive test coverage.

Changes

Cohort / File(s) Summary
Encryption Wrapper Implementation
src/key_value/aio/wrappers/encryption/keyring.py, src/key_value/aio/wrappers/encryption/__init__.py
Adds KeyringEncryptionWrapper class with constants for version (1), default service name ("py-key-value-encryption"), and default key name ("encryption-key"). Supports key rotation via old_keys parameter, automatic key generation on first use, and decryption version validation with configurable error handling.
Test Suite
tests/stores/wrappers/test_encryption_keyring.py
Comprehensive test suite validating encryption/decryption, key generation and reuse, custom service/key names, batch operations, key rotation, error handling for corrupted data, and isolation between different service configurations.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 A keyring so fine, where secrets shall stay,
Fernet keys locked away, safe from the fray,
Old keys rotate round, like carrots in spring,
Encrypted and nested—what joy this shall bring! 🔐✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 63.64% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main change: adding a new KeyringEncryptionWrapper for system keychain-based encryption, which is the primary focus of all modifications.
Linked Issues check ✅ Passed The PR fully implements the requirements from issue #293: creates a second encryption wrapper variant (KeyringEncryptionWrapper) that generates encryption keys and stores them in the system keychain.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the KeyringEncryptionWrapper feature: new wrapper class, integration into public API, and comprehensive test coverage.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch claude/issue-293-20260203-0844

Comment @coderabbitai help to get the list of available commands and usage tips.

@macroscopeapp
Copy link
Contributor

macroscopeapp bot commented Feb 3, 2026

Add KeyringEncryptionWrapper to key_value.aio.wrappers.encryption to use system keychain-managed Fernet keys with fixed encryption version 1

Introduce KeyringEncryptionWrapper in keyring.py with conditional keyring import, MultiFernet support via old_keys, and export via init.py; add tests in test_encryption_keyring.py.

📍Where to Start

Start with the KeyringEncryptionWrapper class in keyring.py.


📊 Macroscope summarized 966f099. 2 files reviewed, 1 issue evaluated, 1 issue filtered, 0 comments posted. View details

@strawgate
Copy link
Owner Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 3, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@tests/stores/wrappers/test_encryption_keyring.py`:
- Around line 67-69: The test is making flaky substring assertions against the
ciphertext; instead, update the assertions around raw_value in
test_encryption_keyring.py to avoid probabilistic substring checks: remove the
lines asserting '"test" not in str(raw_value)' and '"value" not in
str(raw_value)' and replace them with a deterministic check such as asserting
raw_value != plaintext (or ciphertext != original_plaintext_bytes) and/or
asserting the presence of encryption metadata already used elsewhere in the test
(referencing raw_value and the plaintext variable or the encryption metadata
assertions present in this test).

Comment on lines +67 to +69
# The encrypted data should not contain the original value
assert "test" not in str(raw_value)
assert "value" not in str(raw_value)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Avoid probabilistic ciphertext substring assertions.

Line 67–69 checks that "test"/"value" are not substrings of the ciphertext; encrypted base64 may rarely contain those sequences, causing flaky tests. The encryption metadata assertions already prove encryption.

✅ Safer adjustment
-        # The encrypted data should not contain the original value
-        assert "test" not in str(raw_value)
-        assert "value" not in str(raw_value)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# The encrypted data should not contain the original value
assert "test" not in str(raw_value)
assert "value" not in str(raw_value)
🤖 Prompt for AI Agents
In `@tests/stores/wrappers/test_encryption_keyring.py` around lines 67 - 69, The
test is making flaky substring assertions against the ciphertext; instead,
update the assertions around raw_value in test_encryption_keyring.py to avoid
probabilistic substring checks: remove the lines asserting '"test" not in
str(raw_value)' and '"value" not in str(raw_value)' and replace them with a
deterministic check such as asserting raw_value != plaintext (or ciphertext !=
original_plaintext_bytes) and/or asserting the presence of encryption metadata
already used elsewhere in the test (referencing raw_value and the plaintext
variable or the encryption metadata assertions present in this test).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Introduce keystore encryption wrapper

1 participant