Secure API Key Generation
Secure API Key Generation
API key generation requires cryptographic randomness to ensure keys cannot be predicted or guessed. Weak key generation using predictable sources like timestamps or sequential numbers enables attackers to enumerate valid keys. Strong key generation uses cryptographically secure random number generators and sufficient entropy.
# Python secure API key generation
import secrets
import string
import hashlib
import hmac
from datetime import datetime, timedelta
from typing import Dict, Optional, Tuple
import base64
class APIKeyGenerator:
def __init__(self, signing_secret: str):
self.signing_secret = signing_secret.encode()
def generate_api_key(self, key_length: int = 32) -> str:
"""Generate a cryptographically secure API key"""
# Use secrets module for cryptographic randomness
alphabet = string.ascii_letters + string.digits
key = ''.join(secrets.choice(alphabet) for _ in range(key_length))
return key
def generate_prefixed_key(self, prefix: str = "sk_live") -> str:
"""Generate key with prefix for easy identification"""
random_part = self.generate_api_key(32)
return f"{prefix}_{random_part}"
def generate_signed_key(self, user_id: str, permissions: List[str]) -> Tuple[str, str]:
"""Generate a signed API key with embedded metadata"""
# Create key payload
payload = {
'user_id': user_id,
'permissions': permissions,
'created_at': datetime.utcnow().isoformat(),
'key_id': secrets.token_hex(8)
}
# Serialize payload
payload_json = json.dumps(payload, sort_keys=True)
payload_bytes = payload_json.encode()
# Create signature
signature = hmac.new(
self.signing_secret,
payload_bytes,
hashlib.sha256
).digest()
# Combine payload and signature
key_data = payload_bytes + signature
# Encode as URL-safe base64
api_key = base64.urlsafe_b64encode(key_data).decode().rstrip('=')
return api_key, payload['key_id']
def verify_signed_key(self, api_key: str) -> Optional[Dict]:
"""Verify and extract data from signed API key"""
try:
# Decode from base64
key_data = base64.urlsafe_b64decode(api_key + '===')
# Split payload and signature
payload_bytes = key_data[:-32] # SHA256 produces 32 bytes
provided_signature = key_data[-32:]
# Verify signature
expected_signature = hmac.new(
self.signing_secret,
payload_bytes,
hashlib.sha256
).digest()
if not hmac.compare_digest(provided_signature, expected_signature):
return None
# Parse payload
payload = json.loads(payload_bytes.decode())
# Check expiration if implemented
created_at = datetime.fromisoformat(payload['created_at'])
if datetime.utcnow() - created_at > timedelta(days=365):
return None
return payload
except Exception:
return None
def generate_hierarchical_keys(self, master_key: str, scope: str) -> str:
"""Generate scoped keys from master key"""
# Derive scoped key using HMAC
scoped_key = hmac.new(
master_key.encode(),
scope.encode(),
hashlib.sha256
).hexdigest()
return f"sk_{scope}_{scoped_key[:32]}"
# Database model for API keys
from sqlalchemy import Column, String, DateTime, Boolean, JSON
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class APIKey(Base):
__tablename__ = 'api_keys'
id = Column(String, primary_key=True)
key_hash = Column(String, nullable=False, index=True) # Never store plain keys
user_id = Column(String, nullable=False, index=True)
name = Column(String)
permissions = Column(JSON)
created_at = Column(DateTime, default=datetime.utcnow)
last_used_at = Column(DateTime)
expires_at = Column(DateTime)
is_active = Column(Boolean, default=True)
@staticmethod
def hash_key(api_key: str) -> str:
"""Hash API key for storage"""
return hashlib.sha256(api_key.encode()).hexdigest()
Key format and structure impact both security and usability. Prefixed keys like "sk_live_abc123" help developers identify key types and environments at a glance, reducing the risk of using wrong keys. Including checksums in keys can detect typos before API requests are made. However, avoid encoding sensitive information directly in keys, as they are often logged or exposed.