Spec-Zone.ru › Django 4.2

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

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

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

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

  • Генерация URL-адресов «восстановления моего аккаунта» для отправки пользователям, которые потеряли свой пароль.
  • Обеспечение того, что данные, хранящиеся в скрытых полях формы, не были изменены.
  • Генерация URL-адресов одноразового секрета для временного доступа к защищенному ресурсу, например, к загружаемому файлу, за который пользователь заплатил.

Защита SECRET_KEY и SECRET_KEY_FALLBACKS

При создании нового проекта Django с помощью startproject, файл settings.py автоматически генерируется и получает случайное значение SECRET_KEY. Это значение является ключом к защите подписанных данных — крайне важно сохранить его в секрете, иначе злоумышленники смогут сгенерировать свои собственные подписанные значения.

SECRET_KEY_FALLBACKS можно использовать для смены секретных ключей. Значения не будут использоваться для подписи данных, но если они указаны, они будут использоваться для проверки подписанных данных и должны храниться в секрете.

Изменено в Django 4.1:

Добавлена настройка 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:GdMGD6HNQ_qdgxYP8yBZAdAIV1w'

Подпись добавляется в конец строки после двоеточия. Вы можете получить исходное значение с помощью метода 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:Xdc-mOFDjs22KsQAqfVfi8PQSPdo3ckWJxPWwQOFhR4'
>>> 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:EkfQJafvGyiofrdGnuthdxImIJw'
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.

Изменено в Django 4.1:

Добавлен аргумент fallback_keys.

Устарело начиная с версии 4.2: Поддержка передачи позиционных аргументов устарела.

Использование аргумента salt

Если вы не хотите, чтобы каждый случай появления определённой строки имел тот же хэш подписи, вы можете использовать необязательный аргумент salt к классу Signer. Использование соли «посеет» функцию хэширования подписи как солью, так и вашим SECRET_KEY:

>>> signer = Signer()
>>> signer.sign("My string")
'My string:GdMGD6HNQ_qdgxYP8yBZAdAIV1w'
>>> signer.sign_object({"message": "Hello!"})
'eyJtZXNzYWdlIjoiSGVsbG8hIn0:Xdc-mOFDjs22KsQAqfVfi8PQSPdo3ckWJxPWwQOFhR4'
>>> signer = Signer(salt="extra")
>>> signer.sign("My string")
'My string:Ee7vGi-ING6n02gkcJ-QLHg6vFw'
>>> signer.unsign("My string:Ee7vGi-ING6n02gkcJ-QLHg6vFw")
'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:1NMg5H:oPVuCqlJWmChm1rA2lyTUtelC-c'
>>> 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.

Устарело начиная с версии 4.2: Поддержка передачи позиционных аргументов устарела.

Защита сложных структур данных

Если вы хотите защитить список, кортеж или словарь, вы можете сделать это с помощью методов 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:1kx6R3:D4qGKiptAqo5QW9iv4eNLc6xl4RwiFfes6oOcYhkYnc'
>>> signer.unsign_object(value)
{'foo': 'bar'}
>>> value = signing.dumps({"foo": "bar"})
>>> value
'eyJmb28iOiJiYXIifQ:1kx6Rf:LBB39RQmME-SRvilheUe5EmPYRbuDBgQp2tCAi7KGLk'
>>> 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-безопасную, подписанную, сжатую строку base64 JSON. Сериализованный объект подписывается с помощью TimestampSigner.

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

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

Изменено в Django 4.1:

Добавлен аргумент fallback_keys.

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

Spec-Zone.ru

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