伙计们,我一直在研究一种方法,以便在Selly.gg商户网站中使用烧瓶用python实现HMAC验证。
因此,sellery的开发人员文档给出了以下示例,以验证HMAC签名(在PHP和ruby中):https://developer.selly.gg/?php#signing-validating (下面的代码:)
PHP:
<?php
$signature = hash_hmac('sha512', json_encode($_POST), $secret);
if hash_equals($signature, $signatureFromHeader) {
// Webhook is valid
}
?>
红宝石:
signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha512'), secret, payload.to_json)
is_valid_signature = ActiveSupport::SecurityUtils.secure_compare(request.headers['X-Selly-Signature'], signature)
因此,到目前为止,我能弄清楚的是:它们不使用base64进行编码(如shopify等),它使用SHA-512,将秘密代码与json响应数据一起编码,最后请求标头为' X-Selly-Signature'
到目前为止,我已经编写了以下代码(基于shopify的HMAC签名https://help.shopify.com/en/api/getting-started/webhooks代码):
SECRET = "secretkeyhere"
def verify_webhook(data, hmac_header):
digest = hmac.new(bytes(SECRET, 'ascii'), bytes(json.dumps(data), 'utf8'), hashlib.sha512).hexdigest()
return hmac.compare_digest(digest, hmac_header)
try:
responsebody = request.json #line:22
status = responsebody['status']#line:25
except Exception as e:
print(e)
return not_found()
print("X Selly sign: " + request.headers.get('X-Selly-Signature'))
verified = verify_webhook(responsebody, request.headers.get('X-Selly-Signature'))
print(verified)
然而,selly有一个Webhook模拟器,即使具有正确的密钥和有效的请求,verify_webhook也将始终返回False。我曾尝试联系Selly支持人员,但他们对此无能为力
您可以在以下地址测试webhook模拟器: https://selly.io/dashboard/ {您的帐户} /开发人员/ webhook /模拟
答案 0 :(得分:3)
您几乎是对的,只是不需要json.dumps
请求数据。这可能会在输出中引入更改,例如格式更改,这些更改与原始数据不匹配,这意味着HMAC将失败。
例如
{"id":"fd87d909-fbfc-466c-964a-5478d5bc066a"}
不同于:
{
"id":"fd87d909-fbfc-466c-964a-5478d5bc066a"
}
实际上是:
{x0ax20x20"id":"fd87d909-fbfc-466c-964a-5478d5bc066a"x0a}
两个输入的哈希将完全不同。
了解json.loads
和json.dumps
将如何修改格式并因此修改哈希:
http_data = b'''{
"id":"fd87d909-fbfc-466c-964a-5478d5bc066a"
}
'''
print(http_data)
h = hashlib.sha512(http_data).hexdigest()
print(h)
py_dict = json.loads(http_data) # deserialise to Python dict
py_str = json.dumps(py_dict) # serialise to a Python str
py_bytes = json.dumps(py_dict).encode('utf-8') # encode to UTF-8 bytes
print(py_str)
h2 = hashlib.sha512(py_bytes).hexdigest()
print(h2)
输出:
b'{\n "id":"fd87d909-fbfc-466c-964a-5478d5bc066a"\n}\n'
364325098....
{"id": "fd87d909-fbfc-466c-964a-5478d5bc066a"}
9664f687a....
Selly的PHP示例显示类似的内容并没有帮助。实际上,Selly PHP示例是无用的,因为无论如何都不会对数据进行形式编码,因此数据不会在$_POST
中!
这是我的Flask小例子:
import hmac
import hashlib
from flask import Flask, request, Response
app = Flask(__name__)
php_hash = "01e5335ed340ef3f211903f6c8b0e4ae34c585664da51066137a2a8aa02c2b90ca13da28622aa3948b9734eff65b13a099dd69f49203bc2d7ae60ebee9f5d858"
secret = "1234ABC".encode("ascii") # returns a byte object
@app.route("/", methods=['POST', 'GET'])
def selly():
request_data = request.data # returns a byte object
hm = hmac.new(secret, request_data, hashlib.sha512)
sig = hm.hexdigest()
resp = f"""req: {request_data}
sig: {sig}
match: {sig==php_hash}"""
return Response(resp, mimetype='text/plain')
app.run(debug=True)
请注意使用request.data
来获取原始字节输入,并在encode
str上简单地使用secret
来获取编码的字节(而不是使用冗长的{{1 }}实例化。
可以用以下方法进行测试:
bytes()
我还创建了一些PHP来验证两种语言是否创建相同的结果:
curl -X "POST" "http://localhost:5000/" \
-H 'Content-Type: text/plain; charset=utf-8' \
-d "{\"id\":\"fd87d909-fbfc-466c-964a-5478d5bc066a\"}"