You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何用Python 3生成HMAC SHA256并验证API POST请求签名

解决Python 3中HMAC验证和PHP结果不一致的问题

我明白你现在的困扰——明明逻辑看起来一样,但Python生成的HMAC就是和收到的对不上。核心问题往往出在没有使用原始的请求体字节,而是自己重新序列化JSON,这会导致哈希的内容和PHP那边处理的不一样。

先拆解你的PHP代码逻辑,确保Python完全对齐:

  1. 读取客户端发送的原始请求体(没有经过任何解析或修改的字节)
  2. 用SHA256算法,以客户端密钥为密钥,对原始数据计算HMAC二进制摘要
  3. 将二进制摘要base64编码,和请求头里的HMAC值对比

对应的Python实现

首先写通用的验证函数,再结合Web框架(以Flask为例)展示如何使用:

1. 核心验证函数

import hashlib
import base64

# 注意:密钥要转成字节,和PHP的字符串处理逻辑一致
CLIENT_SECRET = b'817a3723917f4c7fac24b1f1b324bbab'

def verify_webhook(raw_request_body: bytes, received_hmac: str) -> bool:
    # 计算HMAC:参数顺序是密钥、原始消息、哈希算法
    hmac_obj = hashlib.hmac(CLIENT_SECRET, raw_request_body, hashlib.sha256)
    # 获取二进制摘要,然后base64编码成字符串
    calculated_hmac = base64.b64encode(hmac_obj.digest()).decode('utf-8')
    # 对比两个HMAC字符串
    return calculated_hmac == received_hmac

2. 在Flask中接收并验证请求

from flask import Flask, request

app = Flask(__name__)

@app.route('/your-webhook-endpoint', methods=['POST'])
def handle_webhook():
    # 关键:获取原始请求体字节,和PHP的file_get_contents('php://input')完全等价
    raw_body = request.get_data()
    
    # 从请求头获取HMAC值(对应PHP里的X-NINJAVAN-HMAC-SHA256)
    received_hmac = request.headers.get('X-NINJAVAN-HMAC-SHA256')
    
    if not received_hmac:
        return "Missing HMAC header", 400
    
    is_verified = verify_webhook(raw_body, received_hmac)
    
    if is_verified:
        # 验证通过,这里写你的业务逻辑
        print("Webhook verified successfully!")
        return "OK", 200
    else:
        print("Invalid HMAC signature")
        return "Unauthorized", 403

if __name__ == '__main__':
    app.run(debug=True)

避坑关键点

  • 绝对不要重新序列化JSON:如果你先解析JSON成字典,再用json.dumps()转成字符串,很可能因为JSON的格式(比如空格、缩进、键的排序)和原始请求不一致,导致HMAC计算结果不同。一定要用原始的请求体字节。
  • 密钥必须是字节类型:PHP中的字符串本质是字节序列,Python中要把密钥转成字节(用b''前缀或者.encode('utf-8')),否则hashlib会抛出错误。
  • 确保HMAC头正确:要和PHP代码中使用的X-NINJAVAN-HMAC-SHA256头一致,不要拼写错误。

用你提供的测试数据验证的话,只要用原始的JSON字节作为raw_request_body,计算出的HMAC就会和你收到的OD5ZxL4tdGgWr78e9vO3cYrjuOFT8WOrTbTIuuIH1PQ=一致。

内容的提问来源于stack exchange,提问作者Larissa Hameru

火山引擎 最新活动