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;?>