# ADR-010: API keys vs client secrets — direction matters - **Status**: Accepted - **Date**: 2026-04-19 - **Deciders**: alkdev ## Context Both api_keys and client_secrets store authentication credentials, but they serve opposite directions. ## Decision Keep as separate tables with different security models. api_keys: keys WE issue so others can call US (hub auth). Managed by keypal. Stored as SHA-256 hashes. client_secrets: keys OTHERS issue so we can call THEM (outbound auth). Managed by us. Stored as AES-256-GCM encrypted values. Never mix — a hashed client secret is useless (we can't send it), an encryptable API key defeats the purpose of hashing. ## SHA-256 vs KDF trade-off API keys are hashed with SHA-256, not a deliberately slow KDF (bcrypt, Argon2). This is acceptable because: 1. API keys are high-entropy machine-generated strings (128-bit+). With 2^128 key space, brute-force is infeasible regardless of hash speed — there are not enough keys to make a dictionary attack viable. 2. SHA-256 provides O(1) verification latency at high throughput, which matters for every API request. 3. Slow KDFs exist to protect low-entropy human passwords (where rate-limiting cannot compensate for small key space). Machine-generated keys do not have this weakness. If the database is compromised, the attacker has the SHA-256 hashes but cannot reverse them without enumerating the key space — which is computationally infeasible for 128-bit+ random keys. ## Consequences Positive: clear security model per direction, appropriate crypto per use case, no confusion about how credentials are stored. Negative: two tables instead of one, but the security models are fundamentally incompatible so merging would be wrong.