implementing-api-key-security-controls

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Implementing API Key Security Controls

实现 API Key 安全控制

When to Use

使用场景

  • Designing secure API key generation with sufficient entropy and identifiable prefixes for leak detection
  • Implementing server-side API key hashing (never storing keys in plaintext) with SHA-256 or bcrypt
  • Building key rotation workflows that allow zero-downtime key replacement for API consumers
  • Configuring per-key scoping to limit each API key to specific endpoints, IP ranges, and rate limits
  • Setting up automated monitoring for API key leakage in GitHub repos, logs, and client-side code
Do not use API keys as the sole authentication mechanism for user-facing applications. API keys are best suited for server-to-server communication and developer access.
  • 设计具有足够熵且包含可识别前缀的安全 API Key 生成方案,以便检测泄露
  • 使用 SHA-256 或 bcrypt 在服务端对 API Key 进行哈希处理(绝不以明文存储 Key)
  • 构建 Key Rotation 工作流,允许 API 使用方零停机替换 Key
  • 配置按 Key 的作用域,将每个 API Key 限制到特定端点、IP 范围和速率限制
  • 设置自动化监控,检测 GitHub 仓库、日志和客户端代码中的 API Key 泄露
请勿使用 API Key 作为面向用户的应用的唯一身份验证机制。API Key 最适合用于服务器到服务器通信和开发者访问。

Prerequisites

前置条件

  • Secure random number generator (os.urandom, secrets module) for key generation
  • Database with proper encryption at rest for storing hashed API keys
  • Redis or similar store for key-to-metadata caching and rate limiting
  • Secret scanning tools (GitHub secret scanning, truffleHog, gitleaks)
  • Monitoring and alerting infrastructure for key usage anomalies
  • 用于 Key 生成的安全随机数生成器(os.urandom、secrets 模块)
  • 具备适当静态加密的数据库,用于存储经过哈希处理的 API Key
  • Redis 或类似存储,用于 Key 到元数据的缓存及速率限制
  • Secret Scanning 工具(GitHub Secret Scanning、truffleHog、gitleaks)
  • 用于 Key 使用异常监控和告警的基础设施

Workflow

工作流程

Step 1: Secure API Key Generation

步骤 1:安全生成 API Key

python
import secrets
import hashlib
import hmac
import time
import json
from datetime import datetime, timedelta

class APIKeyManager:
    """Manages secure API key lifecycle: generation, storage, validation, rotation."""

    # Key format: prefix_base64random (e.g., sk_live_a1b2c3d4e5f6...)
    # Prefix identifies the key type and environment for leak detection
    KEY_PREFIXES = {
        "live_secret": "sk_live_",
        "test_secret": "sk_test_",
        "live_public": "pk_live_",
        "test_public": "pk_test_",
    }

    def __init__(self, db_connection, redis_connection):
        self.db = db_connection
        self.redis = redis_connection

    def generate_key(self, key_type="live_secret", owner_id=None, scopes=None,
                     rate_limit=None, ip_allowlist=None, expires_days=365):
        """Generate a new API key with metadata."""
        prefix = self.KEY_PREFIXES.get(key_type, "sk_live_")

        # Generate 32 bytes (256 bits) of randomness
        random_bytes = secrets.token_bytes(32)
        key_body = secrets.token_urlsafe(32)  # Base64url-encoded

        # Full API key that the client receives (shown only once)
        full_key = f"{prefix}{key_body}"

        # Hash the key for storage (never store the raw key)
        key_hash = hashlib.sha256(full_key.encode()).hexdigest()

        # Create a short key ID for reference (first 8 chars)
        key_id = f"{prefix}{key_body[:8]}..."

        # Store the hashed key with metadata
        key_metadata = {
            "key_hash": key_hash,
            "key_id": key_id,
            "key_type": key_type,
            "owner_id": owner_id,
            "scopes": scopes or ["read"],
            "rate_limit": rate_limit or {"requests": 1000, "window": 3600},
            "ip_allowlist": ip_allowlist or [],
            "created_at": datetime.utcnow().isoformat(),
            "expires_at": (datetime.utcnow() + timedelta(days=expires_days)).isoformat(),
            "last_used": None,
            "is_active": True,
            "usage_count": 0,
        }

        # Store in database
        self.db.execute(
            "INSERT INTO api_keys (key_hash, key_id, metadata) VALUES (?, ?, ?)",
            (key_hash, key_id, json.dumps(key_metadata))
        )

        # Cache in Redis for fast validation
        self.redis.setex(
            f"apikey:{key_hash}",
            86400,  # 24-hour cache TTL
            json.dumps(key_metadata)
        )

        return {
            "api_key": full_key,       # Show to user ONCE
            "key_id": key_id,          # For reference/management
            "scopes": key_metadata["scopes"],
            "expires_at": key_metadata["expires_at"],
        }

    def validate_key(self, api_key):
        """Validate an API key and return its metadata."""
        key_hash = hashlib.sha256(api_key.encode()).hexdigest()

        # Check Redis cache first
        cached = self.redis.get(f"apikey:{key_hash}")
        if cached:
            metadata = json.loads(cached)
        else:
            # Fall back to database
            row = self.db.execute(
                "SELECT metadata FROM api_keys WHERE key_hash = ?",
                (key_hash,)
            ).fetchone()
            if not row:
                return None, "invalid_key"
            metadata = json.loads(row[0])
            # Refresh cache
            self.redis.setex(f"apikey:{key_hash}", 86400, row[0])

        # Validation checks
        if not metadata.get("is_active"):
            return None, "key_revoked"

        if metadata.get("expires_at"):
            if datetime.fromisoformat(metadata["expires_at"]) < datetime.utcnow():
                return None, "key_expired"

        # Update last used
        metadata["last_used"] = datetime.utcnow().isoformat()
        metadata["usage_count"] = metadata.get("usage_count", 0) + 1
        self.redis.setex(f"apikey:{key_hash}", 86400, json.dumps(metadata))

        return metadata, "valid"

    def revoke_key(self, key_id):
        """Immediately revoke an API key."""
        row = self.db.execute(
            "SELECT key_hash, metadata FROM api_keys WHERE key_id = ?",
            (key_id,)
        ).fetchone()
        if row:
            key_hash = row[0]
            metadata = json.loads(row[1])
            metadata["is_active"] = False
            metadata["revoked_at"] = datetime.utcnow().isoformat()

            self.db.execute(
                "UPDATE api_keys SET metadata = ? WHERE key_id = ?",
                (json.dumps(metadata), key_id)
            )
            # Invalidate cache immediately
            self.redis.delete(f"apikey:{key_hash}")
            return True
        return False

    def rotate_key(self, old_key_id, grace_period_hours=24):
        """Rotate an API key with a grace period where both old and new keys work."""
        old_row = self.db.execute(
            "SELECT key_hash, metadata FROM api_keys WHERE key_id = ?",
            (old_key_id,)
        ).fetchone()
        if not old_row:
            return None, "key_not_found"

        old_metadata = json.loads(old_row[1])

        # Generate new key with same settings
        new_key_data = self.generate_key(
            key_type=old_metadata["key_type"],
            owner_id=old_metadata["owner_id"],
            scopes=old_metadata["scopes"],
            rate_limit=old_metadata["rate_limit"],
            ip_allowlist=old_metadata["ip_allowlist"],
        )

        # Schedule old key revocation after grace period
        revoke_at = datetime.utcnow() + timedelta(hours=grace_period_hours)
        old_metadata["scheduled_revocation"] = revoke_at.isoformat()
        self.db.execute(
            "UPDATE api_keys SET metadata = ? WHERE key_id = ?",
            (json.dumps(old_metadata), old_key_id)
        )

        return {
            "new_key": new_key_data,
            "old_key_id": old_key_id,
            "old_key_revokes_at": revoke_at.isoformat(),
            "message": f"Old key will be revoked in {grace_period_hours} hours"
        }, "success"
python
import secrets
import hashlib
import hmac
import time
import json
from datetime import datetime, timedelta

class APIKeyManager:
    """Manages secure API key lifecycle: generation, storage, validation, rotation."""

    # Key format: prefix_base64random (e.g., sk_live_a1b2c3d4e5f6...)
    # Prefix identifies the key type and environment for leak detection
    KEY_PREFIXES = {
        "live_secret": "sk_live_",
        "test_secret": "sk_test_",
        "live_public": "pk_live_",
        "test_public": "pk_test_",
    }

    def __init__(self, db_connection, redis_connection):
        self.db = db_connection
        self.redis = redis_connection

    def generate_key(self, key_type="live_secret", owner_id=None, scopes=None,
                     rate_limit=None, ip_allowlist=None, expires_days=365):
        """Generate a new API key with metadata."""
        prefix = self.KEY_PREFIXES.get(key_type, "sk_live_")

        # Generate 32 bytes (256 bits) of randomness
        random_bytes = secrets.token_bytes(32)
        key_body = secrets.token_urlsafe(32)  # Base64url-encoded

        # Full API key that the client receives (shown only once)
        full_key = f"{prefix}{key_body}"

        # Hash the key for storage (never store the raw key)
        key_hash = hashlib.sha256(full_key.encode()).hexdigest()

        # Create a short key ID for reference (first 8 chars)
        key_id = f"{prefix}{key_body[:8]}..."

        # Store the hashed key with metadata
        key_metadata = {
            "key_hash": key_hash,
            "key_id": key_id,
            "key_type": key_type,
            "owner_id": owner_id,
            "scopes": scopes or ["read"],
            "rate_limit": rate_limit or {"requests": 1000, "window": 3600},
            "ip_allowlist": ip_allowlist or [],
            "created_at": datetime.utcnow().isoformat(),
            "expires_at": (datetime.utcnow() + timedelta(days=expires_days)).isoformat(),
            "last_used": None,
            "is_active": True,
            "usage_count": 0,
        }

        # Store in database
        self.db.execute(
            "INSERT INTO api_keys (key_hash, key_id, metadata) VALUES (?, ?, ?)",
            (key_hash, key_id, json.dumps(key_metadata))
        )

        # Cache in Redis for fast validation
        self.redis.setex(
            f"apikey:{key_hash}",
            86400,  # 24-hour cache TTL
            json.dumps(key_metadata)
        )

        return {
            "api_key": full_key,       # Show to user ONCE
            "key_id": key_id,          # For reference/management
            "scopes": key_metadata["scopes"],
            "expires_at": key_metadata["expires_at"],
        }

    def validate_key(self, api_key):
        """Validate an API key and return its metadata."""
        key_hash = hashlib.sha256(api_key.encode()).hexdigest()

        # Check Redis cache first
        cached = self.redis.get(f"apikey:{key_hash}")
        if cached:
            metadata = json.loads(cached)
        else:
            # Fall back to database
            row = self.db.execute(
                "SELECT metadata FROM api_keys WHERE key_hash = ?",
                (key_hash,)
            ).fetchone()
            if not row:
                return None, "invalid_key"
            metadata = json.loads(row[0])
            # Refresh cache
            self.redis.setex(f"apikey:{key_hash}", 86400, row[0])

        # Validation checks
        if not metadata.get("is_active"):
            return None, "key_revoked"

        if metadata.get("expires_at"):
            if datetime.fromisoformat(metadata["expires_at"]) < datetime.utcnow():
                return None, "key_expired"

        # Update last used
        metadata["last_used"] = datetime.utcnow().isoformat()
        metadata["usage_count"] = metadata.get("usage_count", 0) + 1
        self.redis.setex(f"apikey:{key_hash}", 86400, json.dumps(metadata))

        return metadata, "valid"

    def revoke_key(self, key_id):
        """Immediately revoke an API key."""
        row = self.db.execute(
            "SELECT key_hash, metadata FROM api_keys WHERE key_id = ?",
            (key_id,)
        ).fetchone()
        if row:
            key_hash = row[0]
            metadata = json.loads(row[1])
            metadata["is_active"] = False
            metadata["revoked_at"] = datetime.utcnow().isoformat()

            self.db.execute(
                "UPDATE api_keys SET metadata = ? WHERE key_id = ?",
                (json.dumps(metadata), key_id)
            )
            # Invalidate cache immediately
            self.redis.delete(f"apikey:{key_hash}")
            return True
        return False

    def rotate_key(self, old_key_id, grace_period_hours=24):
        """Rotate an API key with a grace period where both old and new keys work."""
        old_row = self.db.execute(
            "SELECT key_hash, metadata FROM api_keys WHERE key_id = ?",
            (old_key_id,)
        ).fetchone()
        if not old_row:
            return None, "key_not_found"

        old_metadata = json.loads(old_row[1])

        # Generate new key with same settings
        new_key_data = self.generate_key(
            key_type=old_metadata["key_type"],
            owner_id=old_metadata["owner_id"],
            scopes=old_metadata["scopes"],
            rate_limit=old_metadata["rate_limit"],
            ip_allowlist=old_metadata["ip_allowlist"],
        )

        # Schedule old key revocation after grace period
        revoke_at = datetime.utcnow() + timedelta(hours=grace_period_hours)
        old_metadata["scheduled_revocation"] = revoke_at.isoformat()
        self.db.execute(
            "UPDATE api_keys SET metadata = ? WHERE key_id = ?",
            (json.dumps(old_metadata), old_key_id)
        )

        return {
            "new_key": new_key_data,
            "old_key_id": old_key_id,
            "old_key_revokes_at": revoke_at.isoformat(),
            "message": f"Old key will be revoked in {grace_period_hours} hours"
        }, "success"

Step 2: API Key Validation Middleware

步骤 2:API Key 验证中间件

python
from flask import Flask, request, jsonify, g
from functools import wraps

app = Flask(__name__)

def require_api_key(required_scopes=None):
    """Middleware to validate API key and check scopes."""
    def decorator(f):
        @wraps(f)
        def wrapped(*args, **kwargs):
            # Extract API key from header
            api_key = request.headers.get("X-API-Key")
            if not api_key:
                # Also check Authorization: Bearer <key>
                auth_header = request.headers.get("Authorization", "")
                if auth_header.startswith("Bearer "):
                    api_key = auth_header[7:]

            if not api_key:
                return jsonify({"error": "missing_api_key"}), 401

            # Validate the key
            metadata, status = key_manager.validate_key(api_key)
            if status != "valid":
                return jsonify({"error": status}), 401

            # Check IP allowlist
            if metadata.get("ip_allowlist"):
                client_ip = request.remote_addr
                if client_ip not in metadata["ip_allowlist"]:
                    return jsonify({"error": "ip_not_allowed"}), 403

            # Check scopes
            if required_scopes:
                key_scopes = set(metadata.get("scopes", []))
                if not key_scopes.intersection(required_scopes):
                    return jsonify({"error": "insufficient_scope"}), 403

            # Attach metadata to request context
            g.api_key_metadata = metadata
            return f(*args, **kwargs)
        return wrapped
    return decorator

@app.route('/api/v1/data', methods=['GET'])
@require_api_key(required_scopes=["read", "admin"])
def get_data():
    return jsonify({"data": "sensitive information"})

@app.route('/api/v1/data', methods=['POST'])
@require_api_key(required_scopes=["write", "admin"])
def create_data():
    return jsonify({"created": True})
python
from flask import Flask, request, jsonify, g
from functools import wraps

app = Flask(__name__)

def require_api_key(required_scopes=None):
    """Middleware to validate API key and check scopes."""
    def decorator(f):
        @wraps(f)
        def wrapped(*args, **kwargs):
            # Extract API key from header
            api_key = request.headers.get("X-API-Key")
            if not api_key:
                # Also check Authorization: Bearer <key>
                auth_header = request.headers.get("Authorization", "")
                if auth_header.startswith("Bearer "):
                    api_key = auth_header[7:]

            if not api_key:
                return jsonify({"error": "missing_api_key"}), 401

            # Validate the key
            metadata, status = key_manager.validate_key(api_key)
            if status != "valid":
                return jsonify({"error": status}), 401

            # Check IP allowlist
            if metadata.get("ip_allowlist"):
                client_ip = request.remote_addr
                if client_ip not in metadata["ip_allowlist"]:
                    return jsonify({"error": "ip_not_allowed"}), 403

            # Check scopes
            if required_scopes:
                key_scopes = set(metadata.get("scopes", []))
                if not key_scopes.intersection(required_scopes):
                    return jsonify({"error": "insufficient_scope"}), 403

            # Attach metadata to request context
            g.api_key_metadata = metadata
            return f(*args, **kwargs)
        return wrapped
    return decorator

@app.route('/api/v1/data', methods=['GET'])
@require_api_key(required_scopes=["read", "admin"])
def get_data():
    return jsonify({"data": "sensitive information"})

@app.route('/api/v1/data', methods=['POST'])
@require_api_key(required_scopes=["write", "admin"])
def create_data():
    return jsonify({"created": True})

Step 3: Automated Key Leakage Detection

步骤 3:自动检测 Key 泄露

bash
undefined
bash
undefined

Scan GitHub repositories for leaked API keys using gitleaks

Scan GitHub repositories for leaked API keys using gitleaks

gitleaks detect --source=/path/to/repo --config=gitleaks.toml --report-path=leaks.json
gitleaks detect --source=/path/to/repo --config=gitleaks.toml --report-path=leaks.json

Custom gitleaks configuration for API key prefix detection

Custom gitleaks configuration for API key prefix detection

gitleaks.toml

gitleaks.toml

cat <<'EOF' [[rules]] id = "company-api-key-live" description = "Company Live API Key" regex = '''sk_live_[A-Za-z0-9_-]{32,}''' tags = ["api-key", "live", "critical"]
[[rules]] id = "company-api-key-test" description = "Company Test API Key" regex = '''sk_test_[A-Za-z0-9_-]{32,}''' tags = ["api-key", "test"]
[[rules]] id = "company-public-key" description = "Company Public API Key" regex = '''pk_live_[A-Za-z0-9_-]{32,}''' tags = ["api-key", "public"] EOF

```python
cat <<'EOF' [[rules]] id = "company-api-key-live" description = "Company Live API Key" regex = '''sk_live_[A-Za-z0-9_-]{32,}''' tags = ["api-key", "live", "critical"]
[[rules]] id = "company-api-key-test" description = "Company Test API Key" regex = '''sk_test_[A-Za-z0-9_-]{32,}''' tags = ["api-key", "test"]
[[rules]] id = "company-public-key" description = "Company Public API Key" regex = '''pk_live_[A-Za-z0-9_-]{32,}''' tags = ["api-key", "public"] EOF

```python

Automated leaked key revocation

Automated leaked key revocation

import json
def process_leaked_keys(leaks_file): """Automatically revoke API keys detected in public repositories.""" with open(leaks_file) as f: leaks = json.load(f)
for leak in leaks:
    key_match = leak.get("match", "")
    # Extract the key from the match
    for prefix in ["sk_live_", "sk_test_", "pk_live_"]:
        if prefix in key_match:
            start = key_match.index(prefix)
            potential_key = key_match[start:start+50]  # Max key length
            # Validate and revoke
            metadata, status = key_manager.validate_key(potential_key)
            if status == "valid":
                key_manager.revoke_key(metadata["key_id"])
                print(f"[REVOKED] Key {metadata['key_id']} leaked in {leak.get('file')}")
                # Notify the key owner
                notify_owner(metadata["owner_id"], metadata["key_id"], leak)
undefined
import json
def process_leaked_keys(leaks_file): """Automatically revoke API keys detected in public repositories.""" with open(leaks_file) as f: leaks = json.load(f)
for leak in leaks:
    key_match = leak.get("match", "")
    # Extract the key from the match
    for prefix in ["sk_live_", "sk_test_", "pk_live_"]:
        if prefix in key_match:
            start = key_match.index(prefix)
            potential_key = key_match[start:start+50]  # Max key length
            # Validate and revoke
            metadata, status = key_manager.validate_key(potential_key)
            if status == "valid":
                key_manager.revoke_key(metadata["key_id"])
                print(f"[REVOKED] Key {metadata['key_id']} leaked in {leak.get('file')}")
                # Notify the key owner
                notify_owner(metadata["owner_id"], metadata["key_id"], leak)
undefined

Key Concepts

核心概念

TermDefinition
API KeyA secret string used to authenticate API requests, typically passed in headers or query parameters
Key HashingStoring only the hash (SHA-256) of the API key in the database, never the plaintext key, similar to password hashing
Key RotationReplacing an API key with a new one while maintaining a grace period where both keys work, ensuring zero-downtime transition
Key ScopingLimiting each API key to specific endpoints, HTTP methods, IP ranges, and rate limits to minimize blast radius
Key PrefixAn identifiable prefix (e.g., sk_live_) that enables automated detection of leaked keys in logs, code, and public repositories
Secret ScanningAutomated monitoring of repositories, logs, and public sources for exposed API keys and credentials
术语定义
API Key用于验证 API 请求的机密字符串,通常通过请求头或查询参数传递
Key Hashing仅在数据库中存储 API Key 的哈希值(SHA-256),绝不存储明文 Key,机制类似密码哈希
Key Rotation将 API Key 替换为新 Key,同时保留新旧 Key 均可使用的宽限期,从而实现零停机过渡
Key Scoping将每个 API Key 限制到特定端点、HTTP 方法、IP 范围和速率限制,以尽量缩小安全事件的影响范围
Key Prefix可识别的前缀(例如
sk_live_
),用于在日志、代码和公共仓库中自动检测泄露的 Key
Secret Scanning自动监控代码仓库、日志和公共来源,检测暴露的 API Key 和凭据

Tools & Systems

工具与系统

  • GitHub Secret Scanning: Built-in GitHub feature that detects exposed secrets in repositories and alerts key providers
  • gitleaks: Open-source tool for detecting secrets in git repositories using customizable regex patterns
  • truffleHog: Secret scanning tool that searches entire git history for high-entropy strings and known secret patterns
  • HashiCorp Vault: Enterprise secret management system for API key storage, rotation, and dynamic credential generation
  • AWS Secrets Manager: Managed secret storage with automatic rotation support for API keys and credentials
  • GitHub Secret Scanning:GitHub 内置功能,用于检测代码仓库中暴露的 Secret,并向 Key 提供方发出警报
  • gitleaks:开源工具,使用可自定义的正则表达式检测 Git 仓库中的 Secret
  • truffleHog:Secret Scanning 工具,搜索完整的 Git 历史记录,查找高熵字符串和已知 Secret 模式
  • HashiCorp Vault:企业级 Secret 管理系统,用于 API Key 存储、轮换和动态凭据生成
  • AWS Secrets Manager:托管式 Secret 存储服务,支持 API Key 和凭据的自动轮换

Common Scenarios

常见场景

Scenario: API Key Security Program for Developer Platform

场景:面向开发者平台的 API Key 安全计划

Context: A developer platform provides public APIs authenticated with API keys. The platform has 10,000+ API consumers generating 50M+ requests per day. Keys are frequently leaked in public GitHub repositories.
Approach:
  1. Implement prefixed API keys (sk_live_, sk_test_) with 256-bit entropy for leak detection
  2. Store only SHA-256 hashes of keys in the database, cache validated keys in Redis
  3. Implement per-key scoping: each key restricted to specific endpoints, rate limits, and optional IP allowlists
  4. Build key rotation API with 24-hour grace period for seamless transitions
  5. Integrate with GitHub Secret Scanning to automatically detect and revoke leaked keys within minutes
  6. Run gitleaks in CI/CD pipelines to prevent key commits in first place
  7. Implement anomaly detection: alert on keys used from unusual IPs or with abnormal traffic patterns
  8. Add key expiration policy: all keys expire after 365 days with 30-day advance notification
Pitfalls:
  • Storing API keys in plaintext in the database (use SHA-256 hashing)
  • Using predictable or low-entropy key generation (use cryptographically secure random generators)
  • Not implementing key prefixes, making it impossible to identify leaked keys in automated scans
  • Allowing API keys in URL query parameters where they leak in logs, browser history, and Referer headers
  • Not implementing rate limiting per key, allowing a single compromised key to abuse the entire API
背景:开发者平台提供使用 API Key 验证的公共 API。该平台拥有超过 10,000 个 API 使用方,每天处理超过 5,000 万次请求。Key 经常泄露在公共 GitHub 仓库中。
方案:
  1. 实现带前缀的 API Key(
    sk_live_
    、
    sk_test_
    ),采用 256 位熵以便检测泄露
  2. 数据库中仅存储 Key 的 SHA-256 哈希值,并在 Redis 中缓存已验证的 Key
  3. 实现按 Key 的作用域:每个 Key 限制到特定端点和速率限制,并可选用 IP 允许列表
  4. 构建带 24 小时宽限期的 Key Rotation API,实现无缝过渡
  5. 与 GitHub Secret Scanning 集成,在数分钟内自动检测并吊销泄露的 Key
  6. 在 CI/CD 流水线中运行 gitleaks,从源头防止 Key 被提交
  7. 实现异常检测:当 Key 从异常 IP 使用或呈现异常流量模式时发出警报
  8. 添加 Key 过期策略:所有 Key 在 365 天后过期,并提前 30 天通知
常见问题:
  • 将 API Key 以明文存储在数据库中(应使用 SHA-256 哈希)
  • 使用可预测或低熵的 Key 生成方式(应使用密码学安全随机数生成器)
  • 未实现 Key 前缀,导致无法在自动扫描中识别泄露的 Key
  • 允许在 URL 查询参数中传递 API Key,这会导致 Key 泄露到日志、浏览器历史记录和 Referer 标头中
  • 未实现按 Key 的速率限制,使单个被盗 Key 得以滥用整个 API

Output Format

输出格式

undefined
undefined

API Key Security Implementation Report

API Key Security Implementation Report

Platform: Developer API v3 Total Active Keys: 12,450 Daily Key Validations: 52M
Platform: Developer API v3 Total Active Keys: 12,450 Daily Key Validations: 52M

Security Controls

Security Controls

ControlImplementationStatus
Key Entropy256-bit (secrets.token_urlsafe(32))Implemented
Key Formatsk_live_/sk_test_ prefixedImplemented
StorageSHA-256 hashed, Redis cachedImplemented
ScopingPer-key endpoint/IP/rate limitsImplemented
Rotation24-hour grace period APIImplemented
Expiration365-day max TTLImplemented
Leak DetectionGitHub Secret Scanning + gitleaksActive
Auto-RevocationLeaked keys revoked within 5 minActive
ControlImplementationStatus
Key Entropy256-bit (secrets.token_urlsafe(32))Implemented
Key Formatsk_live_/sk_test_ prefixedImplemented
StorageSHA-256 hashed, Redis cachedImplemented
ScopingPer-key endpoint/IP/rate limitsImplemented
Rotation24-hour grace period APIImplemented
Expiration365-day max TTLImplemented
Leak DetectionGitHub Secret Scanning + gitleaksActive
Auto-RevocationLeaked keys revoked within 5 minActive

Key Leakage Stats (Last 30 Days)

Key Leakage Stats (Last 30 Days)

  • Keys detected in public repos: 23
  • Average time to revocation: 3.2 minutes
  • Keys detected in CI/CD pre-commit: 7 (prevented)
undefined
  • Keys detected in public repos: 23
  • Average time to revocation: 3.2 minutes
  • Keys detected in CI/CD pre-commit: 7 (prevented)
undefined