Криптографическая подпись¶
The golden rule of web application security is to never trust data from untrusted sources. Sometimes it can be useful to pass data through an untrusted medium. Cryptographically signed values can be passed through an untrusted channel safe in the knowledge that any tampering will be detected.
Django provides both a low-level API for signing values and a high-level API for setting and reading signed cookies, one of the most common uses of signing in web applications.
Вы также можете счесть подписание полезным для следующего:
- Генерация URL-адресов «восстановить мой аккаунт» для отправки пользователям, потерявшим пароль.
- Убедитесь, что данные, хранящиеся в скрытых полях формы, не были подделаны.
- Генерация одноразовых секретных URL-адресов для предоставления временного доступа к защищенному ресурсу, например, к загружаемому файлу, за который пользователь заплатил.
Protecting SECRET_KEY and 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(key='my-other-secret')
>>> value = signer.sign('My string')
>>> value
'My string:EkfQJafvGyiofrdGnuthdxImIJw'
-
class
Signer(*, key=None, sep=':', salt=None, algorithm=None, fallback_keys=None)[исходный код]¶ Returns a signer which uses
keyto generate signatures andsepto separate values.sepcannot be in the URL safe base64 alphabet. This alphabet contains alphanumeric characters, hyphens, and underscores.algorithmmust be an algorithm supported byhashlib, it defaults to'sha256'.fallback_keysis a list of additional values used to validate signed data, defaults toSECRET_KEY_FALLBACKS.Changed in Django Development version:The
fallback_keysargument was added.Не рекомендуется, начиная с версии 4.2: Поддержка передачи позиционных аргументов устарела.
Использование аргумента 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')[исходный код]¶ -
sign(value)[исходный код]¶ Знак
valueи добавьте к нему текущую метку времени.
-
unsign(value, max_age=None)[исходный код]¶ Проверяет, если
valueбыло подписано менееmax_ageсекунд назад, в противном случае выдаетSignatureExpired. Параметрmax_ageможет принимать целое число или объектdatetime.timedelta.
-
sign_object(obj, serializer=JSONSerializer, compress=False)¶ Кодировать, опционально сжимать, добавлять текущую метку времени и подписывать сложную структуру данных (например, список, кортеж или словарь).
-
unsign_object(signed_obj, serializer=JSONSerializer, max_age=None)¶ Checks if
signed_objwas signed less thanmax_ageseconds ago, otherwise raisesSignatureExpired. Themax_ageparameter can accept an integer or adatetime.timedeltaobject.
Не рекомендуется, начиная с версии 4.2: Поддержка передачи позиционных аргументов устарела.
-
Защита сложных структур данных¶
If you wish to protect a list, tuple or dictionary you can do so using the
Signer.sign_object() and unsign_object() methods, or signing module’s
dumps() or loads() functions (which are shortcuts for
TimestampSigner(salt='django.core.signing').sign_object()/unsign_object()).
These use JSON serialization under the hood. JSON ensures that even if your
SECRET_KEY is stolen an attacker will not be able to execute
arbitrary commands by exploiting the pickle format:
>>> 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)[исходный код]¶ Возвращает безопасную для URL, подписанную base64 сжатую строку JSON. Сериализованный объект подписывается с помощью
TimestampSigner.
-
loads(string, key=None, salt='django.core.signing', serializer=JSONSerializer, max_age=None, fallback_keys=None)[исходный код]¶ Обратный вариант
dumps(), повышаетBadSignature, если подпись не прошла. Проверяетmax_age(в секундах), если задано.Changed in Django Development version:The
fallback_keysargument was added.