How Open-Notebook Encrypts AI Credentials Using Fernet: A Complete Technical Guide
Open-Notebook uses Fernet symmetric encryption from the cryptography library to encrypt AI provider API keys at rest, deriving the encryption key from the OPEN_NOTEBOOK_ENCRYPTION_KEY environment variable using SHA-256 hashing.
Open-Notebook is an open-source knowledge management system that ensures AI credentials are encrypted using Fernet before storage. To protect sensitive API keys from unauthorized access, the application implements Fernet symmetric encryption—a standardized scheme combining AES-128-CBC with HMAC-SHA256 authentication—before persisting data to SurrealDB.
The Fernet Encryption Architecture
The encryption system centers on the cryptography.fernet.Fernet class, which provides authenticated encryption ensuring both confidentiality and integrity. According to the Open-Notebook source code, this implementation prevents credential exposure even if the database is compromised.
The encryption utilities reside in open_notebook/utils/encryption.py, which handles key derivation, encryption, and decryption operations. The domain models in open_notebook/domain/credential.py invoke these utilities automatically during database operations.
Deriving the Encryption Key from Environment Variables
Open-Notebook derives the Fernet key from the OPEN_NOTEBOOK_ENCRYPTION_KEY environment variable (or its Docker secret variant). The process ensures any arbitrary passphrase becomes a valid 32-byte URL-safe Base64-encoded Fernet key.
In open_notebook/utils/encryption.py, the _ensure_fernet_key helper function (lines 105-112) performs this transformation:
- Hashes the input string using SHA-256
- Encodes the digest as URL-safe Base64
- Returns a valid Fernet key suitable for the cryptography library
The get_fernet() function (lines 15-25) then instantiates the Fernet class using this derived key, caching the instance for reuse across the application lifecycle.
Encrypting Credentials Before Database Storage
When persisting AI credentials, the Credential domain model automatically encrypts sensitive fields before transmission to SurrealDB. The api_key field—defined as a Pydantic SecretStr—undergoes encryption in the _prepare_save_data() method (lines 97-108 of open_notebook/domain/credential.py).
The encryption workflow follows this sequence:
- The
api_keyvalue is extracted from theSecretStrwrapper - The
encrypt_value()utility encrypts the plaintext using the Fernet instance - The resulting ciphertext replaces the original value in the database payload
This ensures that API keys never traverse the network or persist to disk in plaintext form.
Decrypting AI Credentials on Retrieval
Decryption occurs transparently when retrieving credentials via the Credential.get() class method (lines 27-40 of open_notebook/domain/credential.py). The process reverses the storage workflow:
- The database record is fetched from SurrealDB
- The encrypted
api_keystring is passed todecrypt_value() - The Fernet instance decrypts the ciphertext using the same derived key
- The plaintext is re-wrapped in a
SecretStrfor secure in-memory handling
The generic decrypt_value() function in open_notebook/utils/encryption.py (lines 66-78) includes validation logic to detect malformed tokens and provides fallback handling for legacy plaintext entries that predate encryption implementation.
Handling Encryption Errors and Legacy Data
Open-Notebook implements robust error handling for encryption edge cases. If decryption fails due to a key mismatch—indicating the OPEN_NOTEBOOK_ENCRYPTION_KEY has changed or the ciphertext was corrupted—the system raises a clear ValueError with instructions to verify the environment configuration (lines 86-94 of open_notebook/utils/encryption.py).
The ProviderConfig singleton (referenced in open_notebook/domain/provider_config.py, lines 15-18) maintains compatibility with the same encryption helpers, ensuring consistent security across both the newer Credential model and legacy configuration storage.
Implementation Examples
Encrypting a New API Key
When creating a new credential, encryption happens automatically during the save operation:
# Example: encrypting a new API key before storing a Credential
from open_notebook.domain.credential import Credential
from pydantic import SecretStr
cred = Credential(
name="Prod",
provider="openai",
modalities=["language", "embedding"],
api_key=SecretStr("sk-very-secret-key"),
)
await cred.save() # encrypt_value is called automatically
Retrieving Decrypted Credentials
Accessing the plaintext value requires explicit extraction from the SecretStr wrapper:
# Example: retrieving and using a decrypted API key
cred = await Credential.get(cred_id)
# `cred.api_key` is a SecretStr containing the clear-text key
api_key = cred.api_key.get_secret_value()
Manual Encryption Utilities
For advanced use cases, developers can access the encryption layer directly:
# Manually using the encryption utilities (rarely needed)
from open_notebook.utils.encryption import encrypt_value, decrypt_value
cipher = encrypt_value("my-plain-key")
plain = decrypt_value(cipher) # returns "my-plain-key"
Summary
- Fernet symmetric encryption: Open-Notebook uses
cryptography.fernet.Fernetproviding AES-128-CBC with HMAC-SHA256 authentication for all AI credentials. - Environment-based key derivation: The encryption key derives from
OPEN_NOTEBOOK_ENCRYPTION_KEYvia SHA-256 hashing and Base64 encoding inopen_notebook/utils/encryption.py. - Automatic encryption: The
Credentialmodel inopen_notebook/domain/credential.pyencryptsapi_keyvalues in_prepare_save_data()before database persistence. - Transparent decryption: The
Credential.get()method decrypts values automatically usingdecrypt_value()and returns them as PydanticSecretStrobjects. - Legacy support: The system handles unencrypted legacy entries gracefully while maintaining strict encryption for new data.
Frequently Asked Questions
What encryption algorithm does Open-Notebook use for AI credentials?
Open-Notebook implements Fernet symmetric encryption from the Python cryptography library. This constructs an AES-128-CBC cipher with HMAC-SHA256 authentication, providing both confidentiality and integrity protection for stored API keys. The implementation resides in open_notebook/utils/encryption.py and applies to all credential storage operations.
How is the encryption key derived from the environment variable?
The system derives the Fernet key from the OPEN_NOTEBOOK_ENCRYPTION_KEY environment variable using the _ensure_fernet_key helper function (lines 105-112). This function hashes the environment value with SHA-256, then encodes the digest as URL-safe Base64 to produce a valid 32-byte Fernet key. This derivation ensures compatibility with arbitrary passphrase strings while maintaining cryptographic strength.
What happens if the OPEN_NOTEBOOK_ENCRYPTION_KEY is changed or lost?
If the encryption key changes, existing encrypted credentials cannot be decrypted, and the system raises a ValueError with explicit instructions to check the OPEN_NOTEBOOK_ENCRYPTION_KEY configuration (lines 86-94). Since Open-Notebook uses symmetric encryption, losing the key results in permanent irretrievability of encrypted credentials. Operators must maintain secure backups of the encryption key to prevent data loss.
Does Open-Notebook support migrating unencrypted legacy credentials?
Yes, the decrypt_value() function in open_notebook/utils/encryption.py (lines 66-78) includes fallback logic to return raw values for legacy plaintext entries that predate encryption implementation. This allows the system to read older unencrypted credentials while enforcing encryption for all new saves. The ProviderConfig class also utilizes these compatible encryption helpers to ensure consistent security across different configuration storage methods.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →