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

> Learn how Open-Notebook secures provider credentials with Fernet encryption and OPEN_NOTEBOOK_ENCRYPTION_KEY. Protect API keys by storing them as authenticated tokens.

- Repository: [Luis Novo/open-notebook](https://github.com/lfnovo/open-notebook)
- Tags: internals
- Published: 2026-06-28

---

**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`](https://github.com/lfnovo/open-notebook/blob/main/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:

```python

# 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

```

```python

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

```python
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`](https://github.com/lfnovo/open-notebook/blob/main/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`](https://github.com/lfnovo/open-notebook/blob/main/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.