# How Open Notebook Implements Fernet Encryption for Secure Credential Storage

> Learn how Open Notebook uses Fernet encryption to secure API keys and credentials field by field. Discover transparent encryption and decryption with this Python library.

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

---

**Open Notebook uses the cryptography library's Fernet scheme to encrypt API keys and credentials at the field level, deriving a 32-byte key from the `OPEN_NOTEBOOK_ENCRYPTION_KEY` environment variable and providing transparent encryption/decryption through utility functions in [`open_notebook/utils/encryption.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/encryption.py).**

The `lfnovo/open-notebook` repository protects sensitive provider credentials using **Fernet encryption**, a symmetric encryption standard that combines AES-128-CBC with HMAC-SHA256 for authenticated encryption. This implementation ensures that API keys and secrets never persist in plaintext within the database, while maintaining seamless access for authorized application components.

## Fernet Encryption Architecture

The encryption layer resides in [`open_notebook/utils/encryption.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/encryption.py) and serves as the central utility for all credential protection. Rather than storing raw encryption keys directly, the system accepts arbitrary strings through environment variables and normalizes them into valid Fernet keys using cryptographic hashing.

The `ProviderConfig` model in [`open_notebook/domain/provider_config.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/provider_config.py) consumes these utilities to automatically encrypt values before persistence and decrypt them on access, ensuring developers never handle plaintext secrets in business logic.

## Key Derivation and Environment Configuration

### Environment Variable Setup

The system sources its encryption material from the `OPEN_NOTEBOOK_ENCRYPTION_KEY` environment variable. For Docker deployments, it supports a file-based fallback via `OPEN_NOTEBOOK_ENCRYPTION_KEY_FILE`, reading the key from a secrets file rather than the environment.

Any string provided through these channels is accepted, removing the burden of generating Fernet-specific base64-encoded keys manually.

### SHA-256 Key Normalization

Since Fernet requires a 32-byte URL-safe base64-encoded key, the implementation derives valid keys from arbitrary input strings using the `_ensure_fernet_key` function. This process hashes the input with SHA-256 and base64-url-encodes the digest, producing a deterministic 32-byte key that satisfies Fernet requirements.

The `_get_or_create_encryption_key` function orchestrates this retrieval and normalization process, ensuring consistent key material across application restarts.

## Encryption and Decryption Workflow

### Lazy Fernet Instance Creation

The `get_fernet()` function implements lazy initialization, constructing the `Fernet` object only upon first use. This design prevents application crashes during import if the encryption key is temporarily unavailable, deferring key validation until the first encryption or decryption operation occurs.

### Field-Level Encryption Process

When persisting credentials, the `encrypt_value(plain_text)` function performs the following operations:

1. Encodes the plaintext string to UTF-8 bytes
2. Encrypts using the Fernet instance (AES-128-CBC encryption with HMAC-SHA256 authentication)
3. Returns a URL-safe base64 string suitable for database storage

During retrieval, `decrypt_value(possibly_encrypted)` reverses this process, converting the token back to the original plaintext string.

### Graceful Legacy Data Handling

The implementation includes backward compatibility for unencrypted legacy data. The `looks_like_fernet_token` helper checks whether a stored value matches Fernet's token format. If the value does not resemble a Fernet token, `decrypt_value` returns it unchanged, allowing seamless migration from plaintext storage.

If decryption fails due to an incorrect key, the system raises a clear `ValueError` rather than returning corrupted data, preventing silent authentication failures with invalid API keys.

## Integration with the Credential Model

The `ProviderConfig` class in [`open_notebook/domain/provider_config.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/provider_config.py) integrates encryption directly into its property accessors. The setter automatically encrypts values using `encrypt_value`, while the getter decrypts them using `decrypt_value` before returning the plaintext to consuming code.

This pattern ensures that the database always contains encrypted blobs (prefixed with `gAAAAAB` in base64), while the application works with decrypted strings transparently. The [`open_notebook/ai/key_provider.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/key_provider.py) module retrieves these decrypted keys to instantiate AI provider clients without exposing secrets in logs or stack traces.

## Security Properties and Guarantees

**Authenticated Encryption**: Fernet provides both confidentiality and integrity protection. The ciphertext includes an HMAC-SHA256 signature that prevents tampering, ensuring that modified or corrupted credentials are detected during decryption.

**Deterministic Key Derivation**: The SHA-256 derivation ensures that the same environment variable always produces the same encryption key, while allowing users to provide simple passphrases rather than cryptographically random 32-byte strings.

**Fail-Safe Defaults**: The lazy initialization and legacy data handling ensure that encryption enhancements do not break existing deployments or prevent application startup in development environments.

## Practical Implementation Examples

### Basic Encryption and Decryption

```python
from open_notebook.utils.encryption import encrypt_value, decrypt_value

# Encrypt a credential before database storage

api_key = "sk-abc123-secret"
encrypted_key = encrypt_value(api_key)

# Result: 'gAAAAABlY...' (URL-safe base64 Fernet token)

# Decrypt when retrieving from storage

plain_key = decrypt_value(encrypted_key)

# Returns: 'sk-abc123-secret'

```

### Model Integration Pattern

```python

# Simplified pattern from open_notebook/domain/provider_config.py

class ProviderConfig(BaseModel):
    _encrypted_api_key: str = ""

    @property
    def api_key(self) -> str:
        """Return the decrypted API key for use in API clients."""
        return decrypt_value(self._encrypted_api_key)

    @api_key.setter
    def api_key(self, value: str) -> None:
        """Store the API key as an encrypted Fernet token."""
        self._encrypted_api_key = encrypt_value(value)

```

### Environment Configuration

```bash

# Set the encryption key in your environment

export OPEN_NOTEBOOK_ENCRYPTION_KEY="your-secret-passphrase-here"

# Or use Docker secrets

export OPEN_NOTEBOOK_ENCRYPTION_KEY_FILE="/run/secrets/encryption_key"

```

## Summary

- **Fernet encryption** in Open Notebook uses the `cryptography` library to provide authenticated AES-128-CBC encryption with HMAC-SHA256 integrity verification.
- The implementation resides in [`open_notebook/utils/encryption.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/encryption.py), providing `encrypt_value()` and `decrypt_value()` functions for field-level protection.
- Keys are derived from the `OPEN_NOTEBOOK_ENCRYPTION_KEY` environment variable using SHA-256 hashing, accepting arbitrary strings rather than requiring base64-encoded bytes.
- The `ProviderConfig` model in [`open_notebook/domain/provider_config.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/provider_config.py) transparently encrypts credentials before database storage and decrypts them on access.
- Legacy plaintext credentials remain readable through the `looks_like_fernet_token` check, ensuring backward compatibility during migrations.

## Frequently Asked Questions

### What encryption algorithm does Open Notebook use for credential storage?

Open Notebook uses **Fernet symmetric encryption** from the Python `cryptography` library. This scheme combines AES-128 in CBC mode for confidentiality with HMAC-SHA256 for message authentication, ensuring both privacy and integrity of stored API keys.

### How do I configure the encryption key for production deployments?

Set the `OPEN_NOTEBOOK_ENCRYPTION_KEY` environment variable to a secure, random string before starting the application. For Docker Swarm or Kubernetes deployments, use `OPEN_NOTEBOOK_ENCRYPTION_KEY_FILE` to specify a path to a secrets file. The system automatically hashes your input with SHA-256 to derive the 32-byte Fernet key.

### What happens if I lose or change the encryption key?

If you change the encryption key, existing encrypted credentials cannot be decrypted and `decrypt_value()` will raise a `ValueError`. However, legacy plaintext credentials (stored before encryption was implemented) continue to work normally. Always back up your `OPEN_NOTEBOOK_ENCRYPTION_KEY` value, as losing it renders encrypted API keys permanently irretrievable.

### Does the encryption impact application performance?

The impact is negligible for typical API key storage and retrieval. Fernet encryption operates on small strings (API keys and tokens) and uses optimized AES implementations from the `cryptography` library. The lazy initialization in `get_fernet()` ensures the encryption context is reused across operations within the same process.