Криптографическая подпись¶
Золотое правило безопасности веб-приложений — никогда не доверять данным из ненадежных источников. Иногда может быть полезно передать данные через ненадежный носитель. Значения с криптографической подписью можно передавать по недоверенному каналу, зная, что любое вмешательство будет обнаружено.
Django предоставляет как низкоуровневый API для подписи значений, так и высокоуровневый API для установки и чтения подписанных файлов cookie, что является одним из наиболее распространенных вариантов использования подписи в веб-приложениях.
Вы также можете найти подпись полезной для следующего:
Генерация URL для восстановления аккаунта пользователя, которые будут отправлены пользователю, потерявшему свой пароль.
Проверка целостности данных, спрятанных в скрытом поле формы.
Генерация одноразового секретного URL для обеспечения временного доступа к защищённому ресурсу, например на скачивание файла за который заплатил пользователь.
Защита SECRET_KEY и SECRET_KEY_FALLBACKS¶
При создании нового Django проекта с помощью startproject автоматически генерируется файл settings.py и определяется случайное значение SECRET_KEY. Это значение является ключевым аспектом защиты подписанных данных – очень важно сохранять его в тайне. В противном случае у сторонних людей появляется возможность генерировать собственные подписанные значения.
SECRET_KEY_FALLBACKS можно использовать для ротации секретных ключей. Значения не будут использоваться для подписи данных, но если они указаны, они будут использоваться для проверки подписанных данных и должны храниться в безопасности.
Использование низкоуровневого API¶
Методы подписи Django находятся в модуле django.core.signing. Чтобы подписать значение, сначала создайте экземпляр Signer:
>>> from django.core.signing import Signer
>>> signer = Signer()
>>> value = signer.sign("My string")
>>> value
'My string:v9G-nxfz3iQGTXrePqYPlGvH79WTcIgj1QIQSUODTW0'
Подпись добавляется в конец строки после двоеточия. Вы можете получить исходное значение, используя метод unsign:
>>> original = signer.unsign(value)
>>> original
'My string'
Если вы передадите нестроковое значение в sign, перед подписанием значение будет принудительно преобразовано в строку, и результат unsign даст вам это строковое значение:
>>> signed = signer.sign(2.5)
>>> original = signer.unsign(signed)
>>> original
'2.5'
Если вы хотите защитить список, кортеж или словарь, вы можете сделать это, используя методы sign_object() и unsign_object():
>>> signed_obj = signer.sign_object({"message": "Hello!"})
>>> signed_obj
'eyJtZXNzYWdlIjoiSGVsbG8hIn0:bzb48DBkB-bwLaCnUVB75r5VAPUEpzWJPrTb80JMIXM'
>>> obj = signer.unsign_object(signed_obj)
>>> obj
{'message': 'Hello!'}
Дополнительную информацию см. в Защита сложных структур данных.
Если подпись или значение были каким-либо образом изменены, будет создано исключение django.core.signing.BadSignature:
>>> from django.core import signing
>>> value += "m"
>>> try:
... original = signer.unsign(value)
... except signing.BadSignature:
... print("Tampering detected!")
...
По умолчанию класс Signer использует параметр SECRET_KEY для генерации подписей. Вы можете использовать другой секрет, передав его конструктору Signer:
>>> signer = Signer(key="my-other-secret")
>>> value = signer.sign("My string")
>>> value
'My string:o3DrrsT6JRB73t-HDymfDNbTSxfMlom2d8TiUlb1hWY'
- class Signer(*, key=None, sep=':', salt=None, algorithm=None, fallback_keys=None)¶
Возвращает подписывающую сторону, которая использует
keyдля генерации подписей иsepдля разделения значений.sepне может быть в URL-безопасном алфавите Base64. Этот алфавит содержит буквенно-цифровые символы, дефисы и символы подчеркивания.algorithmдолжен быть алгоритмом, поддерживаемымhashlib, по умолчанию он'sha256'.fallback_keys— это список дополнительных значений, используемых для проверки подписанных данных, по умолчанию: SECRET_KEY_FALLBACKS.
Использование аргумента salt¶
Если вы не хотите, чтобы каждое вхождение определенной строки имело один и тот же хэш подписи, вы можете использовать необязательный аргумент salt для класса Signer. Использование соли заполнит хэш-функцию подписи как солью, так и вашим SECRET_KEY:
>>> signer = Signer()
>>> signer.sign("My string")
'My string:v9G-nxfz3iQGTXrePqYPlGvH79WTcIgj1QIQSUODTW0'
>>> signer.sign_object({"message": "Hello!"})
'eyJtZXNzYWdlIjoiSGVsbG8hIn0:bzb48DBkB-bwLaCnUVB75r5VAPUEpzWJPrTb80JMIXM'
>>> signer = Signer(salt="extra")
>>> signer.sign("My string")
'My string:YMD-FR6rof3heDkFRffdmG4pXbAZSOtb-aQxg3vmmfc'
>>> signer.unsign("My string:YMD-FR6rof3heDkFRffdmG4pXbAZSOtb-aQxg3vmmfc")
'My string'
>>> signer.sign_object({"message": "Hello!"})
'eyJtZXNzYWdlIjoiSGVsbG8hIn0:-UWSLCE-oUAHzhkHviYz3SOZYBjFKllEOyVZNuUtM-I'
>>> signer.unsign_object(
... "eyJtZXNzYWdlIjoiSGVsbG8hIn0:-UWSLCE-oUAHzhkHviYz3SOZYBjFKllEOyVZNuUtM-I"
... )
{'message': 'Hello!'}
Использование соли таким образом помещает разные подписи в разные пространства имен. Подпись, полученная из одного пространства имен (определенное значение соли), не может использоваться для проверки той же строки открытого текста в другом пространстве имен, в котором используется другая настройка соли. В результате злоумышленник не может использовать подписанную строку, сгенерированную в одном месте кода, в качестве входных данных для другого фрагмента кода, который генерирует (и проверяет) подписи с использованием другой соли.
В отличие от значения SECRET_KEY, ваша «соль» не является секретом.
Проверка значений с подписанным слепком времени¶
TimestampSigner является подклассом Signer, который добавляет к значению подписанную временную метку. Это позволяет вам подтвердить, что подписанное значение было создано в течение указанного периода времени:
>>> from datetime import timedelta
>>> from django.core.signing import TimestampSigner
>>> signer = TimestampSigner()
>>> value = signer.sign("hello")
>>> value
'hello:1stLqR:_rvr4oXCgT4HyfwjXaU39QvTnuNuUthFRCzNOy4Hqt0'
>>> signer.unsign(value)
'hello'
>>> signer.unsign(value, max_age=10)
SignatureExpired: Signature age 15.5289158821 > 10 seconds
>>> signer.unsign(value, max_age=20)
'hello'
>>> signer.unsign(value, max_age=timedelta(seconds=20))
'hello'
- class TimestampSigner(*, key=None, sep=':', salt=None, algorithm='sha256')¶
- sign(value)¶
Подписывает
valueи добавляет текущее время к нему.
- unsign(value, max_age=None)¶
Проверяет, было ли
valueподписано менееmax_ageсекунд назад, в противном случае выдаетсяSignatureExpired. Параметр max_age может принимать целое число или объектdatetime.timedelta.
- sign_object(obj, serializer=JSONSerializer, compress=False)¶
Кодируйте, при необходимости сжимайте, добавляйте текущую метку времени и подписывайте сложную структуру данных (например, список, кортеж или словарь).
- unsign_object(signed_obj, serializer=JSONSerializer, max_age=None)¶
Проверяет, был ли подписан
signed_objменееmax_ageсекунд назад, в противном случае вызываетSignatureExpired. Параметр max_age может принимать целое число или объектdatetime.timedelta.
Защита сложных структур данных¶
Если вы хотите защитить список, кортеж или словарь, вы можете сделать это, используя методы Signer.sign_object() и unsign_object() или функции dumps() или loads() модуля подписи (которые являются ярлыками для TimestampSigner(salt='django.core.signing').sign_object()/unsign_object()). Они используют сериализацию JSON под капотом. JSON гарантирует, что даже если ваш SECRET_KEY будет украден, злоумышленник не сможет выполнять произвольные команды, используя формат Pickle:
>>> from django.core import signing
>>> signer = signing.TimestampSigner()
>>> value = signer.sign_object({"foo": "bar"})
>>> value
'eyJmb28iOiJiYXIifQ:1stLrZ:_QiOBHafwucBF9FyAr54qEs84ZO1UdsO1XiTJCvvdno'
>>> signer.unsign_object(value)
{'foo': 'bar'}
>>> value = signing.dumps({"foo": "bar"})
>>> value
'eyJmb28iOiJiYXIifQ:1stLsC:JItq2ZVjmAK6ivrWI-v1Gk1QVf2hOF52oaEqhZHca7I'
>>> signing.loads(value)
{'foo': 'bar'}
Из-за особенностей JSON (нет внутреннего различия между списками и кортежами), если вы передадите кортеж, вы получите список из signing.loads(object):
>>> from django.core import signing
>>> value = signing.dumps(("a", "b", "c"))
>>> signing.loads(value)
['a', 'b', 'c']
- dumps(obj, key=None, salt='django.core.signing', serializer=JSONSerializer, compress=False)¶
Возвращает URL-безопасную подписанную сжатую строку JSON в формате Base64. Сериализованный объект подписывается с помощью
TimestampSigner.
- loads(string, key=None, salt='django.core.signing', serializer=JSONSerializer, max_age=None, fallback_keys=None)¶
Обратный методу``dumps()``, вызывает исключение
BadSignature, если проверка подписи не пройдена. Проверяетmax_age(в секундах) при его наличии.