# API Key Encryption at Rest in OmniRoute: Best Practices and Implementation Guide

> Discover best practices for API key encryption at rest in OmniRoute. Learn how to secure provider credentials with AES-256-GCM and scrypt for robust protection.

- Repository: [Diego Rodrigues de Sa e Souza/OmniRoute](https://github.com/diegosouzapw/OmniRoute)
- Tags: best-practices
- Published: 2026-07-03

---

**OmniRoute secures provider credentials using AES-256-GCM field-level encryption with scrypt-derived keys, storing secrets in a versioned format that supports automatic migration from legacy schemes.**

The OmniRoute open-source routing platform stores sensitive provider credentials—including API keys, access tokens, and refresh tokens—in a local SQLite database. To prevent unauthorized access to these secrets, the codebase implements a comprehensive encryption strategy centered in [`src/lib/db/encryption.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/db/encryption.ts). This guide explains the architectural decisions, configuration requirements, and operational procedures that ensure your API keys remain confidential and tamper-evident while at rest.

## Core Encryption Architecture

OmniRoute employs authenticated encryption to protect credential fields before they reach the database layer. The implementation prioritizes both security and operational continuity.

### AES-256-GCM Implementation

The encryption module in [`src/lib/db/encryption.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/db/encryption.ts) defines `ALGORITHM = "aes-256-gcm"` and `AUTH_TAG_LENGTH = 16` to enforce Galois/Counter Mode with a full-length authentication tag [[lines 30-38]](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/db/encryption.ts#L30). This mode provides **confidentiality** (preventing unauthorized reading) and **integrity** (detecting tampering) simultaneously.

The `encrypt()` function generates a random initialization vector (IV) for each operation, ensuring that identical API keys produce different ciphertexts. The resulting encrypted string follows the format `enc:v1:<iv_hex>:<ciphertext_hex>:<authTag_hex>`, making it easy to identify encrypted values programmatically [[lines 5-6]](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/db/encryption.ts#L5).

### Key Derivation with Static Salt

Rather than using the raw environment variable as the encryption key, OmniRoute derives the key using `scryptSync()`:

```typescript
// From src/lib/db/encryption.ts
const STATIC_SALT = "omniroute-field-encryption-v1";
const derivedKey = scryptSync(secret, STATIC_SALT, KEY_LENGTH);

```

The `getStaticKey()` function implements this derivation [[lines 41-66]](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/db/encryption.ts#L41). Using a **static, versioned salt** ensures that every encryption and decryption operation uses the identical derived key, eliminating key-mismatch bugs that plagued earlier dynamic-salt implementations.

## Configuration and Environment Setup

Proper configuration of the encryption key is the most critical security step when deploying OmniRoute.

### Setting STORAGE_ENCRYPTION_KEY

The system expects a `STORAGE_ENCRYPTION_KEY` environment variable containing a high-entropy secret. Generate a secure 32-byte key using standard tools:

```bash
export STORAGE_ENCRYPTION_KEY=$(openssl rand -base64 32)

```

OmniRoute reads `process.env.STORAGE_ENCRYPTION_KEY` at runtime. This key protects all stored credentials, making it the singular secret that safeguards your entire credential database.

### Graceful Degradation Behavior

If `STORAGE_ENCRYPTION_KEY` is missing, the encryption module falls back to **passthrough mode**, storing credentials as plaintext while logging a warning [[lines 62-64]](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/db/encryption.ts#L62). Additionally, when encryption fails due to runtime errors, the code returns the original plaintext rather than crashing, maintaining service availability while clearly indicating misconfiguration [[lines 33-38]](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/db/encryption.ts#L33).

## Data Format and Versioning

Structured storage formats enable safe upgrades and prevent common operational errors.

### The enc:v1 Prefix Convention

Every encrypted value carries the prefix `enc:v1:`, creating a clear versioning scheme. This convention allows the system to:
- Detect whether a value requires encryption before writing
- Identify the encryption version for future algorithm upgrades
- Distinguish encrypted blobs from legacy plaintext

### Preventing Double Encryption

The `encrypt()` function short-circuits if the incoming plaintext already starts with the `enc:v1:` prefix [[lines 20-22]](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/db/encryption.ts#L20). This prevents the creation of malformed nested ciphertexts and eliminates unnecessary CPU overhead during repeated save operations.

## Migration and Backward Compatibility

OmniRoute maintains compatibility with data encrypted under previous schemes while providing utilities for seamless upgrades.

### Legacy Key Support

The module retains a `getLegacyDynamicKey()` function for reading data encrypted with older dynamic-salt methods. During decryption, the system attempts the static key first, then falls back to the legacy key if that fails [[lines 90-98]](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/db/encryption.ts#L90). This ensures existing installations can read older ciphertexts without immediate migration.

### Automatic Migration Utilities

The `migrateLegacyEncryptedString()` function detects values encrypted with obsolete keys, decrypts them using the legacy method, and re-encrypts them with the current static key [[lines 49-57]](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/db/encryption.ts#L49). This utility enables zero-downtime migration scripts that gradually upgrade the database to the current security standard.

## Practical Implementation Examples

These patterns demonstrate how to interact with the encryption layer in production code.

### Encrypting a Single API Key

```typescript
import { encrypt } from "@/lib/db/encryption";

const rawKey = "sk-abcdef123456";
const encryptedKey = encrypt(rawKey); 
// Returns: "enc:v1:a1b2c3...:x9y8z7...:tag..."

```

### Encrypting Connection Objects

The `encryptConnectionFields()` helper applies encryption to all credential fields within a connection object:

```typescript
import { encryptConnectionFields } from "@/lib/db/encryption";

const conn = {
  provider: "openai",
  apiKey: "sk-abcdef123456",
  accessToken: null,
};

encryptConnectionFields(conn);
// conn.apiKey now contains the encrypted string

```

### Decrypting After Database Retrieval

```typescript
import { decryptConnectionFields } from "@/lib/db/encryption";

const row = await db.getConnection(providerId);
const plaintextConn = decryptConnectionFields(row);
// plaintextConn.apiKey contains the original API key

```

### Running Legacy Migration Scripts

```typescript
import { migrateLegacyEncryptedString } from "@/lib/db/encryption";

function migrateRow(row) {
  const fields = ["apiKey", "accessToken", "refreshToken", "idToken"];
  let updated = false;

  for (const f of fields) {
    const val = row[f];
    if (val && val.startsWith("enc:v1:")) {
      const { updated: changed, value } = migrateLegacyEncryptedString(val);
      if (changed) {
        row[f] = value;
        updated = true;
      }
    }
  }
  return updated;
}

```

## Summary

- **Use AES-256-GCM** as implemented in [`src/lib/db/encryption.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/db/encryption.ts) to ensure both confidentiality and integrity of stored credentials.
- **Configure `STORAGE_ENCRYPTION_KEY`** with a high-entropy, base64-encoded 32-byte value before production deployment.
- **Leverage static salt derivation** (`scryptSync` with `STATIC_SALT`) to ensure consistent key generation across all operations.
- **Store values in the `enc:v1:` format** to enable version detection and future algorithm upgrades.
- **Utilize helper functions** like `encryptConnectionFields()` and `decryptConnectionFields()` to centralize encryption logic and prevent missed fields.
- **Migrate legacy data** using `migrateLegacyEncryptedString()` to bring older installations up to current security standards.

## Frequently Asked Questions

### What happens if I forget to set STORAGE_ENCRYPTION_KEY?

OmniRoute enters passthrough mode and stores credentials as plaintext while logging a warning. The application continues to function, but your API keys remain unencrypted in the SQLite database, exposing them to anyone with file system access.

### Why does OmniRoute use a static salt instead of a random one?

The static salt `"omniroute-field-encryption-v1"` ensures deterministic key derivation. This prevents key-mismatch bugs where the same password produces different keys across restarts, which was a critical issue in earlier dynamic-salt implementations. The versioned salt also acts as a schema marker for future upgrades.

### Can I rotate the encryption key without losing access to existing credentials?

Key rotation requires re-encrypting existing data. You must decrypt all values using the old key and re-encrypt them with the new key. The current codebase does not provide automatic key rotation; you would need to implement a migration script that reads all rows, decrypts them with the current key, changes the environment variable, and re-encrypts them with the new key.

### How does the system prevent tampering with encrypted API keys?

AES-256-GCM includes authentication tags that are verified during decryption. If an attacker modifies the ciphertext, the authentication tag validation fails, and the decryption function rejects the tampered data. This prevents tag-truncation attacks and other forgeries that could compromise the credential database.