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.