Spec-Zone.ru › Django 3.2

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

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

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.1:

Параметр algorithm был добавлен.

Изменено в 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.

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

Параметр algorithm был добавлен.

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

Если вы хотите защитить список, кортеж или словарь, вы можете сделать это, используя методы 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/3.2/topics/signing/

Spec-Zone.ru

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