Webhook signature validation - Python

Hi,

I’m not able to produce a matching signature when validating a webhook request.
The relevant part of the controller looks like this:

from hashlib import md5

secret = b'foo'
sighash = md5(secret)
sighash.update(request.get_data())
assert sighash.hexdigest() == request.headers['X-Patreon-Signature']

Anyone has a working example?
I’m using Python 3.5 and request is a Werkzeug Request, so a Flask example should be similar.

Thanks!

I’ve previously posted example code for signature validation here for someone else having issues – although with Javascript, not Python. Their issue was that the value they were using for the body of the request was not the actual request body, I’m going to guess the same is true here.

Looking at the documentation for Werkzeug Request get_data I think you need to pass in as_text=True to obtain the right value for generating the signature.

If as_text is set to True the return value will be a decoded unicode string.

from hashlib import md5

secret = b'foo'
sighash = md5(secret)
sighash.update(request.get_data(as_text=True))
assert sighash.hexdigest() == request.headers['X-Patreon-Signature']

If this doesn’t help I’d recommend visiting the Register Webhooks page and use one of the example webhooks body in your code instead of pulling it from the request body to verify if your signature verification is written correctly, that will help ascertain where the issue lies.

Hi @sam,

Thanks for the reply.

get_data() does return the body/json payload as a bytestring which is what the hashing function expects and not a unicode string which is what will be returned if as_text is set to True.

Feeding string objects into update() is not supported, as hashes work on bytes, not on characters.

I’ve checked that other post earlier and I’m surprised how the correct hash is produced since as I understand the body is parsed into a JS object and then marshalled to a string again which I’d expect the original bytes to get altered. Out of desperation I’ve tried the same myself and no luck :slight_smile:.

1 Like

I’ve not worked with Python before but after trying out various input data that I know to work (verified signature using Javascript and PHP) with your code I couldn’t get it to work, so I looked at some other hashing examples online… and I’ve confirmed the following approach works:

import hashlib
import hmac

json = ''
secret = ''
signature = ''

def verify_signature(message, key, expected):
    
    digester = hmac.new(
      bytes(key), 
      bytes(message),
      hashlib.md5
    )

    return expected == digester.hexdigest()
    
print(verify_signature(json, secret, signature))

This passes with valid input. I don’t know enough about Python to say why my approach works and yours doesn’t… they appear to do the same thing, but maybe there’s a bug in yours I’m not noticing.

2 Likes

Hi sam,

You are right, that works.
My bug was I didn’t have enough crypto knowledge. I debugged my code by reading the wikipedia article on what is HMAC and how it is different from MD5 :).

Thanks!

This was all very useful, but I needed to add an “encoding” parameter to the first “bytes” function in the verify_signature function to get it to work.

def verify_signature(message, key, expected):
    digester = hmac.new(
        bytes(key, encoding='utf-8'),
        bytes(message),
        hashlib.md5
    )
    return expected == digester.hexdigest()
1 Like