Spec-Zone.ru › Django 5.2

Криптографическая подпись

Золотое правило безопасности веб-приложений — никогда не доверять данным из недоверенных источников. Иногда бывает полезно передавать данные через недоверенный канал. Криптографически подписанные значения можно безопасно передавать по недоверенному каналу, зная, что любое вмешательство будет обнаружено.

Django предоставляет как низкоуровневый API для подписи значений, так и высокоуровневый API для установки и чтения подписанных куки, что является одним из наиболее распространённых способов использования подписи в веб-приложениях.

Подпись также может быть полезна для следующих задач:

  • Генерация 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) [source]

Возвращает подписчик, который использует key для генерации подписей и sep для разделения значений. sep не может находиться в алфавите безопасного base64 для URL. Этот алфавит содержит буквенно-цифровые символы, дефисы и нижние подчёркивания. 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') [source]
sign(value) [source]

Подписывает value и добавляет текущую отметку времени к нему.

unsign(value, max_age=None) [source]

Проверяет, было ли 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) [source]

Возвращает URL-безопасную, подписанную сжатую строку JSON в формате base64. Сериализованный объект подписывается с помощью TimestampSigner.

loads(string, key=None, salt='django.core.signing', serializer=JSONSerializer, max_age=None, fallback_keys=None) [source]

Обратная функция dumps(), возбуждает исключение BadSignature, если подпись не проходит проверку. Проверяет max_age (в секундах), если указано.

© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/5.2/topics/signing/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API