Open Notebook Credential Encryption: How API Keys Are Protected in the Database

Open Notebook encrypts API keys using Fernet symmetric encryption (AES-128-CBC with HMAC-SHA256) before storing them in the database, ensuring plaintext credentials never touch persistent storage.

The lfnovo/open-notebook repository implements a robust credential encryption layer to safeguard sensitive provider API keys at rest. By leveraging the Python cryptography library's Fernet implementation, the system automatically encrypts credentials before persistence and decrypts them only when loaded into application memory. This approach ensures that database breaches or unauthorized access never expose plaintext secrets.

How Credential Encryption Works in Open Notebook

The encryption architecture centers on open_notebook/utils/encryption.py, which provides deterministic symmetric encryption using Fernet (AES-128-CBC with HMAC-SHA256 authentication). The process involves three key stages: key derivation, encryption utility functions, and automatic field-level handling in the domain model.

Encryption Key Source and Derivation

The system reads the raw encryption secret from the environment variable OPEN_NOTEBOOK_ENCRYPTION_KEY (or the Docker-compatible OPEN_NOTEBOOK_ENCRYPTION_KEY_FILE for file-based secrets). The helper function _ensure_fernet_key in open_notebook/utils/encryption.py processes this input by hashing it with SHA-256 and then base64-url-encoding the result to produce a valid 32-byte Fernet key. This design accepts any string as input while ensuring cryptographic compatibility with the Fernet specification.

The Fernet Encryption Engine

The get_fernet() function constructs a cryptography.Fernet instance from the derived key. Two primary utilities expose this functionality to the rest of the application:

  • encrypt_value(value: str) → str: Encrypts plaintext and returns a URL-safe base64-encoded token.
  • decrypt_value(value: str) → str: Decrypts Fernet tokens, with fallback logic to return the original value unchanged for legacy unencrypted data.

Automatic Encryption in the Credential Model

The Credential domain model in open_notebook/domain/credential.py automatically intercepts the api_key field (typed as pydantic.SecretStr) during save operations. Before persistence, it extracts the secret value and passes it through encrypt_value(), storing only the resulting ciphertext in the database. Upon retrieval, the model passes the stored ciphertext through decrypt_value() and re-wraps the result in a SecretStr, keeping the decrypted key available only in volatile application memory.

Working with Encrypted Credentials

Developers interact with encrypted credentials through the standard Credential model interface without manual encryption steps.

Storing a New API Key

When creating a credential, simply wrap the API key in SecretStr. The encryption happens automatically during the save operation:

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

cred = Credential(
    name="Production OpenAI",
    provider="openai",
    api_key=SecretStr("sk-abcdef123456789"),
)
await cred.save()  # encrypt_value() is called internally

Retrieving and Using Decrypted Keys

When loading credentials, the decryption occurs transparently:

cred = await Credential.get("credential-id")
if cred.api_key:
    # Returns the decrypted plaintext

    clear_key = cred.api_key.get_secret_value()
    print(f"API Key: {clear_key}")

Manual Encryption for Migrations

For bulk operations or data migrations, use the utility functions directly from open_notebook/utils/encryption.py:

from open_notebook.utils.encryption import encrypt_value, decrypt_value

# Encrypt a value manually

cipher_text = encrypt_value("my-secret-api-key")

# Decrypt it back

plain_text = decrypt_value(cipher_text)
assert plain_text == "my-secret-api-key"

Key Design Benefits

The encryption implementation in lfnovo/open-notebook provides several security advantages:

  • No plaintext in database: Only Fernet tokens persist to the credential table, rendering database dumps useless to attackers without the encryption key.

  • Straightforward key rotation: Changing OPEN_NOTEBOOK_ENCRYPTION_KEY and re-encrypting existing rows allows for rapid credential rotation.

  • Graceful legacy handling: The decrypt_value function returns unencrypted values unchanged, ensuring backward compatibility during migrations.

  • API-level enforcement: The require_encryption_key middleware in api/routers/credentials.py ensures the encryption key is present before accepting credential-related requests.

Summary

  • Open Notebook uses Fernet symmetric encryption (AES-128-CBC with HMAC-SHA256) to protect API keys in the database.
  • Encryption utilities reside in open_notebook/utils/encryption.py, providing encrypt_value() and decrypt_value() functions.
  • The Credential model in open_notebook/domain/credential.py automatically encrypts fields on save and decrypts them on load.
  • The encryption key is sourced from OPEN_NOTEBOOK_ENCRYPTION_KEY and derived via SHA-256 hashing.
  • Legacy unencrypted data is handled gracefully without breaking existing functionality.

Frequently Asked Questions

What encryption algorithm does Open Notebook use for credential storage?

Open Notebook uses Fernet symmetric encryption, which implements AES-128 in CBC mode with PKCS7 padding and HMAC-SHA256 for authentication. This is provided by the Python cryptography library's Fernet class, ensuring authenticated encryption that prevents tampering and unauthorized decryption.

How do I configure the encryption key for Open Notebook?

Set the OPEN_NOTEBOOK_ENCRYPTION_KEY environment variable to any secret string before starting the application. Alternatively, use OPEN_NOTEBOOK_ENCRYPTION_KEY_FILE to specify a file path containing the key (useful for Docker secrets). The system automatically hashes the input with SHA-256 and base64-url-encodes it to create a valid Fernet key.

Can I rotate the encryption key without losing existing credentials?

Yes, key rotation is supported. Change the OPEN_NOTEBOOK_ENCRYPTION_KEY environment variable and re-encrypt existing rows by loading each Credential object (which triggers decryption with the old key) and saving it again (which triggers encryption with the new key). The decrypt_value function includes fallback logic to return legacy unencrypted values unchanged, ensuring smooth transitions.

What happens if the encryption key is lost?

If the OPEN_NOTEBOOK_ENCRYPTION_KEY is lost or changed without re-encrypting existing data, previously encrypted API keys become permanently unreadable. The Fernet tokens stored in the database cannot be decrypted without the exact key used during encryption. Always back up your encryption key in a secure secrets manager.

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 →