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

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 (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 (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 (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 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 (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 (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:

// Setting the 256-bit encryption key (64 hex characters)
// Typically configured via container secrets or .env files
ENCRYPTION_KEY=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
// 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'
  })
});
// 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);
// 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, 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 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 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.

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 →