# How Open-Notebook Encrypts AI Credentials Using Fernet: A Complete Technical Guide

> Learn how Open-Notebook encrypts AI credentials with Fernet symmetric encryption. Explore the technical guide to secure your API keys using SHA-256 hashing derived from your environment variable.

- Repository: [Luis Novo/open-notebook](https://github.com/lfnovo/open-notebook)
- Tags: how-to-guide
- Published: 2026-06-14

---

**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`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/encryption.py), which handles key derivation, encryption, and decryption operations. The domain models in [`open_notebook/domain/credential.py`](https://github.com/lfnovo/open-notebook/blob/main/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`](https://github.com/lfnovo/open-notebook/blob/main/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`](https://github.com/lfnovo/open-notebook/blob/main/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`](https://github.com/lfnovo/open-notebook/blob/main/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`](https://github.com/lfnovo/open-notebook/blob/main/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`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/encryption.py)).

The `ProviderConfig` singleton (referenced in [`open_notebook/domain/provider_config.py`](https://github.com/lfnovo/open-notebook/blob/main/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:

```python

# 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:

```python

# 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:

```python

# 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`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/encryption.py).
- **Automatic encryption**: The `Credential` model in [`open_notebook/domain/credential.py`](https://github.com/lfnovo/open-notebook/blob/main/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`](https://github.com/lfnovo/open-notebook/blob/main/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`](https://github.com/lfnovo/open-notebook/blob/main/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.