Shopify HMAC parameter verification failing in Python

Question:

I’m having some trouble verifying the HMAC parameter coming from Shopify. The code I’m using per the Shopify documentation is returning an incorrect result.

Here’s my annotated code:

import urllib
import hmac
import hashlib

qs = "hmac=96d0a58213b6aa5ca5ef6295023a90694cf21655cf301975978a9aa30e2d3e48&locale=en&protocol=https%3A%2F%2F&shop=myshopname.myshopify.com&timestamp=1520883022"

Parse the querystring

params = urllib.parse.parse_qs(qs)

Extract the hmac value

value = params['hmac'][0]

Remove parameters from the querystring per documentation

del params['hmac']
del params['signature']

Recombine the parameters

new_qs = urllib.parse.urlencode(params)

Calculate the digest

h = hmac.new(SECRET.encode("utf8"), msg=new_qs.encode("utf8"), digestmod=hashlib.sha256)

Returns False!

hmac.compare_digest(h.hexdigest(), value)

That last step should, ostensibly, return true. Every step followed here is outlined as commented in the Shopify docs.

Asked By: dwlz

||

Answers:

At some point, recently, Shopify started including the protocol parameter in the querystring payload. This itself wouldn’t be a problem, except for the fact that Shopify doesn’t document that : and / are not to be URL-encoded when checking the signature. This is unexpected, given that they themselves do URL-encode these characters in the query string that is provided.

To fix the issue, provide the safe parameter to urllib.parse.urlencode with the value :/ (fitting, right?). The full working code looks like this:

params = urllib.parse.parse_qsl(qs)
cleaned_params = []
hmac_value = dict(params)['hmac']

# Sort parameters
for (k, v) in sorted(params):
    if k in ['hmac', 'signature']:
        continue

    cleaned_params.append((k, v))

new_qs = urllib.parse.urlencode(cleaned_params, safe=":/")
secret = SECRET.encode("utf8")
h = hmac.new(secret, msg=new_qs.encode("utf8"), digestmod=hashlib.sha256)

# Compare digests
hmac.compare_digest(h.hexdigest(), hmac_value)

Hope this is helpful for others running into this issue!

Answered By: dwlz
import hmac
import hashlib


...

# Inside your view in Django's views.py
params = request.GET.dict()
#

myhmac = params.pop('hmac')
params['state'] = int(params['state'])
line = '&'.join([
    '%s=%s' % (key, value)
    for key, value in sorted(params.items())
])
print(line)
h = hmac.new(
    key=SHARED_SECRET.encode('utf-8'),
    msg=line.encode('utf-8'),
    digestmod=hashlib.sha256
)

# Cinderella ?
print(hmac.compare_digest(h.hexdigest(), myhmac))
Answered By: pX0r
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.