Криптографическая подпись¶
Золотое правило безопасности веб-приложений - никогда не доверять данным из ненадежных источников. Иногда полезно передавать данные через ненадежный носитель. Криптографически подписанные значения можно передавать по ненадежному каналу, зная, что любая фальсификация будет обнаружена.
Django предоставляет как низкоуровневый API для подписи значений, так и высокоуровневый API для установки и чтения подписанных cookies, что является одним из наиболее распространенных способов использования подписи в веб-приложениях.
Вы также можете счесть подписание полезным для следующего:
- Генерация 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)[исходный код]¶ Возвращает подписант, который использует
key
для генерации подписей иsep
для разделения значений.sep
не может находиться в URL safe base64 alphabet. Этот алфавит содержит буквенно-цифровые символы, дефисы и подчеркивания.
Использование аргумента 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)[исходный код]¶ -
sign
(value)[исходный код]¶ Знак
value
и добавьте к нему текущую метку времени.
-
unsign
(value, max_age=None)[исходный код]¶ Проверяет, если
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)[исходный код]¶ Возвращает безопасную для URL, подписанную sha1 base64 сжатую строку JSON. Сериализованный объект подписывается с помощью
TimestampSigner
.
-
loads
(string, key=None, salt='django.core.signing', max_age=None)[исходный код]¶ Обратный вариант
dumps()
, повышаетBadSignature
, если подпись не прошла. Проверяетmax_age
(в секундах), если задано.