Files
chatbot_v3/src/application/auth/keys.py

40 lines
1.4 KiB
Python

"""API-key generation and hashing (ADR-0008, ADR-0009).
Keys are `sk_{prefix}_{secret}`. `prefix` is non-secret and indexed
(`api_keys.key_prefix`); `secret` is 256 bits of `secrets.token_urlsafe`
entropy, stored only as a SHA-256 hash. A random 256-bit secret does not
benefit from a slow password-hashing KDF the way a human-chosen password
does — the cost that defends against dictionary/brute-force guessing over a
low-entropy input has nothing to defend here, and would only tax every
request. Comparison is constant-time to avoid a hash-timing oracle.
"""
import hashlib
import hmac
import secrets
_PREFIX_LENGTH = 16
def generate_api_key() -> tuple[str, str, str]:
"""Return `(key_prefix, secret, full_key)` for a newly issued key."""
key_prefix = secrets.token_hex(_PREFIX_LENGTH // 2)
secret = secrets.token_urlsafe(32)
return key_prefix, secret, f"sk_{key_prefix}_{secret}"
def parse_api_key(full_key: str) -> tuple[str, str] | None:
"""Return `(key_prefix, secret)`, or `None` if the token is malformed."""
parts = full_key.split("_", 2)
if len(parts) != 3 or parts[0] != "sk" or not parts[1] or not parts[2]:
return None
return parts[1], parts[2]
def hash_secret(secret: str) -> str:
return hashlib.sha256(secret.encode("utf-8")).hexdigest()
def verify_secret(secret: str, key_hash: str) -> bool:
return hmac.compare_digest(hash_secret(secret), key_hash)