简体   繁体   English

在 Python 中使用 Flask 进行 Hmac 验证(在 PHP 和 RUBY 中参考)

[英]Hmac verification with flask in Python (with reference in PHP and RUBY)

I have been working on a way to implement HMAC verification in python with flask for the selly.gg merchant website.我一直在研究一种在 python 中使用烧瓶为 Selly.gg 商家网站实现 HMAC 验证的方法。

So selly's dev documentation give these following examples to verify HMAC signatures (in PHP and ruby): https://developer.selly.gg/?php#signing-validating (code below:)所以 Selly 的开发文档给出了以下示例来验证 HMAC 签名(在 PHP 和 ruby​​ 中): https : //developer.selli.gg/? php#signing-validating(下面的代码:)

PHP: PHP:

<?php
        $signature = hash_hmac('sha512', json_encode($_POST), $secret);
        if hash_equals($signature, $signatureFromHeader) {
            // Webhook is valid 
        }
?>

RUBY:红宝石:

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)

So, so far what I could figure out: They don't encode with base64 (like shopify and others do), it uses SHA-512, it encodes the secret code alongside json response data and finally the request header is 'X-Selly-Signature'所以,到目前为止我能弄清楚:他们不使用 base64 编码(就像 shopify 和其他人一样),它使用 SHA-512,它将密码与 json 响应数据一起编码,最后请求标头是“X-Selly” -签名'

I've made the following code so far (based on shopify's code for HMAC signing https://help.shopify.com/en/api/getting-started/webhooks ):到目前为止,我已经编写了以下代码(基于 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)

However selly has a webhook simulator, and even with the proper secret key and valid requests, the verify_webhook will always return False.然而,sally 有一个 webhook 模拟器,即使有正确的密钥和有效的请求,verify_webhook 也将始终返回 False。 I tried contacting Selly support, but they couldn't help me more than that我尝试联系 Selly 支持,但他们帮不了我更多

You can test the webhook simulator at the following address: https://selly.io/dashboard/{your account}/developer/webhook/simulate您可以在以下地址测试 webhook 模拟器: https ://selly.io/dashboard/{your account}/developer/webhook/simulate

You're nearly right except that you don't need to json.dumps the request data.你几乎是对的,只是你不需要json.dumps请求数据。 This will likely introduce changes into output, such as changes to formatting, that won't match the original data meaning the HMAC will fail.这可能会在输出中引入更改,例如格式更改,这些更改与原始数据不匹配,这意味着 HMAC 将失败。

Eg例如

{"id":"fd87d909-fbfc-466c-964a-5478d5bc066a"}

is different to:不同于:

{
  "id":"fd87d909-fbfc-466c-964a-5478d5bc066a"
}

which is actually:这实际上是:

{x0ax20x20"id":"fd87d909-fbfc-466c-964a-5478d5bc066a"x0a}

A hash will be completely different for the two inputs.两个输入的散列将完全不同。

See how json.loads and json.dumps will modify the formatting and therefore the hash:看看json.loadsjson.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)

Output:输出:

b'{\n    "id":"fd87d909-fbfc-466c-964a-5478d5bc066a"\n}\n'
364325098....
{"id": "fd87d909-fbfc-466c-964a-5478d5bc066a"}
9664f687a....

It doesn't help that Selly's PHP example shows something similar. Selly 的 PHP 示例显示了类似的内容,这无济于事。 In fact, the Selly PHP example is useless as the data won't be form encoded anyway, so the data won't be in $_POST !事实上,Selly PHP 示例是无用的,因为无论如何都不会对数据进行表单编码,因此数据不会在$_POST

Here's my little Flask example:这是我的 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)

Note the use of request.data to get the raw byte input and the simple use of encode on the secret str to get the encoded bytes (instead of using the verbose bytes() instantiation).请注意使用request.data来获取原始字节输入,并在secret str 上简单使用encode来获取编码字节(而不是使用详细的bytes()实例化)。

This can be tested with:这可以通过以下方式进行测试:

curl -X "POST" "http://localhost:5000/" \
 -H 'Content-Type: text/plain; charset=utf-8' \
 -d "{\"id\":\"fd87d909-fbfc-466c-964a-5478d5bc066a\"}"

I also created a bit of PHP to validate both languages create the same result:我还创建了一些 PHP 来验证两种语言创建相同的结果:

<?php
    header('Content-Type: text/plain');
    $post = file_get_contents('php://input');
    print $post;
    $signature = hash_hmac('sha512', $post, "1234ABC");
    print $signature;
?>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM