Сбой проверки параметров Shopify HMAC в Python

У меня возникли проблемы с проверкой параметра HMAC, поступающего из Shopify. Код, который я использую в документации Shopify, возвращает неверный результат.

Вот мой аннотированный код:

import urllib
import hmac
import hashlib

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

Разобрать строку запроса

params = urllib.parse.parse_qs(qs)

Извлеките значение hmac

value = params['hmac'][0]

Удалить параметры из строки запроса по документации

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

Рекомбинировать параметры

new_qs = urllib.parse.urlencode(params)

Рассчитать дайджест

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

Возвращает False !

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

Этот последний шаг должен якобы вернуть истину. Каждый следующий шаг описан в комментариях к документации Shopify.

1 ответ

Решение

Я собираюсь опубликовать ответ на свой собственный вопрос, так как после прочесывания форумов Shopify и остальных SO, я не смог найти ничего, что определенно ответило бы на это.

В какой-то момент, недавно, Shopify начал включать protocol параметр в полезной нагрузке строки запроса. Это само по себе не будет проблемой, за исключением того факта, что Shopify не упомянул в своей документации, что : а также / не должны быть закодированы URL при проверке подписи. Это совершенно бессмысленно, учитывая, что они сами выполняют URL-кодирование этих символов в строке запроса, которую они предоставляют.

Итак, чтобы решить проблему, просто предоставьте safe параметр для urllib.parse.urlencode со значением :/ (подходит, верно?). Полный рабочий код выглядит так:

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(), value)

Надеюсь, что это полезно для других, сталкивающихся с этой проблемой!

Обратите внимание, что этот код несколько упрощен, так как параметры запроса не лексикографически (в алфавитном порядке) отсортированы. Если это можно сделать более понятным, я могу обновить код, чтобы сделать это также.

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))
Другие вопросы по тегам