Spec-Zone.ru › Django 4.0

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

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

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

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

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

Защита SECRET_KEY

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

Использование низкоуровневого 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('my-other-secret')
>>> value = signer.sign('My string')
>>> value
'My string:EkfQJafvGyiofrdGnuthdxImIJw'
class Signer(key=None, sep=':', salt=None, algorithm=None)

Возвращает подписывающий экземпляр, который использует key для генерации подписей и sep для разделения значений. sep не может находиться в алфавите безопасного для URL base64. Этот алфавит содержит буквенно-цифровые символы, дефисы и подчёркивания. algorithm должен быть алгоритмом, поддерживаемым hashlib, по умолчанию он равен 'sha256'.

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

Были добавлены методы sign_object() и unsign_object().

Использование аргумента 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, аргумент соли не должен быть секретным.

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

Были добавлены методы sign_object() и unsign_object().

Проверка значений со временем

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)
Добавлено в Django 3.2.

Кодирует, необязательно сжимает, добавляет текущую временную метку и подписывает сложную структуру данных (например, список, кортеж или словарь).

unsign_object(signed_obj, serializer=JSONSerializer, max_age=None)
Добавлено в Django 3.2.

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

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

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

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

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

Были добавлены методы sign_object() и unsign_object().

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

Spec-Zone.ru

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