Криптографическая подпись
Золотое правило безопасности веб-приложений — никогда не доверять данным из недоверенных источников. Иногда бывает полезно передавать данные через недоверенный канал. Криптографически подписанные значения можно передавать через недоверенный канал, будучи уверенными в том, что любая попытка подмены будет обнаружена.
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: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.Устарело начиная с версии 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 Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/5.0/topics/signing/