wok-api-conversion / src /utils /security.py
woook001's picture
Upload 35 files
b30d305 verified
Raw
History Blame Contribute Delete
4.83 kB
"""
安全工具函数
提供敏感信息掩码和安全日志功能
"""
import json
import re
from typing import Any, Dict, Union
from copy import deepcopy
def mask_api_key(api_key: str, prefix_length: int = 4) -> str:
"""
安全地掩码API key,只显示前几个字符,其余用星号替代
Args:
api_key: 原始API key
prefix_length: 显示的前缀长度,默认4个字符
Returns:
掩码后的API key,格式如: sk-1234****
"""
if not api_key:
return "***empty***"
if len(api_key) <= prefix_length:
return "*" * len(api_key)
# 显示前几位和足够的星号
masked_length = max(8, len(api_key) - prefix_length) # 至少8个星号
return f"{api_key[:prefix_length]}{'*' * masked_length}"
def mask_sensitive_data(data: Union[Dict[str, Any], str], sensitive_keys: list = None) -> Union[Dict[str, Any], str]:
"""
递归地掩码数据中的敏感信息
Args:
data: 要处理的数据(字典或字符串)
sensitive_keys: 敏感字段名列表
Returns:
掩码后的数据副本
"""
if sensitive_keys is None:
sensitive_keys = [
'api_key', 'apikey', 'key', 'token', 'password', 'secret',
'authorization', 'auth', 'x-api-key', 'x-goog-api-key',
'bearer', 'access_token', 'refresh_token'
]
if isinstance(data, str):
# 如果是字符串,尝试解析为JSON
try:
parsed = json.loads(data)
masked = mask_sensitive_data(parsed, sensitive_keys)
return json.dumps(masked, ensure_ascii=False, indent=2)
except (json.JSONDecodeError, TypeError):
# 如果不是JSON,检查是否包含API key模式
return _mask_string_patterns(data)
elif isinstance(data, dict):
# 深拷贝避免修改原始数据
masked_data = deepcopy(data)
for key, value in masked_data.items():
key_lower = key.lower()
# 检查是否是敏感字段
if any(sensitive_key in key_lower for sensitive_key in sensitive_keys):
if isinstance(value, str):
masked_data[key] = mask_api_key(value)
else:
masked_data[key] = "***masked***"
elif isinstance(value, (dict, list)):
# 递归处理嵌套结构
masked_data[key] = mask_sensitive_data(value, sensitive_keys)
return masked_data
elif isinstance(data, list):
# 处理列表
return [mask_sensitive_data(item, sensitive_keys) for item in data]
else:
# 其他类型直接返回
return data
def _mask_string_patterns(text: str) -> str:
"""
掩码字符串中的API key模式
"""
# 常见的API key模式
patterns = [
# OpenAI格式: sk-开头
(r'sk-[a-zA-Z0-9]{48}', lambda m: mask_api_key(m.group())),
# Anthropic格式: sk-ant-开头
(r'sk-ant-[a-zA-Z0-9\-_]{95}', lambda m: mask_api_key(m.group())),
# Google格式: 39字符的字母数字
(r'AIza[a-zA-Z0-9_\-]{35}', lambda m: mask_api_key(m.group())),
# Bearer token
(r'Bearer\s+[a-zA-Z0-9\-_\.]{20,}', lambda m: f"Bearer {mask_api_key(m.group()[7:])}"),
# 通用长字符串(可能是key)
(r'[a-zA-Z0-9\-_]{32,}', lambda m: mask_api_key(m.group()) if len(m.group()) > 20 else m.group()),
]
result = text
for pattern, replacer in patterns:
result = re.sub(pattern, replacer, result)
return result
def safe_log_data(data: Any, max_length: int = 1000) -> str:
"""
安全地格式化数据用于日志记录
Args:
data: 要记录的数据
max_length: 最大长度限制
Returns:
安全的日志字符串
"""
try:
# 先掩码敏感信息
masked_data = mask_sensitive_data(data)
# 转换为字符串
if isinstance(masked_data, (dict, list)):
log_str = json.dumps(masked_data, ensure_ascii=False, indent=2)
else:
log_str = str(masked_data)
# 限制长度
if len(log_str) > max_length:
log_str = log_str[:max_length] + "...[truncated]"
return log_str
except Exception as e:
return f"***log_error: {str(e)}***"
def safe_log_request(request_data: Dict[str, Any]) -> str:
"""
安全地记录请求数据
"""
return safe_log_data(request_data, max_length=2000)
def safe_log_response(response_data: Dict[str, Any]) -> str:
"""
安全地记录响应数据
"""
return safe_log_data(response_data, max_length=1500)