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

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


You're nearly right except that you don't need to json.dumps the request data. This will likely introduce changes into output, such as changes to formatting, that won't match the original data meaning the HMAC will fail.

E.g.

{"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:

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 dictpy_str = json.dumps(py_dict) # serialise to a Python strpy_bytes = json.dumps(py_dict).encode('utf-8') # encode to UTF-8 bytesprint(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. 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!

Here's my little Flask example:

import hmacimport hashlibfrom flask import Flask, request, Responseapp = 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).

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