云计算百科
云计算领域专业知识百科平台

SERP API 响应签名验证实战:防篡改 + 防重放完整方案

SERP API 跑生产,数据在传输中被篡改或重放,轻则数据污染,重则喂给 LLM 编出错误答案。签名验证是基本防护。

1. 为什么需要签名

3 个威胁场景:

  • 中间人篡改:代理 / 网关篡改响应字段(position 改低)
  • 重放攻击:缓存旧的响应冒充新数据
  • 伪造响应:攻击者假装是 SERP API 返回假数据

签名验证能防御前两个。

2. 签名原理

服务端对响应体做 HMAC-SHA256,附在响应头:

响应头:
X-Response-Signature: sha256=<base64 hmac>

客户端:
1. 取原始响应体
2. 用共享密钥计算 HMAC
3. 对比签名
4. 一致 = 未被篡改

3. 服务端签名(FastAPI)

import hmac
import hashlib
import base64
from fastapi import FastAPI, Request, Response
import json

app = FastAPI()
SIGNATURE_KEY = os.environ['SIGNATURE_KEY']

def sign_body(body: bytes) > str:
"""计算 HMAC 签名"""
digest = hmac.new(
SIGNATURE_KEY.encode(), body, hashlib.sha256
).digest()
return base64.b64encode(digest).decode()

@app.post('/google/search')
async def search(request: Request):
# … 处理查询,生成响应
result = generate_serp(request)

# 签名
body = json.dumps(result, ensure_ascii=False).encode()
signature = sign_body(body)

response = Response(content=body)
response.headers['X-Response-Signature'] = f'sha256={signature}'
response.headers['X-Response-Timestamp'] = str(int(time.time()))
return response

4. 客户端验证

import hmac
import hashlib
import base64
import requests

SIGNATURE_KEY = os.environ['SIGNATURE_KEY']

def verify_signature(response) > bool:
"""验证响应签名"""
# 1. 取签名头
signature = response.headers.get('X-Response-Signature', '')
if not signature or not signature.startswith('sha256='):
return False

expected = signature.split('=', 1)[1]

# 2. 用原始响应体计算
raw_body = response.content
digest = hmac.new(
SIGNATURE_KEY.encode(), raw_body, hashlib.sha256
).digest()
actual = base64.b64encode(digest).decode()

# 3. 对比
return hmac.compare_digest(expected, actual)

def safe_search(query):
r = requests.post(
'https://api.example.com/google/search',
headers={'X-API-Key': os.environ['SERPBASE_API_KEY']},
json={'q': query, 'num': 10},
timeout=5
)

if not verify_signature(r):
raise SignatureError('Response signature invalid')

return r.json()

5. 防重放:时间戳窗口

签名只能防篡改,防重放要靠时间戳:

import time

REPLAY_WINDOW = 300 # 5 分钟窗口

def verify_replay(response) > bool:
"""验证时间戳,防重放"""
ts = int(response.headers.get('X-Response-Timestamp', 0))

# 1. 时间戳不能太旧(重放检测)
if abs(time.time() ts) > REPLAY_WINDOW:
return False

# 2. 时间戳不能太新(时钟偏差)
if ts > time.time() + 60:
return False

return True

6. 防重放:nonce 方案

更严格,用一次性 nonce:

import uuid
import redis

r = redis.Redis()

class NonceVerifier:
def __init__(self, window=300):
self.window = window

def verify(self, nonce):
"""检查 nonce 是否用过"""
key = f'nonce:{nonce}'

# SETNX:不存在才设置成功
ok = r.set(key, '1', ex=self.window, nx=True)

if not ok:
# nonce 已用过 = 重放
return False
return True

# 客户端请求带 nonce
nonce = str(uuid.uuid4())
r = requests.post(
url,
headers={'X-Request-Nonce': nonce, ...}
)

7. 完整验证流程

def validate_response(response) > tuple[bool, str]:
"""完整校验:签名 + 时间戳 + nonce"""
# 1. 签名
if not verify_signature(response):
return False, 'signature_mismatch'

# 2. 时间戳
if not verify_replay(response):
return False, 'replay_detected'

# 3. nonce(可选)
nonce = response.headers.get('X-Response-Nonce')
if nonce and not nonce_verifier.verify(nonce):
return False, 'nonce_reused'

return True, 'ok'

8. 常见坑

坑 1:响应体被压缩

响应是 gzip,签名是基于压缩前的 body 还是压缩后的?必须约定:

# 方案:签名基于未压缩的原始 JSON
# 客户端要先解压再验证

def verify_with_encoding(response):
# 取原始字节(requests 已解压)
raw = response.content

# 计算签名(用解压后的)
digest = hmac.new(KEY.encode(), raw, hashlib.sha256).digest()
# …

坑 2:签名密钥管理

密钥泄露 = 签名失效:

# 用 KMS / Vault 管理
from vault_client import get_secret
SIGNATURE_KEY = get_secret('serp-signature-key')

# 定期轮换
def rotate_key():
old = SIGNATURE_KEY
new = generate_key()
# 过渡期:两个密钥都接受
# 1 天后废弃 old

坑 3:HMAC 对比用 compare_digest

不要用 == 比较,会时序攻击:

# 错误
if expected == actual: ...

# 正确
if hmac.compare_digest(expected, actual): ...

9. 监控

from prometheus_client import Counter

signature_ok = Counter('serp_signature_ok', 'Valid signatures')
signature_fail = Counter('serp_signature_fail', 'Invalid signatures', ['reason'])

def monitored_validate(response):
ok, reason = validate_response(response)
if ok:
signature_ok.inc()
else:
signature_fail.labels(reason=reason).inc()
return ok, reason

告警规则:签名失败率 > 0.1% 立即告警(可能被攻击)。

10. 实战:30 天数据

跑 30 天签名验证:

指标数值
验证总数 30 万次
签名通过 99.97%
签名失败 0.03%(9 次)
失败原因 8 次密钥轮换过渡期,1 次中间人尝试
重放检测 2 次(缓存层误用)

11. 总结

签名验证 3 件事:

  • HMAC-SHA256 防篡改
  • 时间戳窗口防重放
  • nonce 一次性防重放(严格场景)
  • 代码 100 行,安全提升明显。

    参考文档

    本文 API 示例参考 serpbase 文档(serpbase.dev/docs),接口路径、参数和返回字段以官方文档为准。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » SERP API 响应签名验证实战:防篡改 + 防重放完整方案
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!