Python3中POST请求SHA256签名无效问题求助
排查POST请求SHA256签名无效问题
问题背景
需要向要求SHA256哈希签名认证的服务发送POST请求,已持有app_id和app_secret,按照服务商步骤实现后,GET请求签名验证正常,但POST请求始终返回「无效签名」错误。
服务商要求的签名生成步骤:
- 构建MAC字符串,包含以下元素拼接:
AppId + HTTP Method + URI + UNIX Timestamp + Nonce(请求唯一字符串) + payload的MD5转base64字符串 - 使用
AppSecret对上述字符串生成SHA256 HMAC哈希 - 最终认证签名格式:
ApiKey AppId:hash:nonce:timestamp
用户原有代码(POST请求部分):
app_id = 'my_app_id' app_secret = 'my_app_secret' providerURL = 'https://the_provider_url' # POST请求调用代码 def publish_data(number, date, status): endPointUrl = providerURL + "/v1/posting" # 测试用参数 number = '123456789' date = None # 可选参数 status = 'valid' payload = "?number={}&date={}&status={}".format(str(number), str(date), str(status)) strHash = hmac_generator(endPointUrl, 'POST', payload) request_headers = {'Authorization': "ApiKey " + strHash, 'Content-Type': 'application/x-www-form-urlencoded' } response = requests.post(endPointUrl, headers=request_headers) print('RESPONSE:', response.text) # HMAC生成函数 def hmac_generator(uri, method, body=None): timestamp = int(time.time()) nonce = str(uuid.uuid4()) content = "" if body is not None: body_md5 = hashlib.md5(body.encode('utf-8')).digest() content = base64.b64encode(body_md5).decode('utf-8') message = app_id + method + uri + str(timestamp) + nonce + content message_bytes = message.encode('utf-8') # 生成SHA256哈希 signature = hmac.new(app_secret, msg=message_bytes, digestmod=hashlib.sha256).digest() hashed = base64.b64encode(signature).decode('utf-8') return "{}:{}:{}:{}".format(app_id, hashed, nonce, timestamp)
问题排查
POST请求签名无效的核心问题集中在payload处理不匹配,具体有以下几点:
- Payload格式与发送不匹配:用户将POST的payload构造成带
?的QueryString格式,但实际发送请求时未传递该payload,导致服务端收到的请求体为空,而签名计算用的是带?的字符串,两边payload的MD5值完全不一致。 - 可选参数处理错误:当
date为None时,str(date)会转换成字符串'None',但服务商可能要求忽略该参数(不加入payload)或用空字符串代替,导致签名计算的payload与服务端预期不符。 - HMAC密钥编码问题:
hmac.new要求密钥为字节串,直接传入字符串app_secret会依赖系统默认编码,可能与服务商的编码方式不一致。
修复后的代码
1. 修正POST请求的Payload处理
将payload改为标准表单键值对格式,实际传递给请求体,同时处理可选参数:
import requests import time import uuid import hashlib import hmac import base64 app_id = 'my_app_id' app_secret = 'my_app_secret' providerURL = 'https://the_provider_url' def publish_data(number, date, status): endPointUrl = providerURL + "/v1/posting" # 测试用参数 number = '123456789' date = None # 可选参数 status = 'valid' # 构建表单数据,忽略None值的参数 payload_data = { 'number': number, 'status': status } if date is not None: payload_data['date'] = date # 将表单数据编码为x-www-form-urlencoded格式的字符串(用于签名计算) payload = requests.models.URL_ENCODED_FORM_ENCODER.encode(payload_data) strHash = hmac_generator(endPointUrl, 'POST', payload) request_headers = { 'Authorization': f"ApiKey {strHash}", 'Content-Type': 'application/x-www-form-urlencoded' } # 发送POST请求时传递表单数据 response = requests.post(endPointUrl, headers=request_headers, data=payload_data) print('RESPONSE:', response.text)
2. 修正HMAC生成函数的编码问题
确保密钥是字节串,统一编码逻辑:
def hmac_generator(uri, method, body=None): timestamp = int(time.time()) nonce = str(uuid.uuid4()) content = "" if body is not None: # 统一处理body的编码 if isinstance(body, bytes): body_bytes = body else: body_bytes = body.encode('utf-8') body_md5 = hashlib.md5(body_bytes).digest() content = base64.b64encode(body_md5).decode('utf-8') message = app_id + method + uri + str(timestamp) + nonce + content message_bytes = message.encode('utf-8') # 将app_secret转为字节串 secret_bytes = app_secret.encode('utf-8') signature = hmac.new(secret_bytes, msg=message_bytes, digestmod=hashlib.sha256).digest() hashed = base64.b64encode(signature).decode('utf-8') return f"{app_id}:{hashed}:{nonce}:{timestamp}"
验证要点
- 确认生成签名用的payload和实际发送给服务端的payload完全一致(包括参数顺序、编码方式)
- 检查可选参数的处理逻辑是否符合服务商文档要求(比如
date为None时是否需要从payload中移除) - 确保HMAC生成时所有字符串都使用UTF-8编码,避免因编码差异导致签名不一致
内容的提问来源于stack exchange,提问作者Paulo Matos
相关产品推荐
相关产品推荐

