Криптографическая подпись
Золотое правило безопасности веб-приложений — никогда не доверяйте данным из недоверенных источников. Иногда бывает полезно передавать данные через недоверенный канал. Криптографически подписанные значения можно безопасно передавать через недоверенный канал, зная, что любое вмешательство будет обнаружено.
Django предоставляет как низкоуровневый API для подписи значений, так и высокоуровневый API для установки и чтения подписанных куки, что является одним из наиболее распространённых применений подписи в веб-приложениях.
Вы также можете найти подпись полезной для следующего:
- Генерация ссылок «восстановление моего аккаунта» для отправки пользователям, которые потеряли свой пароль.
- Обеспечение того, что данные, хранящиеся в скрытых полях формы, не были изменены.
- Генерация одноразовых секретных ссылок для предоставления временного доступа к защищенному ресурсу, например, загружаемому файлу, за который пользователь заплатил.
Защита SECRET_KEY и SECRET_KEY_FALLBACKS
При создании нового проекта Django с помощью startproject, файл settings.py автоматически генерируется и получает случайное значение SECRET_KEY. Это значение является ключом для защиты подписанных данных — крайне важно, чтобы вы сохранили его в секрете, иначе злоумышленники смогут генерировать свои собственные подписанные значения.
SECRET_KEY_FALLBACKS можно использовать для смены секретных ключей. Значения не будут использоваться для подписи данных, но, если указаны, они будут использоваться для проверки подписанных данных и должны храниться в секрете.
Было добавлено значение 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('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.Изменено в Django 4.1:Добавлен аргумент
fallback_keys.
Использование аргумента 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-безопасную, подписанную сжатую строку base64 JSON. Сериализованный объект подписывается с использованием
TimestampSigner.
-
loads(string, key=None, salt='django.core.signing', serializer=JSONSerializer, max_age=None, fallback_keys=None)[source] -
Обратный метод
dumps(), вызывает исключениеBadSignatureпри неудачной проверке подписи. Проверяетmax_age(в секундах), если задано.Изменено в Django 4.1:Добавлен аргумент
fallback_keys.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/4.1/topics/signing/