Сбой проверки параметров 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×tamp=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))