# How Provider API Keys Are Encrypted at Rest Using AES-256-GCM in OmniRoute's SQLite Database

> Learn how OmniRoute secures provider API keys at rest with AES-256-GCM encryption in its SQLite database. Discover the encryption methods and key management. Read more now.

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

---

**OmniRoute encrypts sensitive provider credentials using AES-256-GCM in [`src/lib/db/encryption.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/db/encryption.ts), deriving a 32-byte master key from the environment variable `OMNIROUTE_DB_ENCRYPTION_KEY` and storing the IV, ciphertext, and auth tag concatenated as a BLOB in SQLite.**

OmniRoute is an open-source routing layer that stores provider API keys, OAuth tokens, and client secrets in a local SQLite database. To ensure these credentials remain secure even if the database file is compromised, the project implements **AES-256-GCM** encryption at rest according to the source code in `diegosouzapw/OmniRoute`. This article explains the complete encryption workflow, from the low-level crypto primitives to the high-level database operations that automatically secure your connection strings.

## The AES-256-GCM Encryption Architecture

### Core Encryption Module

The encryption logic lives in [`src/lib/db/encryption.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/db/encryption.ts). This module exports two low-level helpers—`encryptValue` and `decryptValue`—that wrap Node.js's native `crypto` API to provide authenticated encryption using the `aes-256-gcm` algorithm.

Every encryption operation generates a random 12-byte initialization vector (IV) via `crypto.randomBytes(12)`. GCM mode produces a 16-byte authentication tag that provides integrity checking. The final stored value is a binary BLOB containing the concatenation: `IV‖ciphertext‖authTag`.

### Key Management and Environment Configuration

The master encryption key is sourced from the environment variable `OMNIROUTE_DB_ENCRYPTION_KEY`. This key must be exactly **32 bytes** (256 bits) to match the AES-256 key size requirement. The application validates this key at startup before initializing the database connection, ensuring the encryption layer is ready before any provider credentials are persisted.

### High-Level Field Encryption

The module also exports `encryptConnectionFields` and `decryptConnectionFields`, which walk through provider connection objects and target fields marked as sensitive—such as `apiKey`, `clientSecret`, and `oauthToken`. These high-level helpers replace plaintext secrets with encrypted BLOBs before the record reaches the database.

## Database Integration Layer

### Encrypting Fields Before Storage

When inserting a new provider connection, the database layer in [`src/lib/db/core.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/db/core.ts) invokes the encryption helper to transform sensitive fields:

```typescript
// src/lib/db/core.ts
import { encryptConnectionFields } from './encryption.js';

export function insertProviderConnection(conn: ProviderConnection) {
  const encrypted = encryptConnectionFields(conn);
  const stmt = db.prepare(`
    INSERT INTO provider_connections (providerId, modelId, encryptedFields)
    VALUES (?, ?, ?)
  `);
  stmt.run(conn.providerId, conn.modelId, encrypted);
}

```

The raw API keys never touch the SQLite file; they are replaced by the encrypted BLOB containing the IV, ciphertext, and authentication tag.

### Transparent Decryption on Retrieval

When reading records back, the reverse process occurs automatically. The `decryptConnectionFields` function decrypts the BLOBs to plaintext strings before returning the object to the application code:

```typescript
// src/lib/db/core.ts
import { decryptConnectionFields } from './encryption.js';

export function getProviderConnection(id: number) {
  const row = db.prepare('SELECT * FROM provider_connections WHERE id = ?').get(id);
  return decryptConnectionFields(row);
}

```

## Practical Implementation Examples

### Inserting a Provider with an API Key

```typescript
import { insertProviderConnection } from '@/lib/db/core';
import { ProviderConnection } from '@/lib/db/types';

const conn: ProviderConnection = {
  providerId: 'openai',
  modelId: 'gpt-4o',
  apiKey: 'sk-your-secret-key', // Automatically encrypted
  clientSecret: 'oauth-secret'    // Automatically encrypted
};

await insertProviderConnection(conn);
// The API key is stored as an encrypted BLOB; plaintext never touches disk

```

### Retrieving Decrypted Credentials

```typescript
import { getProviderConnection } from '@/lib/db/core';

const stored = await getProviderConnection(42);
console.log(stored.apiKey); // Plaintext: 'sk-your-secret-key'

```

### Manual Encryption Operations

```typescript
import { encryptValue, decryptValue } from '@/lib/db/encryption';

const secret = 'sensitive-data';
const encrypted: Buffer = encryptValue(secret); // Returns Buffer(IV‖cipher‖tag)
const decrypted: string = decryptValue(encrypted); // Returns 'sensitive-data'

```

## Security Benefits of GCM Mode

AES-256-GCM provides **authenticated encryption**, meaning any tampering with the stored ciphertext will cause decryption to fail. This integrity protection is validated in [`tests/unit/db-encryption.test.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/tests/unit/db-encryption.test.ts), which ensures that corrupted data or modified auth tags trigger exceptions that prevent the application from using compromised credentials.

## Summary

- OmniRoute uses **AES-256-GCM** authenticated encryption for all provider credentials stored in SQLite
- The encryption module at [`src/lib/db/encryption.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/db/encryption.ts) handles low-level crypto operations via `encryptValue` and `decryptValue`
- A 32-byte master key from `OMNIROUTE_DB_ENCRYPTION_KEY` drives the encryption process
- Data is stored as BLOBs containing `IV‖ciphertext‖authTag` to ensure both confidentiality and integrity
- The database layer in [`src/lib/db/core.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/db/core.ts) automatically encrypts on write and decrypts on read via `encryptConnectionFields` and `decryptConnectionFields`

## Frequently Asked Questions

### What happens if the OMNIROUTE_DB_ENCRYPTION_KEY is lost?

If the master key is lost or changed, existing encrypted data becomes permanently inaccessible. OmniRoute does not implement key recovery or escrow mechanisms; you must re-enter all provider credentials through the configuration interface to regenerate encrypted entries with the new key.

### Why does OmniRoute use AES-256-GCM specifically?

**AES-256-GCM** provides both confidentiality and authentication in a single cryptographic operation. The 16-byte authentication tag protects against tampering attacks where an attacker might modify the encrypted database file to inject malicious credentials, ensuring the application detects any modification before using the decrypted values.

### Are all fields in the database encrypted?

No. Only sensitive fields explicitly marked as secrets—such as `apiKey`, `clientSecret`, and `oauthToken`—are encrypted according to the schema definitions in [`src/lib/db/providers.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/db/providers.ts). Metadata fields like `providerId` and `modelId` remain in plaintext to support indexing, querying, and foreign key relationships without decryption overhead.

### How is the encryption key protected in memory?

The key is loaded once at startup from the environment variable and held in memory by the Node.js process. OmniRoute relies on the host operating system's memory protection mechanisms and does not write the key to logs, stack traces, or crash dumps. Process memory isolation prevents other applications from accessing the key while the OmniRoute service is running.