Python中Shopify HMAC参数验证失败问题求助
我之前在做Shopify集成时也卡过HMAC验证这一步,明明跟着文档一步步来却返回False,那种抓心挠肝的感觉太熟悉了!大概率是你忽略了几个文档里没写得特别细的细节,我帮你梳理下最容易踩的坑:
最容易出错的几个细节
参数排序必须严格按字典序(ASCII顺序)
Shopify要求重新组合参数时,必须按照参数名的ASCII字典序排序后再拼接。比如参数shop、timestamp、order_id,要按order_id→shop→timestamp的顺序排列,不能按原始查询字符串的顺序来。很多人直接用解析后的字典原样拼接,这就会导致哈希不一致。URL编码规则要完全匹配Shopify的要求
重新拼接key=value时,键和值都需要做严格的百分号编码:- 空格必须转成
%20(不能用+,这是表单编码的规则,不是Shopify要求的URL编码) - 所有特殊字符(比如
&、=、%)都要正确编码,比如&转成%26,=转成%3D
很多语言的默认URL编码函数会用+代替空格,这是常见的错误点。
- 空格必须转成
必须使用Shared Secret而非API Key
很多人搞混了密钥:Shopify HMAC验证用的是你应用的Shared Secret(在Shopify后台应用设置里的"API credentials"下),而不是API Key。用错密钥肯定会验证失败。处理多值参数和空值参数
如果查询字符串里有同一个参数出现多次(比如foo=bar&foo=baz),要保留所有键值对;空值参数(比如empty=)也不能忽略,必须包含在拼接的字符串里。哈希结果要转成小写十六进制字符串
Shopify返回的HMAC值是小写的十六进制字符串,如果你计算出的结果是大写,或者转码时出错,就会匹配失败。另外,推荐用语言内置的安全比较函数(比如Python的hmac.compare_digest)来对比,避免时序攻击。
示例验证代码(Python)
这里给你一个符合Shopify要求的验证代码,你可以对照自己的代码找差异:
import hmac import hashlib import urllib.parse def verify_shopify_hmac(query_string, shared_secret): # 解析查询字符串,保留空值和多值参数 params = urllib.parse.parse_qs(query_string, keep_blank_values=True) # 提取收到的HMAC值,处理可能的多值情况 hmac_received = params.pop('hmac', [None])[0] if not hmac_received: return False # 按ASCII字典序排序参数键 sorted_keys = sorted(params.keys()) # 重新编码并拼接参数 encoded_params = [] for key in sorted_keys: # 处理同一个键的多个值 for value in params[key]: # 严格编码键和值,不保留任何安全字符 encoded_key = urllib.parse.quote(key, safe='') encoded_value = urllib.parse.quote(value, safe='') encoded_params.append(f"{encoded_key}={encoded_value}") message = '&'.join(encoded_params) # 计算HMAC-SHA256摘要 computed_hmac = hmac.new( shared_secret.encode('utf-8'), message.encode('utf-8'), digestmod=hashlib.sha256 ).hexdigest() # 安全比较两个哈希值(忽略大小写,兼容可能的大写返回值) return hmac.compare_digest(computed_hmac, hmac_received.lower())
快速自查清单
- 打印你重新拼接的
message字符串,和Shopify文档里的示例对比格式是否一致 - 确认使用的是Shared Secret,复制时有没有多空格或漏字符
- 检查参数排序是否严格按字典序,有没有遗漏参数
- 验证URL编码后的字符串是否符合要求(比如空格是
%20而非+)
内容的提问来源于stack exchange,提问作者Dan Loewenherz

