How the Open Notebook Encryption Key System Uses Fernet for Credential Storage

Open Notebook uses Fernet symmetric encryption (AES-128-CBC with HMAC-SHA256) to automatically encrypt API keys at rest, deriving the encryption key from a user-supplied environment variable hashed with SHA-256.

The lfnovo/open-notebook repository implements a transparent field-level encryption system that protects provider API credentials using Python's cryptography library. By leveraging Fernet's authenticated symmetric encryption, the system ensures that sensitive data remains encrypted in the database while requiring minimal changes to application logic.

Fernet Key Derivation and Management

The encryption system centers on open_notebook/utils/encryption.py, which handles the transformation of user-supplied secrets into valid Fernet keys and provides encrypt/decrypt utilities.

Deriving Keys from Environment Variables

The system expects a master secret via the OPEN_NOTEBOOK_ENCRYPTION_KEY environment variable (or its Docker secret counterpart). When the application first requires encryption services, the _get_encryption_key function (lines 78-84) lazily loads this value and passes it to _ensure_fernet_key (lines 96-103).

The _ensure_fernet_key function processes the arbitrary string through SHA-256 hashing, then Base64-url-encodes the 32-byte digest to produce a specification-compliant Fernet key (lines 104-112). This derived key then initializes a Fernet instance via get_fernet() (lines 15-25).

This approach allows administrators to use any passphrase while ensuring the underlying cryptographic key meets Fernet's strict 32-byte requirement.

Core Encryption Utilities

The module exposes two primary functions for credential protection:

  • encrypt_value (lines 28-42): Accepts a plaintext string, encodes it to UTF-8 bytes, applies Fernet.encrypt, and returns a Base64-encoded token.
  • decrypt_value (lines 66-80): Attempts to decrypt Fernet tokens using Fernet.decrypt. If decryption fails but the token resembles a Fernet format, it raises a clear error; otherwise, it returns the raw value unchanged to support legacy plaintext credentials (lines 85-96).

Automatic Credential Encryption in the Domain Layer

The Credential domain model in open_notebook/domain/credential.py integrates encryption seamlessly into the persistence lifecycle, ensuring api_key fields never reach the database in plaintext.

Encryption on Save

When a Credential instance is persisted via _prepare_save_data (lines 34-40), the system extracts the underlying string from the api_key field (stored as Pydantic's SecretStr). This value passes to encrypt_value, producing an opaque token that replaces the plaintext in the database row.

Decryption on Load

During retrieval operations such as Credential.get, Credential.get_all, or _from_db_row, the stored token undergoes decryption via decrypt_value. The recovered plaintext is immediately wrapped back in SecretStr (lines 60-68) before returning to application code, maintaining type safety and preventing accidental logging of secrets.

Practical Implementation Examples

Configure the encryption key via environment variable:


# .env or Docker environment

# OPEN_NOTEBOOK_ENCRYPTION_KEY=my-super-secret-passphrase

Manual encryption and decryption for debugging or external tooling:

from open_notebook.utils.encryption import encrypt_value, decrypt_value

plain = "sk-abc123"
cipher = encrypt_value(plain)      # Returns Fernet token

recovered = decrypt_value(cipher)   # Returns "sk-abc123"

High-level credential API handling encryption automatically:

from open_notebook.domain.credential import Credential
from pydantic import SecretStr

# Create and save (automatically encrypts)

cred = Credential(
    name="Production",
    provider="openai",
    modalities=["language"],
    api_key=SecretStr("sk-prod-key")
)
await cred.save()

# Retrieve and use (automatically decrypts)

saved = await Credential.get(cred.id)
print(saved.api_key.get_secret_value())  # "sk-prod-key"

Summary

  • Fernet symmetric encryption: Open Notebook uses AES-128-CBC with HMAC-SHA256 authentication via the cryptography library's Fernet implementation.
  • SHA-256 key derivation: The OPEN_NOTEBOOK_ENCRYPTION_KEY environment variable is hashed and Base64-encoded in open_notebook/utils/encryption.py to generate a valid 32-byte Fernet key.
  • Transparent field-level encryption: The Credential class in open_notebook/domain/credential.py automatically encrypts api_key fields before saving and decrypts them upon retrieval.
  • Legacy support: The decrypt_value function gracefully handles legacy plaintext values while strictly validating Fernet tokens.
  • Type safety: Decrypted values are wrapped in Pydantic's SecretStr to prevent accidental exposure in logs or traces.

Frequently Asked Questions

What cryptographic algorithm does the Open Notebook encryption system use?

The system uses Fernet, which combines AES-128-CBC encryption with HMAC-SHA256 authentication. This provides authenticated symmetric encryption, ensuring both confidentiality and integrity of stored API keys.

How do I configure the encryption key in production environments?

Set the OPEN_NOTEBOOK_ENCRYPTION_KEY environment variable to a strong, unique passphrase before starting the application. In Docker deployments, you can use Docker secrets mounted at /run/secrets/OPEN_NOTEBOOK_ENCRYPTION_KEY. The system hashes this value with SHA-256 and Base64-encodes it to derive the actual Fernet key, meaning you can use any string without worrying about specific key length requirements.

What happens if I lose or change the encryption key?

If you lose the OPEN_NOTEBOOK_ENCRYPTION_KEY, you cannot decrypt existing credentials stored in the database. The system does not store the key locally; it relies entirely on the environment variable. Changing the key will prevent decryption of existing encrypted values, effectively rendering stored API keys inaccessible. You would need to re-create credentials with the new key.

Can existing plaintext credentials be migrated to encrypted storage?

Yes. The decrypt_value function in open_notebook/utils/encryption.py (lines 85-96) includes backward compatibility logic that returns raw values unchanged if they do not match the Fernet token format. When you save an existing credential retrieved in plaintext mode, the _prepare_save_data method automatically encrypts it, migrating the value to secure storage on the next write operation.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →