OmniRoute's Encryption Practices for API Keys at Rest: AES-256-GCM Field-Level Security

OmniRoute protects provider credentials using AES-256-GCM field-level encryption with scrypt-derived keys, storing secrets in SQLite with automatic legacy migration and graceful fallback mechanisms.

The open-source OmniRoute repository (diegosouzapw/OmniRoute) implements comprehensive at-rest encryption for sensitive API keys and tokens. All cryptographic operations reside in src/lib/db/encryption.ts, which enforces authenticated encryption while maintaining backward compatibility with older data formats.

Core Encryption Architecture

OmniRoute's security model centers on field-level encryption applied individually to credential fields rather than full-database encryption.

AES-256-GCM Implementation

The system uses the AES-256-GCM authenticated encryption algorithm as defined by the constants in src/lib/db/encryption.ts:

const ALGORITHM = "aes-256-gcm";
const AUTH_TAG_LENGTH = 16;

These settings enforce Galois/Counter Mode with a full 128-bit authentication tag. According to the source code, this configuration provides both confidentiality and integrity protection, preventing tampering attacks such as tag-truncation forgeries.

Key Derivation with scrypt

The encryption key derives from the STORAGE_ENCRYPTION_KEY environment variable using scryptSync:

const STATIC_SALT = "omniroute-field-encryption-v1";
const derivedKey = scryptSync(secret, STATIC_SALT, KEY_LENGTH);

The static, versioned salt ensures consistent key derivation across all operations. This approach eliminates key-mismatch bugs that occurred with previous dynamic-salt implementations.

Field-Level Encryption Workflow

Individual credential fields undergo encryption before persistence in the SQLite database.

Storage Format and Versioning

Encrypted values follow a structured string format:


enc:v1:<iv_hex>:<ciphertext_hex>:<authTag_hex>

This prefix system enables the codebase to detect encrypted values at runtime and supports future format upgrades without breaking existing data.

Double-Encryption Prevention

The encrypt() function includes a short-circuit check that prevents redundant encryption operations. According to lines 20-22 in src/lib/db/encryption.ts, if the plaintext already starts with "enc:v1:", the function returns the input unchanged. This safeguard prevents malformed ciphertexts and unnecessary CPU consumption.

Backward Compatibility and Migration

OmniRoute maintains support for legacy encryption schemes while automating migration to current standards.

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 derivation. As implemented in lines 90-98, the decryption logic tries the static key initially, and only attempts the legacy key if the first attempt fails.

Automatic Migration Utilities

The migrateLegacyEncryptedString() function detects outdated encryption formats and re-encrypts them using the current static-key method. This utility ensures all stored secrets eventually conform to the latest security scheme without manual intervention.

Configuration and Environment Setup

Proper configuration requires setting a high-entropy encryption key before runtime.

STORAGE_ENCRYPTION_KEY Requirements

OmniRoute expects a base64-encoded 32-byte value in the STORAGE_ENCRYPTION_KEY environment variable. Generate a compliant key using:

export STORAGE_ENCRYPTION_KEY=$(openssl rand -base64 32)

The application reads process.env.STORAGE_ENCRYPTION_KEY during initialization to derive the master encryption key.

Graceful Degradation Behavior

If STORAGE_ENCRYPTION_KEY is missing, the encryption module falls back to passthrough mode (plaintext storage) and logs a warning. Lines 62-64 in src/lib/db/encryption.ts implement this fallback to prevent application crashes while clearly signaling misconfiguration through log output.

Practical Implementation Examples

Implement field-level encryption using the helper functions exported from the encryption module.

Encrypting Connection Credentials

Use encryptConnectionFields() to secure an entire connection object before database storage:

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

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

encryptConnectionFields(connection);
// connection.apiKey now contains "enc:v1:..." ciphertext

Decrypting Retrieved Records

After fetching from the database, decrypt fields using decryptConnectionFields():

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

const row = await db.getConnection(providerId);
const plaintextConnection = decryptConnectionFields(row);
// plaintextConnection.apiKey restored to original value

Migrating Legacy Data

Execute a one-time migration script to upgrade old encryption formats:

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

function migrateRow(row) {
  const fields = ["apiKey", "accessToken", "refreshToken"];
  let updated = false;
  
  for (const field of fields) {
    const value = row[field];
    if (value?.startsWith("enc:v1:")) {
      const result = migrateLegacyEncryptedString(value);
      if (result.updated) {
        row[field] = result.value;
        updated = true;
      }
    }
  }
  return updated;
}

Summary

  • OmniRoute implements AES-256-GCM authenticated encryption for API keys stored in SQLite, with all logic contained in src/lib/db/encryption.ts.
  • scrypt key derivation uses a static, versioned salt ("omniroute-field-encryption-v1") to ensure consistent encryption keys across sessions.
  • The enc:v1: prefix format enables version detection and prevents double-encryption while supporting future upgrades.
  • Graceful degradation to passthrough mode occurs when STORAGE_ENCRYPTION_KEY is unset, preventing crashes but logging warnings.
  • Backward compatibility functions support legacy dynamic-salt encryption while migrateLegacyEncryptedString() automates upgrades to the current static-key format.
  • Field-level helpers encryptConnectionFields() and decryptConnectionFields() centralize credential protection across the application's database layer.

Frequently Asked Questions

What encryption algorithm does OmniRoute use for API keys?

OmniRoute uses AES-256-GCM (Galois/Counter Mode) with a 16-byte authentication tag. This authenticated encryption mode provides both confidentiality and integrity verification, preventing attackers from tampering with encrypted credentials without detection.

How does OmniRoute derive the encryption key from the environment variable?

The system passes the STORAGE_ENCRYPTION_KEY environment variable through scryptSync with a static salt value of "omniroute-field-encryption-v1". This produces a deterministic 32-byte derived key that remains consistent across application restarts, avoiding key-mismatch issues present in earlier dynamic-salt implementations.

What happens if I don't set the STORAGE_ENCRYPTION_KEY environment variable?

If the encryption key is missing, OmniRoute enters passthrough mode and stores API keys in plaintext while emitting a warning log. The application continues running to maintain availability, but this configuration exposes sensitive credentials and should only occur during development or misconfiguration scenarios.

Can OmniRoute decrypt API keys encrypted with older versions of the software?

Yes. The decryption logic first attempts verification with the current static-derived key, then automatically falls back to the legacy dynamic-key derivation method. The migrateLegacyEncryptedString() utility can re-encrypt legacy data with the current scheme, ensuring seamless upgrades without losing access to existing stored credentials.

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 →