如何用Python 3生成HMAC SHA256并验证API POST请求签名
解决Python 3中HMAC验证和PHP结果不一致的问题
我明白你现在的困扰——明明逻辑看起来一样,但Python生成的HMAC就是和收到的对不上。核心问题往往出在没有使用原始的请求体字节,而是自己重新序列化JSON,这会导致哈希的内容和PHP那边处理的不一样。
先拆解你的PHP代码逻辑,确保Python完全对齐:
- 读取客户端发送的原始请求体(没有经过任何解析或修改的字节)
- 用SHA256算法,以客户端密钥为密钥,对原始数据计算HMAC二进制摘要
- 将二进制摘要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




