How Credential Encryption Works in Open‑Notebook with Fernet and OPEN_NOTEBOOK_ENCRYPTION_KEY

Open‑Notebook encrypts every provider credential using Fernet symmetric encryption derived from the OPEN_NOTEBOOK_ENCRYPTION_KEY environment variable, ensuring API keys are stored as authenticated tokens rather than plaintext.

Open‑Notebook, the open-source notebook application, protects sensitive provider credentials by encrypting them at rest using the Fernet implementation from the cryptography library. This article explains the complete encryption pipeline—from key derivation to runtime decryption—based on the actual source code in lfnovo/open-notebook.

The Encryption Pipeline in open_notebook/utils/encryption.py

The encryption system consists of three distinct phases: key derivation, encryption during writes, and decryption during reads.

Deriving the Fernet Key from OPEN_NOTEBOOK_ENCRYPTION_KEY

In open_notebook/utils/encryption.py, the function _ensure_fernet_key handles the transformation of your environment variable into a valid Fernet key. The process takes any string provided in OPEN_NOTEBOOK_ENCRYPTION_KEY, computes its SHA‑256 hash, and then Base64‑url‑encodes the digest to produce a 32‑byte key compatible with Fernet's requirements.

Initializing the Fernet Instance

The get_fernet() function lazily loads the encryption key and returns a cryptography.fernet.Fernet instance. This singleton pattern ensures that the same key is reused throughout the application lifecycle, initialized only when first needed.

Encrypting Credentials at Rest

When a Credential model is saved, the Credential._prepare_save_data() method extracts the plaintext api_key (stored as a Pydantic SecretStr) and passes it to encrypt_value(). This function converts the secret to UTF‑8 bytes, encrypts it using the Fernet instance, and returns a URL‑safe Base64 string that is persisted in the database's credential table.

Decrypting Credentials at Runtime

On retrieval, Credential.get() and the bulk-load helper _from_db_row invoke decrypt_value() to restore the original secret. If the stored token appears to be a Fernet token but decryption fails, the system raises a clear error indicating that the OPEN_NOTEBOOK_ENCRYPTION_KEY environment variable does not match the key used for encryption.

Working with Encrypted Credentials

The encryption process is transparent to the application code. Here is how to create and retrieve encrypted credentials:


# Set the encryption key in your environment

# OPEN_NOTEBOOK_ENCRYPTION_KEY=my-very-secret-passphrase

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

# Create a credential - encryption happens automatically on save

cred = Credential(
    name="Production",
    provider="openai",
    modalities=["language", "embedding"],
    api_key=SecretStr("sk-my-openai-key")
)
await cred.save()  # Encrypts api_key before persisting

# Load a credential - decryption happens transparently

loaded = await Credential.get(cred.id)
print(loaded.api_key.get_secret_value())  # Outputs: sk-my-openai-key

You can also use the encryption utilities directly for other sensitive fields:

from open_notebook.utils.encryption import encrypt_value, decrypt_value

# Encrypt a value manually

token = encrypt_value("my-sensitive-data")
print(token)  # Fernet token (Base64 URL-safe)

# Decrypt it back

plain = decrypt_value(token)
print(plain)  # Outputs: my-sensitive-data

Key Rotation and Security Considerations

Because the Fernet key is deterministically derived from OPEN_NOTEBOOK_ENCRYPTION_KEY using SHA‑256, rotating the environment variable requires re‑encrypting existing credentials. The system does not support automatic key rotation; changing the passphrase without updating stored credentials will result in decryption failures.

The implementation uses authenticated encryption (AES‑128‑CBC with HMAC), ensuring that tampered ciphertext is detected before decryption. Legacy plaintext credentials remain readable as a fallback, though new writes always use encryption.

Summary

  • Key Derivation: The _ensure_fernet_key function in open_notebook/utils/encryption.py transforms OPEN_NOTEBOOK_ENCRYPTION_KEY into a 32‑byte Fernet key using SHA‑256 and Base64‑url encoding.
  • Automatic Encryption: The Credential model automatically encrypts api_key fields via encrypt_value() before persistence.
  • Transparent Decryption: Retrieval operations use decrypt_value() to restore secrets at runtime without manual intervention.
  • Deterministic Keys: Key derivation is deterministic, meaning credential rotation is required when changing the encryption passphrase.

Frequently Asked Questions

What happens if I change the OPEN_NOTEBOOK_ENCRYPTION_KEY after creating credentials?

If you change the OPEN_NOTEBOOK_ENCRYPTION_KEY environment variable, existing encrypted credentials will fail to decrypt because the Fernet key is deterministically derived from that specific string. You must either re‑encrypt all existing credentials using the new key or export them before rotation and re‑import after updating the environment variable.

What encryption algorithm does Open‑Notebook use?

Open‑Notebook uses Fernet from the cryptography library, which implements AES‑128 in CBC mode with HMAC‑SHA256 for authentication. This provides authenticated encryption, ensuring that any tampering with the stored ciphertext is detected during decryption.

Can I use the encryption utilities for fields other than API keys?

Yes. The encrypt_value() and decrypt_value() functions in open_notebook/utils/encryption.py are general-purpose utilities that can encrypt any string value. They are used automatically for api_key fields in the Credential model, but you can import and use them directly for other sensitive data in your application.

How does Open‑Notebook handle legacy unencrypted credentials?

The decryption logic in decrypt_value() attempts to detect whether a stored value is a Fernet token. If decryption fails or the value does not appear to be encrypted, the system may fall back to treating it as plaintext. However, all new credentials are automatically encrypted when saved through the Credential model.

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 →