# How FreeLLMAPI Stores API Keys Securely: AES-256-GCM Encryption Implementation

> Discover how FreeLLMAPI secures API keys with AES-256-GCM encryption. Learn how plaintext credentials are never saved to disk, only decrypted in memory.

- Repository: [Tashfeen/freellmapi](https://github.com/tashfeenahmed/freellmapi)
- Tags: security
- Published: 2026-08-26

---

**FreeLLMAPI stores API keys using AES-256-GCM encryption derived from an environment variable, ensuring plaintext credentials never persist to disk and are only decrypted in memory when calling provider endpoints.**

FreeLLMAPI implements a zero-trust storage model for third-party LLM credentials, ensuring sensitive provider keys remain encrypted at rest within a SQLite database. By leveraging Node.js native crypto modules and a mandatory 256-bit encryption key, the system guarantees that raw API keys never touch the filesystem or logs. According to the tashfeenahmed/freellmapi source code, the encryption lifecycle spans from environment validation through backup integrity checks.

## The Encryption Architecture

### The ENCRYPTION_KEY Environment Variable

All cryptographic operations hinge on the **ENCRYPTION_KEY** environment variable, which must contain a 256-bit hex string. In [`server/src/lib/crypto.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/lib/crypto.ts) (lines 13-32), the application validates this key during startup, generating a temporary in-memory fallback only in development environments. Production deployments abort initialization if this variable is missing or malformed, preventing accidental storage of unencrypted credentials.

### AES-GCM Implementation

The platform utilizes **AES-256-GCM** (Galois/Counter Mode) via Node.js's native `crypto` module to provide authenticated encryption. The implementation in [`server/src/lib/crypto.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/lib/crypto.ts) (lines 90-119) generates a unique initialization vector (IV) and authentication tag for each key, ensuring both confidentiality and integrity. This approach prevents tampering with stored ciphertext while eliminating the need to store plaintext keys in memory longer than necessary.

## Data Flow: From Plaintext to Encrypted Storage

### Key Ingestion via the API

When users submit new credentials through the `POST /keys` endpoint defined in [`server/src/routes/keys.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/routes/keys.ts) (lines 152-214), the server immediately encrypts the payload before database insertion. The plaintext key exists only transiently within the request handler, after which it is transformed into an encrypted blob consisting of ciphertext, IV, and authentication tag.

### Database Schema and Storage

Encrypted credentials reside in the SQLite **api_keys** table alongside metadata fields such as `platform`, `label`, and `endpoint_url`. The schema dedicates three binary columns to cryptographic data: `encrypted_key` stores the ciphertext, `iv` holds the 16-byte initialization vector, and `auth_tag` contains the GCM authentication tag. This separation ensures that even with database access, attackers cannot reconstruct usable API keys without the master `ENCRYPTION_KEY`.

### Runtime Decryption

During provider requests, the server retrieves the encrypted blob and invokes the `decrypt` function from [`server/src/lib/crypto.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/lib/crypto.ts) using the same `ENCRYPTION_KEY`. If decryption fails—such as when the database was encrypted with a different master key—the error handling logic in [`server/src/routes/keys.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/routes/keys.ts) (lines 384-390) rejects the request with a clear error message, preventing silent failures that could expose malformed credentials.

## Security Controls and Production Hardening

### Development vs. Production Behavior

The system distinguishes between development and production environments through strict initialization checks. While development instances generate ephemeral encryption keys to facilitate local testing, production servers enforce the presence of `ENCRYPTION_KEY` at startup. This distinction ensures that production deployments cannot accidentally persist unencrypted keys due to missing configuration.

### Backup Integrity Verification

Database backups maintain the encrypted state of all credentials without requiring additional encryption layers. The backup routine in [`server/src/services/backups.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/services/backups.ts) (lines 436-443) verifies key fingerprints to ensure archives were created using the current `ENCRYPTION_KEY`. This verification prevents restoration scenarios where backup files contain ciphertext incompatible with the current encryption key, ensuring business continuity without compromising security.

## Implementation Examples

The following patterns demonstrate secure key handling within the FreeLLMAPI codebase:

```typescript
// Setting the 256-bit encryption key (64 hex characters)
// Typically configured via container secrets or .env files
ENCRYPTION_KEY=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef

```

```typescript
// Adding a provider key via the HTTP API
// The plaintext key is encrypted server-side before storage
await fetch(`${BASE_URL}/keys`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    platform: 'openai',
    label: 'production-key',
    key: 'sk-xxxxxxxxxxxxxxxxxxxx'
  })
});

```

```typescript
// Internal encryption flow (simplified from server/src/lib/crypto.ts)
import { encrypt } from './lib/crypto';

const { ciphertext, iv, tag } = encrypt(
  process.env.ENCRYPTION_KEY,
  plaintextKey
);

// Storage in SQLite
db.prepare(`
  INSERT INTO api_keys 
  (platform, label, encrypted_key, iv, auth_tag, status, enabled)
  VALUES (?, ?, ?, ?, ?, 'healthy', 1)
`).run(platform, label, ciphertext, iv, tag);

```

```typescript
// Decryption during provider request handling
import { decrypt } from './lib/crypto';

const { key } = decrypt(
  process.env.ENCRYPTION_KEY,
  row.encrypted_key,
  row.iv,
  row.auth_tag
);

```

## Summary

- **AES-256-GCM encryption** ensures API keys remain confidential and tamper-proof at rest within the SQLite database.
- The **ENCRYPTION_KEY** environment variable serves as the sole secret required for deployment, with production servers refusing to start if misconfigured.
- **Three-column storage** (`encrypted_key`, `iv`, `auth_tag`) in the `api_keys` table separates ciphertext from cryptographic parameters.
- **Runtime decryption** occurs only in memory during active provider requests, with plaintext keys never persisting to disk or logs.
- **Backup verification** routines confirm encryption key compatibility before declaring backup operations successful.

## Frequently Asked Questions

### What encryption algorithm does FreeLLMAPI use for API key storage?

FreeLLMAPI uses **AES-256-GCM** (Galois/Counter Mode) via Node.js's native `crypto` module. This provides both encryption and authentication, ensuring that tampered ciphertext is detected during decryption attempts.

### Where is the master encryption key stored?

The master encryption key resides exclusively in the **ENCRYPTION_KEY** environment variable. The system reads this 256-bit hex string at startup from [`server/src/lib/crypto.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/lib/crypto.ts), with production deployments aborting initialization if the variable is absent or invalid.

### Can database backups be restored to a different server instance?

Yes, provided the target server uses the **same ENCRYPTION_KEY** that encrypted the original data. The backup routine in [`server/src/services/backups.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/services/backups.ts) verifies key fingerprints to ensure compatibility, preventing restoration of archives encrypted with different master keys.

### How does FreeLLMAPI handle encryption key rotation?

While the source code does not implement automatic re-encryption, the modular design in [`server/src/lib/crypto.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/lib/crypto.ts) supports key rotation by allowing administrators to decrypt existing keys with the old `ENCRYPTION_KEY` and re-encrypt them with a new key through the standard API endpoints. The backup verification system helps identify archives requiring migration to new encryption keys.