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:

  1. Hashes the input string using SHA-256
  2. Encodes the digest as URL-safe Base64
  3. 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_key value is extracted from the SecretStr wrapper
  • 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:

  1. The database record is fetched from SurrealDB
  2. The encrypted api_key string is passed to decrypt_value()
  3. The Fernet instance decrypts the ciphertext using the same derived key
  4. The plaintext is re-wrapped in a SecretStr for 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.Fernet providing AES-128-CBC with HMAC-SHA256 authentication for all AI credentials.
  • Environment-based key derivation: The encryption key derives from OPEN_NOTEBOOK_ENCRYPTION_KEY via SHA-256 hashing and Base64 encoding in open_notebook/utils/encryption.py.
  • Automatic encryption: The Credential model in open_notebook/domain/credential.py encrypts api_key values in _prepare_save_data() before database persistence.
  • Transparent decryption: The Credential.get() method decrypts values automatically using decrypt_value() and returns them as Pydantic SecretStr objects.
  • 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:

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 →