Series: AI Systems at Scale · Part 4 of 5
BYOK AI SaaS Architecture: AES-256, Multi-Tenancy & LLM Adapters
How I architected Hureka AI — a BYOK (Bring Your Own Key) multi-tenant AI SaaS. AES-256 encryption for API keys, and a unified LLM adapter pattern for Anthropic, OpenAI, Google, and Ollama.
What is BYOK?
BYOK (Bring Your Own Key) means customers connect their own LLM API keys. Your SaaS platform never stores raw keys — instead you encrypt them before storage and decrypt only at runtime during inference.
This is critical for enterprise customers who have their own OpenAI Enterprise contracts, Anthropic API access, or want to run Ollama on-premise.
Encryption Architecture
import { createCipheriv, createDecipheriv, randomBytes } from 'crypto'const MASTER_KEY = Buffer.from(process.env.MASTER_ENCRYPTION_KEY!, 'hex')
export function encryptApiKey(plaintext: string): string { const iv = randomBytes(16) const cipher = createCipheriv('aes-256-gcm', MASTER_KEY, iv)
const encrypted = Buffer.concat([ cipher.update(plaintext, 'utf8'), cipher.final() ]) const authTag = cipher.getAuthTag()
return Buffer.concat([iv, authTag, encrypted]).toString('base64') }
export function decryptApiKey(ciphertext: string): string { const data = Buffer.from(ciphertext, 'base64') const iv = data.subarray(0, 16) const authTag = data.subarray(16, 32) const encrypted = data.subarray(32)
const decipher = createDecipheriv('aes-256-gcm', MASTER_KEY, iv) decipher.setAuthTag(authTag)
return Buffer.concat([decipher.update(encrypted), decipher.final()]).toString('utf8') } ```
The Unified LLM Adapter Pattern
interface LLMAdapter {
complete(messages: Message[], options: CompletionOptions): Promise<string>
stream(messages: Message[], options: CompletionOptions): AsyncGenerator<string>
}class AnthropicAdapter implements LLMAdapter { private client: Anthropic constructor(apiKey: string) { this.client = new Anthropic({ apiKey }) } async complete(messages: Message[]) { / ... / } }
function createAdapter(org: Organization): LLMAdapter { const key = org.llmProvider === 'ollama' ? '' : decryptApiKey(org.encryptedApiKey) switch (org.llmProvider) { case 'anthropic': return new AnthropicAdapter(key) case 'openai': return new OpenAIAdapter(key) case 'google': return new GoogleAdapter(key) case 'ollama': return new OllamaAdapter(org.ollamaUrl, org.ollamaModel) } } ```
Multi-Tenant Data Isolation
Every database query is filtered by organizationId via Prisma middleware:
prisma.$use(async (params, next) => {
if (params.action === 'findMany' || params.action === 'findFirst') {
params.args.where = {
...params.args.where,
organizationId: getCurrentOrganizationId()
}
}
return next(params)
})
Security Checklist for BYOK SaaS
- 1Master key rotation — Support rotating the master encryption key without re-encrypting all tenant keys at once.
- 2Audit every decryption — Log when, by whom, and for what purpose each key was decrypted.
- 3Short-lived decrypted values — Keep decrypted API keys in memory only for the duration of a single request; never persist them.
- 4Separate key storage — Store encrypted keys in your application database, but store the master key in a secrets manager (AWS Secrets Manager, Vault).