# How Provider API Keys Are Encrypted and Stored in FreeLLMAPI

> Discover how FreeLLMAPI encrypts provider API keys using AES-256-GCM. Learn about secure storage methods in the SQLite database and master key protection.

- Repository: [Tashfeen/freellmapi](https://github.com/tashfeenahmed/freellmapi)
- Tags: internals
- Published: 2026-09-04

---

**FreeLLMAPI encrypts every provider API key using AES-256-GCM authenticated encryption, storing the ciphertext alongside a unique initialization vector and authentication tag in the SQLite database while keeping the master encryption key outside the database.**

FreeLLMAPI is an open-source API gateway that aggregates multiple LLM providers while maintaining strict security standards for credential storage. Understanding how provider API keys are encrypted and stored in FreeLLMAPI is essential for security audits, compliance reviews, and self-hosted deployments. The implementation leverages Node.js native crypto modules with AES-256-GCM to ensure both confidentiality and integrity of sensitive credentials.

## Encryption Architecture and Key Management

### AES-256-GCM Implementation in crypto.ts

In [`server/src/lib/crypto.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/lib/crypto.ts), the core encryption logic uses Node.js's native `crypto` module to implement **AES-256-GCM**. When the `encrypt(plaintext)` function is invoked, it first generates a cryptographically secure 96-bit initialization vector (IV). It then initializes a cipher via `crypto.createCipheriv('aes-256-gcm', key, iv)` where `key` is the 256-bit master encryption key. The function returns three components: the **ciphertext** (encrypted data), the IV, and the **authentication tag** (authTag) which prevents tampering.

### Master Encryption Key Configuration

The server-wide encryption key is loaded at runtime from the `ENCRYPTION_KEY` environment variable or read from a `.encryption-key` file located adjacent to the SQLite database. This key must be exactly 256 bits (32 bytes) to satisfy AES-256 requirements. The `encryptionKeyFingerprint()` function in [`server/src/lib/crypto.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/lib/crypto.ts) generates a SHA-256 hash of the key for operational verification without exposing the key material itself.

## Database Schema for Secure Storage

The `api_keys` table, defined in [`server/src/db/migrations/20260810_000001_api_key_proxy.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/db/migrations/20260810_000001_api_key_proxy.ts), stores secrets using three distinct columns that separate the cryptographic components:

- `encrypted_key`: The AES-256-GCM ciphertext
- `iv`: The 96-bit initialization vector (12 bytes)
- `auth_tag`: The 128-bit GCM authentication tag (16 bytes)

This column separation ensures that raw database access alone cannot reconstruct the plaintext keys without the master encryption key stored outside the database.

## Encrypting New Provider Keys

When users submit a new API key through the web interface or the `POST /keys` endpoint handled in [`server/src/routes/keys.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/routes/keys.ts), the server invokes the `encrypt()` utility from [`server/src/lib/crypto.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/lib/crypto.ts). The resulting encrypted payload components are then atomically persisted to the database.

```typescript
// Adding a new provider key (simplified)
import { encrypt } from '../lib/crypto.js';

const { encrypted, iv, authTag } = encrypt(userProvidedKey);

db.prepare(`
  INSERT INTO api_keys (platform, label, encrypted_key, iv, auth_tag, status, enabled)
  VALUES (?, ?, ?, ?, ?, 'healthy', 1)
`).run(platform, label, encrypted, iv, authTag);

```

## Decrypting Keys for Outbound Requests

During LLM provider API calls, FreeLLMAPI retrieves the stored cryptographic components and reconstructs the original plaintext using the `decrypt()` function. This function verifies the authentication tag using GCM mode before returning the sensitive key, ensuring the data has not been tampered with since encryption.

```typescript
// Retrieving and using a stored key
import { decrypt } from '../lib/crypto.js';

const row = db.prepare(`
  SELECT encrypted_key, iv, auth_tag FROM api_keys WHERE id = ?
`).get(keyId);

const apiKey = decrypt(row.encrypted_key, row.iv, row.auth_tag);
// apiKey now holds the plaintext provider key for the outbound request

```

## Zero-Downtime Encryption Key Rotation

To support security policies requiring periodic key rotation, [`server/src/scripts/rotate-encryption-key.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/scripts/rotate-encryption-key.ts) provides a migration utility that operates without service interruption. The script iterates through all entries in the `api_keys` table, decrypts each secret using the old master key, and re-encrypts it with the new master key, generating fresh IVs and authentication tags for each record.

```typescript
// Rotating the master encryption key (script)
import { encryptWith, decryptWith } from './rotate-encryption-key.ts';

const oldKey = Buffer.from(process.argv[2], 'hex');
const newKey = Buffer.from(process.argv[3], 'hex');

// For each stored secret:
const plaintext = decryptWith(oldKey, enc, iv, authTag);
const reEncrypted = encryptWith(newKey, plaintext);

// Update DB with reEncrypted.encrypted, reEncrypted.iv, reEncrypted.authTag

```

## Monitoring Encryption Status

The `/status` endpoint exposes the current encryption key fingerprint via the `encryptionKeyFingerprint()` function to verify which master key is active. This allows administrators to confirm rotation success and configuration consistency across server instances without exposing actual key material.

## Summary

- FreeLLMAPI uses **AES-256-GCM** authenticated encryption for all provider API keys stored in the system.
- Secrets are stored in three separate columns (`encrypted_key`, `iv`, `auth_tag`) within the `api_keys` table according to the schema in [`server/src/db/migrations/20260810_000001_api_key_proxy.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/db/migrations/20260810_000001_api_key_proxy.ts).
- The master encryption key is sourced from the `ENCRYPTION_KEY` environment variable or a `.encryption-key` file, never from the database itself.
- The `encrypt()` and `decrypt()` functions in [`server/src/lib/crypto.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/lib/crypto.ts) handle all cryptographic operations using Node.js's native `crypto` module.
- Key rotation is supported via [`server/src/scripts/rotate-encryption-key.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/scripts/rotate-encryption-key.ts) without requiring downtime or exposing plaintext secrets.

## Frequently Asked Questions

### What encryption algorithm does FreeLLMAPI use?

FreeLLMAPI uses **AES-256-GCM** (Galois/Counter Mode) authenticated encryption. This algorithm provides both confidentiality through 256-bit encryption and integrity verification through a 128-bit authentication tag generated during the encryption process in [`server/src/lib/crypto.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/lib/crypto.ts).

### How is the master encryption key stored?

The master encryption key is never stored in the database. It is loaded at runtime from the `ENCRYPTION_KEY` environment variable or a `.encryption-key` file placed beside the SQLite database. Both sources must provide exactly 256 bits (32 bytes) to satisfy AES-256 requirements.

### Can I rotate the encryption key without downtime?

Yes. The [`server/src/scripts/rotate-encryption-key.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/scripts/rotate-encryption-key.ts) script enables zero-downtime rotation by re-encrypting all stored provider keys with a new master key while the server continues operating. The script decrypts entries using the old key and re-encrypts them with the new key, atomically updating the IV and authentication tag for each database record.

### Where are the encrypted provider keys stored?

Encrypted provider keys reside in the `api_keys` table within the application's SQLite database. The table separates the ciphertext, initialization vector, and authentication tag into distinct columns (`encrypted_key`, `iv`, `auth_tag`) to ensure that database compromise alone cannot yield plaintext secrets without the external master encryption key.