skip to Main Content

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.

2

Answers


  1. Chosen as BEST ANSWER

    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!


  2. 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))
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search