How Provider API Keys Are Encrypted and Stored in FreeLLMAPI
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, 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 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, stores secrets using three distinct columns that separate the cryptographic components:
encrypted_key: The AES-256-GCM ciphertextiv: 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, the server invokes the encrypt() utility from server/src/lib/crypto.ts. The resulting encrypted payload components are then atomically persisted to the database.
// 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.
// 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 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.
// 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 theapi_keystable according to the schema inserver/src/db/migrations/20260810_000001_api_key_proxy.ts. - The master encryption key is sourced from the
ENCRYPTION_KEYenvironment variable or a.encryption-keyfile, never from the database itself. - The
encrypt()anddecrypt()functions inserver/src/lib/crypto.tshandle all cryptographic operations using Node.js's nativecryptomodule. - Key rotation is supported via
server/src/scripts/rotate-encryption-key.tswithout 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.
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 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →