加密签名¶
Web 应用程序安全的黄金法则是不信任来自不受信任来源的数据。有时将数据通过不受信任的媒介传递会很有用。加密签名值可以通过不受信任的通道安全传递,知道任何篡改都会被检测到。
Django 同时提供了一个用于签名值的低级 API 和一个用于设置和读取签名 cookie 的高级 API,这是 Web 应用程序中签名最常见的用途之一。
你还可以发现签名对以下内容很有用
- 为丢失密码的用户生成“恢复我的帐户”URL。
- 确保存储在隐藏表单字段中的数据未被篡改。
- 生成一次性秘密 URL,以允许临时访问受保护的资源,例如用户已付费的可下载文件。
保护 SECRET_KEY
和 SECRET_KEY_FALLBACKS
¶
当您使用 startproject
创建新的 Django 项目时,settings.py
文件会自动生成,并获取一个随机的 SECRET_KEY
值。此值是保护已签名数据的密钥,您必须确保其安全,否则攻击者可能会利用它生成他们自己的已签名值。
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)¶ 返回一个签名器,该签名器使用
key
生成签名,使用sep
分隔值。sep
不能在 URL 安全 base64 字母表 中。此字母表包含字母数字字符、连字符和下划线。algorithm
必须是hashlib
支持的算法,其默认为'sha256'
。fallback_keys
是用于验证已签名数据的附加值列表,默认为SECRET_KEY_FALLBACKS
。自 4.2 版本起已弃用:不支持传递位置参数。
使用 salt
参数¶
如果您不希望特定字符串的每次出现都具有相同的签名哈希,则可以使用 Signer
类的可选 salt
参数。使用 salt 将使用 salt 和 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!'}
以这种方式使用 salt 会将不同的签名放入不同的命名空间。来自一个命名空间(特定 salt 值)的签名不能用于在使用不同 salt 设置的不同命名空间中验证相同的明文字符串。结果是防止攻击者将在一个地方生成的已签名字符串用作输入,而另一个地方使用不同的 salt 生成(并验证)签名。
与 SECRET_KEY
不同,您的 salt 参数不需要保密。
验证带时间戳的值¶
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'
-
类
TimestampSigner
(*, key=无, sep=':', salt=无, algorithm='sha256')¶ -
签名
(值)¶ 对
值
签名并追加当前时间戳。
-
取消签名
(值, 最大年龄=无)¶ 检查
值
是否在最大年龄
秒之前签名,否则引发签名过期
。最大年龄
参数可以接受整数或datetime.timedelta
对象。
-
签名对象
(obj, 序列化器=JSONSerializer, 压缩=否)¶ 对复杂的数据结构(例如列表、元组或字典)进行编码、可选压缩、追加当前时间戳并签名。
-
取消签名对象
(已签名对象, 序列化器=JSONSerializer, 最大年龄=无)¶ 检查
signed_obj
是否在max_age
秒之前签名,否则引发SignatureExpired
。max_age
参数可以接受一个整数或datetime.timedelta
对象。
自 4.2 版本起已弃用:不支持传递位置参数。
-
保护复杂数据结构¶
如果您希望保护列表、元组或字典,可以使用 Signer.sign_object()
和 unsign_object()
方法,或签名模块的 dumps()
或 loads()
函数(它们是 TimestampSigner(salt='django.core.signing').sign_object()/unsign_object()
的快捷方式)。它们在内部使用 JSON 序列化。JSON 确保即使您的 SECRET_KEY
被窃取,攻击者也无法通过利用 pickle 格式来执行任意命令
>>> 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 安全、签名、基于 64 压缩的 JSON 字符串。序列化的对象使用
TimestampSigner
签名。
-
loads
(字符串, 密钥=无, 盐='django.core.signing', 序列化器=JSONSerializer, 最大年龄=无, 后备密钥=无)¶
的反向,如果签名失败,则引发dumps()
。如果给定,则检查BadSignature
(以秒为单位)。max_age