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