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.
你可能还发现签名对以下方面很有用:
SECRET_KEY
and SECRET_KEY_FALLBACKS
¶当你使用 startproject
创建一个新的Django项目时,settings.py
文件会自动生成,并随机得到一个 SECRET_KEY
值。这个值是保证签名数据安全的关键——你必须保证这个值的安全,否则攻击者可以用它来生成自己的签名值。
SECRET_KEY_FALLBACKS
can be used to rotate secret keys. The
values will not be used to sign data, but if specified, they will be used to
validate signed data and must be kept secure.
Django's signing methods live in the django.core.signing
module.
To sign a value, first instantiate a Signer
instance:
>>> from django.core.signing import Signer
>>> signer = Signer()
>>> value = signer.sign("My string")
>>> value
'My string:GdMGD6HNQ_qdgxYP8yBZAdAIV1w'
The signature is appended to the end of the string, following the colon.
You can retrieve the original value using the unsign
method:
>>> original = signer.unsign(value)
>>> original
'My string'
If you pass a non-string value to sign
, the value will be forced to string
before being signed, and the unsign
result will give you that string
value:
>>> signed = signer.sign(2.5)
>>> original = signer.unsign(signed)
>>> original
'2.5'
If you wish to protect a list, tuple, or dictionary you can do so using the
sign_object()
and unsign_object()
methods:
>>> signed_obj = signer.sign_object({"message": "Hello!"})
>>> signed_obj
'eyJtZXNzYWdlIjoiSGVsbG8hIn0:Xdc-mOFDjs22KsQAqfVfi8PQSPdo3ckWJxPWwQOFhR4'
>>> obj = signer.unsign_object(signed_obj)
>>> obj
{'message': 'Hello!'}
详见:ref:signing-complex-data
If the signature or value have been altered in any way, a
django.core.signing.BadSignature
exception will be raised:
>>> from django.core import signing
>>> value += "m"
>>> try:
... original = signer.unsign(value)
... except signing.BadSignature:
... print("Tampering detected!")
...
By default, the Signer
class uses the SECRET_KEY
setting to
generate signatures. You can use a different secret by passing it to the
Signer
constructor:
>>> signer = Signer(key="my-other-secret")
>>> value = signer.sign("My string")
>>> value
'My string:EkfQJafvGyiofrdGnuthdxImIJw'
Signer
(*, key=None, sep=':', salt=None, algorithm=None, fallback_keys=None)¶Returns a signer which uses key
to generate signatures and sep
to
separate values. sep
cannot be in the URL safe base64 alphabet. This alphabet contains alphanumeric characters, hyphens,
and underscores. algorithm
must be an algorithm supported by
hashlib
, it defaults to 'sha256'
. fallback_keys
is a list
of additional values used to validate signed data, defaults to
SECRET_KEY_FALLBACKS
.
4.2 版后已移除: Support for passing positional arguments is deprecated.
salt
参数¶If you do not wish for every occurrence of a particular string to have the same
signature hash, you can use the optional salt
argument to the Signer
class. Using a salt will seed the signing hash function with both the salt and
your 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
is a subclass of Signer
that appends a signed
timestamp to the value. This allows you to confirm that a signed value was
created within a specified period of time:
>>> 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'
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)¶Encode, optionally compress, append current timestamp, and sign complex data structure (e.g. list, tuple, or dictionary).
unsign_object
(signed_obj, serializer=JSONSerializer, max_age=None)¶Checks if signed_obj
was signed less than max_age
seconds ago,
otherwise raises SignatureExpired
. The max_age
parameter can
accept an integer or a datetime.timedelta
object.
4.2 版后已移除: Support for passing positional arguments is deprecated.
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'}
Because of the nature of JSON (there is no native distinction between lists
and tuples) if you pass in a tuple, you will get a list from
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
(以秒为单位)。
12月 05, 2023