Криптографическая подпись
Золотое правило безопасности веб-приложений — никогда не доверяйте данным из недоверенных источников. Иногда бывает полезно передавать данные через недоверенную среду. Криптографически подписанные значения могут передаваться через небезопасный канал с уверенностью, что любая подделка будет обнаружена.
Django предоставляет как низкоуровневый API для подписи значений, так и высокоуровневый API для установки и чтения подписанных cookie, что является одним из наиболее распространенных способов использования подписи в веб-приложениях.
Подпись также может быть полезной для следующих целей:
- Генерация URL-адресов для восстановления учетной записи для отправки пользователям, которые потеряли свой пароль.
- Обеспечение того, что данные, хранящиеся в скрытых полях формы, не были изменены.
- Генерация одноразовых секретных URL-адресов для временного доступа к защищенному ресурсу, например, загружаемому файлу, за который пользователь заплатил.
Защита секретного ключа
Когда вы создаете новый проект 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'
Если подпись или значение были изменены каким-либо образом, будет возбуждено исключение 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)[source] -
Возвращает подписчика, который использует
keyдля генерации подписей иsepдля разделения значений.sepне может быть в алфавите URL-безопасного base64. Этот алфавит содержит буквенно-цифровые символы, дефисы и подчеркивания.
Использование аргумента salt
Если вы не хотите, чтобы каждое вхождение определенной строки имело одинаковый хэш подписи, вы можете использовать необязательный аргумент salt к классу Signer. Использование соли позволит запустить функцию хэширования подписи как с солью, так и с вашим SECRET_KEY:
>>> signer = Signer()
>>> signer.sign('My string')
'My string:GdMGD6HNQ_qdgxYP8yBZAdAIV1w'
>>> signer = Signer(salt='extra')
>>> signer.sign('My string')
'My string:Ee7vGi-ING6n02gkcJ-QLHg6vFw'
>>> signer.unsign('My string:Ee7vGi-ING6n02gkcJ-QLHg6vFw')
'My string'
Использование соли таким образом помещает разные подписи в разные пространства имен. Подпись, которая происходит из одного пространства имен (определенное значение соли), не может использоваться для проверки того же простого текста в другом пространстве имен, использующем другое значение соли. Результатом является предотвращение использования злоумышленником подписанной строки, сгенерированной в одном месте кода, в качестве входных данных для другой части кода, которая генерирует (и проверяет) подписи с использованием другой соли.
В отличие от вашего 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)[source] -
-
sign(value)[source] -
Подписывает
valueи добавляет к нему текущее время.
-
unsign(value, max_age=None)[source] -
Проверяет, была ли подпись
valueменьше, чемmax_ageсекунд назад, иначе возбуждает исключениеSignatureExpired. Параметрmax_ageможет принимать целое число или объектdatetime.timedelta.Ранее параметр
max_ageпринимал только целое число.
-
Защита сложных структур данных
Если вы хотите защитить список, кортеж или словарь, вы можете сделать это с помощью функций модуля подписи dumps и loads. Они имитируют модуль Python pickle, но используют сериализацию JSON под капотом. JSON гарантирует, что даже если ваш SECRET_KEY будет украден, злоумышленник не сможет выполнить произвольные команды, используя формат pickle:
>>> from django.core import signing
>>> value = signing.dumps({"foo": "bar"})
>>> value
'eyJmb28iOiJiYXIifQ:1NMg1b:zGcDE4-TCkaeGzLeW9UQwZesciI'
>>> 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', compress=False)[source] -
Возвращает URL-безопасную, подписанную с помощью sha1, сжатую строку JSON в base64. Сериализованный объект подписывается с помощью
TimestampSigner.
-
loads(string, key=None, salt='django.core.signing', max_age=None)[source] -
Обратное преобразование
dumps(), возбуждает исключениеBadSignatureв случае неудачи проверки подписи. Проверяетmax_age(в секундах), если указано.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/1.8/topics/signing/