HMAC SHA256 signatures in Python and Flask.

November 6, 2019. Filed under python 56 slack 6

I'm playing around a bit with the Slack API, which I'll have a longer post on in a bit. One part of the integration requires generating an HMAC SHA256 signature to verify requests are from Slack. There weren't too many helpful search results, and some of them like the hmac module docs don't include examples. Here are some quick notes for folks in future attempting the same thing.


h/t to Joe Kampschmidt's post which covers signing well.


First step is to instrument your test application is capturing the full headers and raw body from a response so you can verify you implementation.

If you're using Flask for your server, getting at the raw request body turns out to be slightly complicated, as it'll helpfully attempt to convert data with content-type of application/x-www-form-urlencoded into a dictionary instead of raw string. This conversion into a dictionary masks most obvious ways to access to the raw string.

Fortunately you can use Request.get_data to get the raw body as long as you do it before calling Request.form. It does work fine to call Request.get_data and then later Request.form, so be careful to sequence them correctly.

def recall_post(request):
    # must not call request.form here
    headers = request.headers()
    data = request.get_data()
    print(headers)
    print(data)


    # calling request.form is fine now
    request.form
    return "%s %s" % (headers, data)

Once you have thoes headers and data, you'll be able to do a test run of generating the signature and comparing it against the sent signature. The values you need to extract from the inbound request are:

timestamp = request.headers['X-Slack-Request-Timestamp']
expected_sig = request.headers['X-Slack-Signature']

You'll also need to go to your Application page within the Slack dashboard to get your Signing Secret. Once you have these pieces along with the raw POST body retrieved via Request.get_data, then you can use those components to test generating your own signature.

import hmac, hashlib

body = b'token=etc&team_id=etc&...'
timestamp = b'100000000000'
base = 'v0:%s:%s' % (timestamp.decode('utf-8'), body.decode('utf-8'))
secret = b'1234'
computed = hmac.new(secret, base.encode('utf-8'),
                    digestmod=hashlib.sha256).hexdigest()
sig = 'v0=%s' % (computed,)

print(sig)

If you use real values, this should match exactly the signature from Slack signature in the X-Slack-Signature header.

Pulling this all together, the server function will look like:

def verify(request,secret):
    body = request.get_data()
    timestamp = request.headers['X-Slack-Request-Timestamp']
    sig_basestring = 'v0:%s:%s' % (timestamp, body.decode('utf-8'))
    computed_sha = hmac.new(secret,
                            sig_basestring.encode('utf-8'),
                            digestmod=hashlib.sha256).hexdigest()
    my_sig = 'v0=%s' % (computed_sha,)
    slack_sig = request.headers['X-Slack-Signature']
    if my_sig != slack_sig:
        err_str = "my_sig %s does not equal slack_sig %s" % \
                   (my_sig, slack_sig))
        raise Exception(err_str)

def recall_post(request):
    signing_secret = b'your secret from some config tool'
    verify(request, signing_secret)
    return "Signatures match, yay."

Now as long as you call verify in your handlers, you can be confident that the incoming requests are indeed from Slack. Hopefully this will save someone a few minutes!