签名验证实现缺陷防御(恒定时间比对 / 严格类型校验 / Fail-Closed)
防御目标
修补跨语言签名验证逻辑中的常见实现缺陷,包括 PHP == / strcmp() / md5(array) 类型戏法、JavaScript === 缺失与 undefined 比较、Java Signature.verify 异常吞没以及逐字节早停比对导致的时序侧信道泄露。
跨语言缺陷与加固对照表
| 语言 / 场景 | 易错实现 | 攻击后果 | 强制加固方案 |
|---|---|---|---|
| 通用字节/字符串比较 | sig == expected 或 memcmp | 宽松类型转换(Magic Hash 0e...)或时序盲注恢复字节 | Python hmac.compare_digest、PHP hash_equals、Node crypto.timingSafeEqual、Java MessageDigest.isEqual |
| 参数类型未校验 | 传入 sign[]=x、sign=null、{"sign": true} | strcmp(Array, str) == NULL == 0 或 md5(Array) === NULL 绕过 | 入口强制断言 isinstance(sig, str) / is_string($sig),非标准长度 hex/base64 直接拒绝 |
| 空签名 / 缺失字段 | if sign: verify(...) 或默认初始值相同 | 不传 sign 参数直接跳过校验逻辑 | 先强制存在性校验:缺少签名字段直接 return False |
| 异常处理 | try { verify(); } catch (e) { log(e); } 后继续执行 | 构造畸形 Base64/ASN.1 触发异常跳过验签 | Fail-Closed:verified = False 初始化,仅在所有断言成功通过后显式置为 True |
| 大整数 / ASN.1 签名 | ECDSA r=0, s=0 或 RSA Bleichenbacher e=3 伪造 | Java/旧版库未拒绝零值签名或忽略 PKCS#1 v1.5 填充尾部垃圾 | 使用维护期内的标准密码库(如 cryptography / BoringSSL),拒绝非规范 ASN.1 编码 |
防御实现与校验示例
1. Fail-Closed 恒定时间验签封装(Python)
python
import hmac
import hashlib
import re
HEX64_RE = re.compile(r"^[0-9a-f]{64}$")
def safe_verify_hmac_sha256(secret: bytes, message: bytes, client_sig: object) -> bool:
verified = False
try:
if not isinstance(secret, (bytes, bytearray)) or len(secret) < 32:
return False
if not isinstance(message, (bytes, bytearray)):
return False
# 严格类型与定长格式校验,防止 list/dict/None/空串 或 timingSafeEqual 长度抛错泄露
if not isinstance(client_sig, str):
return False
normalized = client_sig.strip().lower()
if not HEX64_RE.fullmatch(normalized):
return False
expected = hmac.new(secret, message, hashlib.sha256).hexdigest()
verified = hmac.compare_digest(normalized, expected)
except Exception:
return False
return verified
if __name__ == "__main__":
sec = b"secret-key-at-least-32-bytes-long"
msg = b"amount=100&order=42"
good = hmac.new(sec, msg, hashlib.sha256).hexdigest()
assert safe_verify_hmac_sha256(sec, msg, good) is True
assert safe_verify_hmac_sha256(sec, msg, None) is False
assert safe_verify_hmac_sha256(sec, msg, ["array_bypass"]) is False
assert safe_verify_hmac_sha256(sec, msg, "0e123456") is False
assert safe_verify_hmac_sha256(sec, msg, "") is False
print("implementation_defense_check: PASS")2. 多语言推荐比对函数清单
text
- Python: hmac.compare_digest(a_bytes, b_bytes)
- PHP: is_string($sig) && hash_equals($expected, $sig)
- Node.js: typeof sig === 'string' && sig.length === expected.length && crypto.timingSafeEqual(...)
- Go: subtle.ConstantTimeCompare([]byte(a), []byte(b)) == 1
- Java: MessageDigest.isEqual(aBytes, bBytes)防御工作流
text
1. 静态代码排查:全局搜索源码中的 ==、!=、strcmp、memcmp、String.equals 与空 catch 块
2. 强类型入口守卫:在验签函数首行强制检验签名类型(必须为非空字符串)与精确长度匹配
3. 替换恒定时间原语:所有 MAC/摘要比对统一替换为语言标准库提供的恒定时间比对函数
4. 强制 Fail-Closed:确保验签函数默认返回 False,任何解析或解码异常都终止业务推进
5. 并发与边界测试:压测数组、布尔值、null、畸形编码与单字节时间差用例验证与判定标准
- 类型混淆免疫:向接口提交
sign[]=a、sign=true、sign=null、sign=0e215962017均返回False且不触发 500 内部异常穿透 - 时序无泄露:对首字节正确与首字节错误的签名各采样 10,000 次,耗时分布无显著统计差异
- 异常安全:传入截断或非法字符签名时,函数稳定返回
False
MCP 工具映射
| 防御环节 | MCP 工具 | 用途 |
|---|---|---|
| 关联缺陷模式检索 | kb_router | 检索其他语言或签名场景的实现加固要点 |
| 验签单测套件生成 | workspace_write_text | 输出多边界类型混淆与 Fail-Closed 单元测试 |
| 异常请求特征规则 | make_sigma_stub | 生成探测类型污染(sign[]=)和异常编码探针的检测规则 |