Spec-Zone.ru › Django 5.1

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

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

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: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) [source]

Возвращает подписчика, который использует 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: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') [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: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) [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.1/topics/signing/

Spec-Zone.ru

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