You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Cloud Functions中Webhook请求的HMAC256签名验证问题

解决Cloud Functions中Webhook HMAC签名验证不匹配的问题

你遇到的核心问题是计算签名时误用了解析后再序列化的JSON,而非原始请求体字节,再加上签名头格式处理的小细节偏差,导致两者哈希结果完全不匹配。下面是一步步的修复方案:

1. 核心问题:用原始请求体替代解析后JSON

当你调用request.get_json()把请求体解析成Python字典后,再用json.dumps()转回字符串,生成的内容大概率和源端发送的原始请求体不一致——JSON序列化的空格、键的排序、甚至引号处理都可能有差异,而HMAC是基于精确字节流计算的,哪怕一个空格不同,最终哈希值都会完全不同。

正确的做法是直接获取请求的原始字节:request.data,这个变量保存了Webhook发送过来的未经任何解析的原始请求体,直接用它来计算HMAC就能保证字节一致性。

2. 处理签名头的格式

源端返回的签名是sha256=<key>格式,你需要先把<key>部分提取出来,再和自己生成的签名对比,注意大小写统一(比如头里是小写sha256=,要避免和大写的SHA256混淆)。

3. 修正后的完整代码

这里是调整后的代码,解决了上述所有问题:

import firebase_admin
from firebase_admin import credentials
from firebase_admin import firestore
import hmac, hashlib

# Use the application default credentials
cred = credentials.ApplicationDefault()
firebase_admin.initialize_app(cred, {
  'projectId': 'my-project-id',
})
db = firestore.client()

def webhook(request):
    myToken = "2ET#####################sB" #Secret from origin
    # 提取签名头中的哈希值
    signature_header = request.headers.get('BTCPAY-SIG', '')
    if not signature_header.startswith('sha256='):
        return f'Invalid signature format', 400
    expected_signature = signature_header.split('=')[1]

    # 获取原始请求体字节
    raw_body = request.data
    generated_signature = _generate_signature(raw_body, myToken)

    print(f"Received signature: {expected_signature}")
    print(f"Generated signature: {generated_signature}")
    print('Validate?', generated_signature == expected_signature)

    if generated_signature != expected_signature:
        return f'Invalid signature', 403
    return f'Hello World!'

def _generate_signature(raw_data, myToken):
    # 密钥转字节,用utf-8编码(和源端保持一致)
    key_bytes = bytes(myToken, 'utf-8')
    # raw_data已经是字节类型,直接传入计算HMAC
    return hmac.new(key_bytes, raw_data, hashlib.sha256).hexdigest()

额外调试建议

如果调整后还是不匹配,可以做这些排查:

  • 打印raw_body的长度和内容(注意屏蔽敏感信息),和源端确认他们发送的原始请求体是否完全一致
  • 确认源端使用的JSON序列化规则:比如是否开启了sort_keys=True,或者用了separators=(',', ':')的紧凑无空格格式。如果Cloud Functions转发请求时自动修改了请求体格式,可尝试用json.dumps(request_json, sort_keys=True, separators=(',', ':'))转成字节再计算(这是备选方案,优先用原始请求体)
  • 核对密钥是否完全一致,没有额外的空格、换行符或大小写错误

内容的提问来源于stack exchange,提问作者Santiago Bocel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 22:47:33