fix: 增强密钥强度检测逻辑并修复解密异常处理

增强API密钥强度检测算法,综合评估字符集熵值和实际字符多样性,避免低复杂度密钥通过验证。同时改进解密功能的错误处理,统一异常类型。

主要改进:
- 新增字符多样性检测,防止全重复字符的弱密钥通过验证
- 修正熵值计算逻辑,基于原始字符集而非密钥实际字符分布
- 增强解密函数异常处理,统一抛出InvalidToken异常类型
- 更新测试用例以匹配新的强校验逻辑

技术细节:
- check_key_strength函数新增min_diversity、charset、exclude_ambiguous参数
- 解密时捕获UnicodeDecodeError并转换为InvalidToken
- 测试代码配合新逻辑调整断言和参数传递
This commit is contained in:
2025-11-30 20:48:34 +08:00
parent 44a916a48d
commit 0e20e4375a
3 changed files with 92 additions and 33 deletions

View File

@@ -10,7 +10,7 @@ except ImportError:
PYDANTIC_AVAILABLE = False
try:
from cryptography.fernet import Fernet
from cryptography.fernet import Fernet, InvalidToken
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.backends import default_backend
CRYPTOGRAPHY_AVAILABLE = True
@@ -157,13 +157,22 @@ def generate_batch(
# ------------------------------
# 密钥强度检测cryptography
# ------------------------------
def check_key_strength(key: str, min_entropy: float = 4.0) -> Tuple[bool, str]:
def check_key_strength(
key: str,
min_entropy: float = 4.0,
min_diversity: int = 4, # 新增最小字符多样性至少4种不同字符
charset: Optional[Union[str, List[str]]] = None,
exclude_ambiguous: bool = True
) -> Tuple[bool, str]:
"""
检测API密钥的强度基于字符集熵值
检测API密钥的强度基于字符集熵值 + 密钥字符多样性
Args:
key: 待检测的API密钥
min_entropy: 最小可接受熵值字符集大小的log2默认4.0对应16种字符
min_entropy: 最小可接受熵值字符集大小的log2默认4.0
min_diversity: 密钥中最小不同字符数默认4避免全重复/低多样性)
charset: 生成密钥时使用的原始字符集
exclude_ambiguous: 生成密钥时是否排除易混淆字符
Returns:
Tuple[是否安全, 强度说明]
@@ -171,35 +180,49 @@ def check_key_strength(key: str, min_entropy: float = 4.0) -> Tuple[bool, str]:
if not key:
return False, "密钥为空"
# 提取密钥主体
# 提取密钥主体(去掉前缀)
if "-" in key:
key_body = key.split("-", 1)[1]
else:
key_body = key
# 1. 校验长度
if len(key_body) < MIN_LENGTH:
return False, f"密钥主体长度不足{MIN_LENGTH}"
# 计算字符集大小和熵值
unique_chars = len(set(key_body))
if unique_chars <= 1:
entropy_per_char = 0.0
# 2. 计算原始字符集的熵值(保持之前的修复逻辑)
if charset is None:
used_charset = DEFAULT_CHARSET
else:
entropy_per_char = math.log2(unique_chars)
used_charset = "".join(charset) if isinstance(charset, list) else charset
if exclude_ambiguous:
ambiguous_chars = {"o", "O", "0", "l", "L", "I", "1"}
used_charset = "".join([c for c in used_charset if c not in ambiguous_chars])
charset_size = len(used_charset)
if charset_size <= 1:
entropy = 0.0
else:
entropy = math.log2(charset_size)
if entropy_per_char < min_entropy:
return False, f"强度不足(字符多样性低,熵值{entropy_per_char:.1f} < 最低{min_entropy:.1f}"
# 3. 计算密钥的实际字符多样性(新增核心逻辑)
actual_unique_chars = len(set(key_body)) # 密钥中实际不同的字符数
if CRYPTOGRAPHY_AVAILABLE:
# 额外校验:密钥是否含足够随机分布(基于哈希)
digest = hashes.Hash(hashes.SHA256(), backend=default_backend())
digest.update(key_body.encode("utf-8"))
hash_hex = digest.finalize().hex()
# 简单校验哈希结果前4位不重复低概率碰撞仅作为辅助
if len(set(hash_hex[:4])) < 3:
return True, f"强度合格(熵值{entropy_per_char:.1f}),但随机分布略差"
return True, f"强度合格(熵值{entropy_per_char:.1f}"
# 4. 综合判定
if entropy < min_entropy and actual_unique_chars < min_diversity:
return False, f"强度不足(字符集熵值{entropy:.1f} < 最低{min_entropy:.1f},实际字符多样性{actual_unique_chars} < 最低{min_diversity}"
elif entropy < min_entropy:
return False, f"强度不足(字符集熵值{entropy:.1f} < 最低{min_entropy:.1f}"
elif actual_unique_chars < min_diversity:
return False, f"强度不足(实际字符多样性{actual_unique_chars} < 最低{min_diversity}"
else:
# cryptography增强校验保持不变
if CRYPTOGRAPHY_AVAILABLE:
digest = hashes.Hash(hashes.SHA256(), backend=default_backend())
digest.update(key_body.encode("utf-8"))
hash_hex = digest.finalize().hex()
if len(set(hash_hex[:4])) < 3:
return True, f"强度合格(字符集熵值{entropy:.1f},实际字符多样性{actual_unique_chars}),但随机分布略差"
return True, f"强度合格(字符集熵值{entropy:.1f},实际字符多样性{actual_unique_chars}"
# ------------------------------
@@ -234,6 +257,14 @@ def decrypt_key(encrypted_key: bytes, secret_key: bytes) -> str:
if not CRYPTOGRAPHY_AVAILABLE:
raise ImportError("请安装 cryptography 库pip install llm-api-keygen[full]")
fernet = Fernet(secret_key)
decrypted_key = fernet.decrypt(encrypted_key).decode("utf-8")
return decrypted_key
try:
fernet = Fernet(secret_key)
decrypted_bytes = fernet.decrypt(encrypted_key)
# 捕获 UTF-8 解码错误(无效密钥可能导致解密后为乱码)
try:
return decrypted_bytes.decode("utf-8")
except UnicodeDecodeError:
raise InvalidToken("解密后数据不是有效的 UTF-8 编码")
except (InvalidToken, ValueError) as e:
# 统一抛出 InvalidToken测试用例预期的异常类型
raise InvalidToken(f"解密失败:{str(e)}") from e

View File

@@ -126,12 +126,18 @@ def test_check_key_strength_basic():
weak_key_repeat = "sk-" + "a" * 32
is_secure, msg = check_key_strength(weak_key_repeat)
assert not is_secure
assert "熵值" in msg and "不足" in msg
# 修复断言:匹配“字符多样性”相关文案,而非“熵值”
assert "字符多样性" in msg and "不足" in msg
# 弱密钥字符集过小仅2种字符
weak_key_charset = "sk-" + "ab" * 16
is_secure, msg = check_key_strength(weak_key_charset)
is_secure, msg = check_key_strength(
weak_key_charset,
charset="ab", # 传递原始字符集(关键)
exclude_ambiguous=False
)
assert not is_secure
# 修复断言此时失败原因是“熵值不足”字符集仅2种熵值=1.0 < 4.0
assert "熵值" in msg and "不足" in msg
# 强密钥:默认生成的密钥
@@ -141,8 +147,19 @@ def test_check_key_strength_basic():
assert "合格" in msg
# 自定义最小熵值
key = generate_api_key(charset=string.ascii_letters) # 52种字符熵值≈5.7
is_secure, msg = check_key_strength(key, min_entropy=5.0)
charset = string.ascii_letters
key = generate_api_key(charset=charset) # 52种字符排除易混淆后约49种
is_secure, msg = check_key_strength(
key,
min_entropy=5.0,
charset=charset, # 传递原始字符集
exclude_ambiguous=True # 与生成时一致
)
assert is_secure
is_secure, msg = check_key_strength(key, min_entropy=6.0)
is_secure, msg = check_key_strength(
key,
min_entropy=6.0,
charset=charset, # 传递原始字符集
exclude_ambiguous=True # 与生成时一致
)
assert not is_secure

View File

@@ -51,9 +51,20 @@ def test_check_key_strength_crypto_enhanced():
assert is_secure
assert "合格" in msg
# 边缘情况:熵值刚好达标
key_marginal = generate_api_key(charset=string.ascii_letters[:16]) # 16种字符熵值=4.0
is_secure, msg = check_key_strength(key_marginal, min_entropy=4.0)
# 边缘情况:熵值刚好达标修改部分传递charset和exclude_ambiguous参数
charset = string.ascii_letters[:16] # 原始字符集a-p16种
# 生成时关闭exclude_ambiguous避免排除o字符确保字符集大小=16
key_marginal = generate_api_key(
charset=charset,
exclude_ambiguous=False, # 关闭易混淆字符排除
length=32
)
is_secure, msg = check_key_strength(
key_marginal,
min_entropy=4.0,
charset=charset, # 传递原始字符集
exclude_ambiguous=False # 与生成时一致
)
assert is_secure