Криптографическая подпись
Золотое правило безопасности веб-приложений – никогда не доверять данным из недоверенных источников. Иногда полезно передавать данные через недоверенный канал. Криптографически подписанные значения могут быть переданы через недоверенный канал, зная, что любое вмешательство будет обнаружено.
Django предоставляет как низкоуровневый API для подписи значений, так и высокоуровневый API для установки и чтения подписанных куки, что является одним из наиболее распространённых способов использования подписи в веб-приложениях.
Подпись также может быть полезной для:
- Генерации URL-адресов «восстановления учетной записи» для отправки пользователям, которые потеряли свой пароль.
- Обеспечения того, что данные, хранящиеся в скрытых полях формы, не были изменены.
- Генерации одноразовых секретных URL-адресов для временного доступа к защищённому ресурсу, например, к загружаемому файлу, за который пользователь заплатил.
Защита секретного ключа 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'
Если подпись или значение были изменены каким-либо образом, будет поднято исключение 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.
-
Защита сложных структур данных
Если вы хотите защитить список, кортеж или словарь, вы можете сделать это с помощью функций dumps и loads модуля подписи. Они имитируют модуль pickle Python, но используют сериализацию 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.10/topics/signing/