| name | implementing-api-key-security-controls |
| description | 实施安全的API密钥生成、存储、轮换和吊销控制,防止API认证凭据泄露、暴力破解和滥用。 设计具有足够熵的API密钥格式,实施安全哈希存储,执行按密钥范围限制和速率限制, 监控公共仓库中的密钥泄露,并构建密钥轮换工作流。
|
| domain | cybersecurity |
| subdomain | api-security |
| tags | ["api-security","api-keys","credential-management","key-rotation","secret-management"] |
| version | 1.0.0 |
| author | mahipal |
| license | Apache-2.0 |
实施API密钥安全控制
适用场景
- 设计具有足够熵和可识别前缀的安全API密钥生成机制,用于泄露检测
- 实施服务端API密钥哈希存储(永不以明文存储密钥),使用SHA-256或bcrypt
- 构建支持零停机密钥替换的密钥轮换工作流
- 配置按密钥范围限制,将每个API密钥限制到特定端点、IP范围和速率限制
- 设置自动化监控,检测GitHub仓库、日志和客户端代码中的API密钥泄露
不适用将API密钥作为面向用户应用的唯一认证机制。API密钥最适合服务间通信和开发者访问。
前置条件
- 安全随机数生成器(os.urandom、secrets模块)用于密钥生成
- 具有静态加密功能的数据库用于存储哈希后的API密钥
- Redis或类似存储用于密钥到元数据的缓存和速率限制
- 密钥扫描工具(GitHub secret scanning、truffleHog、gitleaks)
- 用于密钥使用异常的监控和告警基础设施
工作流程
步骤1:安全API密钥生成
import secrets
import hashlib
import hmac
import time
import json
from datetime import datetime, timedelta
class APIKeyManager:
"""管理安全API密钥生命周期:生成、存储、验证、轮换。"""
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):
"""生成带元数据的新API密钥。"""
prefix = self.KEY_PREFIXES.get(key_type, "sk_live_")
random_bytes = secrets.token_bytes(32)
key_body = secrets.token_urlsafe(32)
full_key = f"{prefix}{key_body}"
key_hash = hashlib.sha256(full_key.encode()).hexdigest()
key_id =
key_metadata = {
: key_hash,
: key_id,
: key_type,
: owner_id,
: scopes [],
: rate_limit {: , : },
: ip_allowlist [],
: datetime.utcnow().isoformat(),
: (datetime.utcnow() + timedelta(days=expires_days)).isoformat(),
: ,
: ,
: ,
}
.db.execute(
,
(key_hash, key_id, json.dumps(key_metadata))
)
.redis.setex(
,
,
json.dumps(key_metadata)
)
{
: full_key,
: key_id,
: key_metadata[],
: key_metadata[],
}
():
key_hash = hashlib.sha256(api_key.encode()).hexdigest()
cached = .redis.get()
cached:
metadata = json.loads(cached)
:
row = .db.execute(
,
(key_hash,)
).fetchone()
row:
,
metadata = json.loads(row[])
.redis.setex(, , row[])
metadata.get():
,
metadata.get():
datetime.fromisoformat(metadata[]) < datetime.utcnow():
,
metadata[] = datetime.utcnow().isoformat()
metadata[] = metadata.get(, ) +
.redis.setex(, , json.dumps(metadata))
metadata,
():
row = .db.execute(
,
(key_id,)
).fetchone()
row:
key_hash = row[]
metadata = json.loads(row[])
metadata[] =
metadata[] = datetime.utcnow().isoformat()
.db.execute(
,
(json.dumps(metadata), key_id)
)
.redis.delete()
():
old_row = .db.execute(
,
(old_key_id,)
).fetchone()
old_row:
,
old_metadata = json.loads(old_row[])
new_key_data = .generate_key(
key_type=old_metadata[],
owner_id=old_metadata[],
scopes=old_metadata[],
rate_limit=old_metadata[],
ip_allowlist=old_metadata[],
)
revoke_at = datetime.utcnow() + timedelta(hours=grace_period_hours)
old_metadata[] = revoke_at.isoformat()
.db.execute(
,
(json.dumps(old_metadata), old_key_id)
)
{
: new_key_data,
: old_key_id,
: revoke_at.isoformat(),
:
},
步骤2:API密钥验证中间件
from flask import Flask, request, jsonify, g
from functools import wraps
app = Flask(__name__)
def require_api_key(required_scopes=None):
"""验证API密钥并检查范围的中间件。"""
def decorator(f):
@wraps(f)
def wrapped(*args, **kwargs):
api_key = request.headers.get("X-API-Key")
if not api_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
metadata, status = key_manager.validate_key(api_key)
if status != "valid":
return jsonify({"error": status}), 401
if metadata.get("ip_allowlist"):
client_ip = request.remote_addr
if client_ip not in metadata[]:
jsonify({: }),
required_scopes:
key_scopes = (metadata.get(, []))
key_scopes.intersection(required_scopes):
jsonify({: }),
g.api_key_metadata = metadata
f(*args, **kwargs)
wrapped
decorator
():
jsonify({: })
():
jsonify({: })
步骤3:自动化密钥泄露检测
gitleaks detect --source=/path/to/repo --config=gitleaks.toml --report-path=leaks.json
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
import json
def process_leaked_keys(leaks_file):
"""自动吊销在公共仓库中检测到的API密钥。"""
with open(leaks_file) as f:
leaks = json.load(f)
for leak in leaks:
key_match = leak.get("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]
metadata, status = key_manager.validate_key(potential_key)
if status == "valid":
key_manager.revoke_key(metadata["key_id"])
print(f"[已吊销] 密钥 {metadata['key_id']} 在 {leak.get('file')} 中泄露")
notify_owner(metadata["owner_id"], metadata["key_id"], leak)
核心概念
| 术语 | 定义 |
|---|
| API密钥(API Key) | 用于认证API请求的密钥字符串,通常通过请求头或查询参数传递 |
| 密钥哈希(Key Hashing) | 在数据库中只存储API密钥的哈希值(SHA-256),永不存储明文密钥,类似于密码哈希 |
| 密钥轮换(Key Rotation) | 在维持宽限期(新旧密钥均有效)的情况下将API密钥替换为新密钥,确保零停机过渡 |
| 密钥范围限制(Key Scoping) | 将每个API密钥限制到特定端点、HTTP方法、IP范围和速率限制,以最小化影响范围 |
| 密钥前缀(Key Prefix) | 可识别的前缀(如 sk_live_),使自动化扫描能够在日志、代码和公共仓库中检测泄露密钥 |
| 密钥扫描(Secret Scanning) | 自动监控仓库、日志和公共来源中暴露的API密钥和凭据 |
工具和系统
- GitHub Secret Scanning:内置的GitHub功能,检测仓库中暴露的密钥并提醒密钥提供者
- gitleaks:使用可定制正则模式检测git仓库中密钥的开源工具
- truffleHog:密钥扫描工具,搜索整个git历史中的高熵字符串和已知密钥模式
- HashiCorp Vault:企业级密钥管理系统,用于API密钥存储、轮换和动态凭据生成
- AWS Secrets Manager:托管的密钥存储服务,支持API密钥和凭据的自动轮换
常见场景
场景:开发者平台的API密钥安全程序
背景:开发者平台提供以API密钥认证的公共API,平台有10,000+个API消费者,每天产生50M+个请求,密钥频繁在公共GitHub仓库中泄露。
方法:
- 实施带前缀的API密钥(sk_live_、sk_test_),使用256位熵用于泄露检测
- 在数据库中只存储密钥的SHA-256哈希值,在Redis中缓存已验证的密钥
- 实施按密钥范围限制:每个密钥限定到特定端点、速率限制和可选的IP白名单
- 构建带24小时宽限期的密钥轮换API,实现无缝过渡
- 与GitHub Secret Scanning集成,在数分钟内自动检测和吊销泄露的密钥
- 在CI/CD管道中运行gitleaks,防止密钥首先被提交
- 实施异常检测:对从异常IP使用或具有异常流量模式的密钥发出告警
- 添加密钥过期策略:所有密钥在365天后过期,并提前30天通知
注意事项:
- 在数据库中以明文存储API密钥(应使用SHA-256哈希)
- 使用可预测或低熵的密钥生成(应使用加密安全随机生成器)
- 未实施密钥前缀,导致无法在自动化扫描中识别泄露密钥
- 允许API密钥出现在URL查询参数中,导致密钥泄露在日志、浏览器历史和Referer头中
- 未实施按密钥速率限制,允许单个受损密钥滥用整个API
输出格式
## API密钥安全实施报告
**平台**: Developer API v3
**总活跃密钥数**: 12,450
**日密钥验证次数**: 5200万
### 安全控制
| 控制措施 | 实施方式 | 状态 |
|---------|---------------|--------|
| 密钥熵 | 256位(secrets.token_urlsafe(32)) | 已实施 |
| 密钥格式 | sk_live_/sk_test_ 前缀 | 已实施 |
| 存储 | SHA-256哈希,Redis缓存 | 已实施 |
| 范围限制 | 按密钥的端点/IP/速率限制 | 已实施 |
| 轮换 | 24小时宽限期API | 已实施 |
| 过期 | 最大TTL 365天 | 已实施 |
| 泄露检测 | GitHub Secret Scanning + gitleaks | 活跃 |
| 自动吊销 | 泄露密钥5分钟内吊销 | 活跃 |
### 密钥泄露统计(过去30天)
- 公共仓库中检测到的密钥: 23个
- 平均吊销时间: 3.2分钟
- CI/CD预提交检测到的密钥: 7个(已阻断)