Криптографическая подпись

Добавлено в Django 1.4.

Золотое правило безопасного веб приложения – это никогда не доверять данным из недоверенных источников. Временами передача данных через недоверенную среду может оказаться полезной. Криптографически подписанные значения могут безопасно передаваться через недоверенный канал, если известно, что любая подмена данных будет обнаружена.

Django предоставляет низкоуровневое API для подписывания значения и высокоуровневое API для установки и чтения подписанных кук, одного из наиболее часто используемых функционалов подписи в веб приложениях.

Вы также можете найти подпись полезной для следующего:

  • Генерация URL для восстановления аккаунта пользователя, которые будут отправлены пользователю, потерявшему свой пароль.

  • Проверка целостности данных, спрятанных в скрытом поле формы.

  • Генерация одноразового секретного URL для обеспечения временного доступа к защищённому ресурсу, например на скачивание файла за который заплатил пользователь.

Защита SECRET_KEY

При создании нового Django проекта с помощью startproject автоматически генерируется файл settings.py и определяется случайное значение SECRET_KEY. Это значение является ключевым аспектом защиты подписанных данных – очень важно сохранять его в тайне. В противном случае у сторонних людей появляется возможность генерировать собственные подписанные значения.

Использование низкоуровневого API

class Signer

Методы подписи расположены в модуле django.core.signing. Для того, чтобы подписать значение, сначала создайте экземпляр Signer:

>>> from django.core.signing import Signer
>>> signer = Signer()
>>> value = signer.sign('My string')
>>> value
'My string:GdMGD6HNQ_qdgxYP8yBZAdAIV1w'

Сигнатура добавляется в конец строки, отделённая двоеточием. Вы можете получить оригинальное значение с помощью метода unsing:

>>> original = signer.unsign(value)
>>> original
u'My string'

Если подпись или значение были изменены любым способом, то будет вызвано исключение django.core.signing.BadSignature:

>>> value += 'm'
>>> try:
...    original = signer.unsign(value)
... except signing.BadSignature:
...    print "Tampering detected!"

По умолчанию, класс Signer использует настройку SECRET_KEY для генерации сигнатур. Вы можете использовать другой ключ, передав его в конструктор:

>>> signer = Signer('my-other-secret')
>>> value = signer.sign('My string')
>>> value
'My string:EkfQJafvGyiofrdGnuthdxImIJw'

Использование “соли”

Если требуется, чтобы подпись одинаковой строки всегда создавала разные сигнатуры, вы можете использовать необязательный аргумент salt конструктора. Функция подписи воспользуется “солью” и 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')
u'My string'

Использование “соли” размещает разные сигнатуры в разных именованных областях. Сигнатура из одной области (определённая значением “соли”) не может быть использована для проверки исходной строки текста в другой именованной области (другое значение “соли”). Результатом является невозможность использовать атакующим строки, подписанные в одном месте, в качестве источника данных в другом.

В отличие от значения SECRET_KEY, ваша “соль” не являеся секретом.

Проверка значений с подписанным слепком времени

class TimestampSigner

TimestampSigner – подкласс Signer, который добавляет подписанный слепок времени к значению. Это позволяет вам удостоверять, что подписанное значение было создано в указанный период времени:

>>> from django.core.signing import TimestampSigner
>>> signer = TimestampSigner()
>>> value = signer.sign('hello')
>>> value
'hello:1NMg5H:oPVuCqlJWmChm1rA2lyTUtelC-c'
>>> signer.unsign(value)
u'hello'
>>> signer.unsign(value, max_age=10)
...
SignatureExpired: Signature age 15.5289158821 > 10 seconds
>>> signer.unsign(value, max_age=20)
u'hello'

Защита сложных структур данных

Если вам потребуется защитить список, кортеж или словарь, вы можете это сделать с помощью функций dumps и loads модуля. Они имитируют поведение функций модуля 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'}
dumps(obj, key=None, salt='django.core.signing', compress=False)

Возвращает безопасный (с точки зрения URL), подписанный SHA1, закодированную base64 JSON строку.

loads(string, key=None, salt='django.core.signing', max_age=None)

Обратнен методу``dumps()``, вызывает исключение BadSignature, если проверка подписи не пройдена.