Skip to main content

密码签名

Web应用程序安全的黄金规则是永远不会信任来自不受信任来源的数据。有时,通过不可信介质传递数据可能很有用。加密签名的值可以通过不可信的信道传递,因为知道将检测到任何篡改。

Django提供了一个用于分配值的底层API和一个用于设置和读取签名cookie的高级API,这是登录Web应用程序最常见的用途之一。

您还可能发现签名对以下内容有用:

  • 生成“恢复我的帐户”网址,以便发送给失去密码的用户。

  • 确保隐藏表单字段中存储的数据未被篡改。

  • 生成一次性秘密URL以允许临时访问受保护资源,例如用户已付费的可下载文件。

保护 SECRET_KEY

当您使用 startproject 创建新的Django项目时,将自动生成 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安全base64字母表 中。此字母表包含字母数字字符,连字符和下划线。

使用 salt 参数

如果你不希望每次出现一个特定的字符串有相同的签名散列,你可以使用可选的 salt 参数到 Signer 类。使用salt将使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')
'My string'

以这种方式使用salt将不同的签名放在不同的命名空间中。来自一个命名空间(特定salt值)的签名不能用于验证在使用不同salt设置的不同命名空间中的相同纯文本字符串。结果是防止攻击者使用在代码中的一个地方生成的带符号的字符串作为使用不同的盐生成(和验证)签名的另一段代码的输入。

与你的 SECRET_KEY 不同,你的salt参数不需要保密。

验证带时间戳的值

TimestampSignerSigner 的一个子类,它将一个已签名的时间戳附加到该值。这允许您确认在指定时间段内创建了签名值:

>>> 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 秒前签署,否则提出 SignatureExpiredmax_age 参数可以接受整数或 datetime.timedelta 对象。

保护复杂的数据结构

如果你想保护一个列表,元组或字典,你可以使用签名模块的 dumpsloads 函数。这些模仿Python的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'}

因为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 (以秒为单位)(如果给出)。