加密签名

Web 应用安全的黄金法则是不信任来自不可信来源的数据。有时,通过不可信的媒介传递数据可能很有用。加密签名值可以通过不可信的通道安全地传递,因为可以确保任何篡改都会被检测到。

Django 提供了用于签名值的低级 API 和用于设置和读取签名 Cookie 的高级 API,这是 Web 应用中签名最常见的用途之一。

您可能还会发现签名对以下方面很有用:

  • 为忘记密码的用户生成“找回我的帐户”网址。

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

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

保护 SECRET_KEYSECRET_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)[source]

返回一个签名器,它使用 key 生成签名,并使用 sep 分隔值。sep 不能在 URL 安全 base64 字母表 中。此字母表包含字母数字字符、连字符和下划线。algorithm 必须是 hashlib 支持的算法,默认为 'sha256'fallback_keys 是用于验证签名数据的附加值的列表,默认为 SECRET_KEY_FALLBACKS

使用 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 参数不需要保密。

验证带时间戳的值

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, algorithm='sha256')[source]
sign(value)[source]

签名 value 并附加当前时间戳。

unsign(value, max_age=None)[source]

检查 value 是否在 max_age 秒前签名,否则引发 SignatureExpiredmax_age 参数可以接受整数或 datetime.timedelta 对象。

sign_object(obj, serializer=JSONSerializer, compress=False)

编码、可选压缩、附加当前时间戳和签名复杂数据结构(例如列表、元组或字典)。

unsign_object(signed_obj, serializer=JSONSerializer, max_age=None)

检查 signed_obj 是否在 max_age 秒前签名,否则引发 SignatureExpiredmax_age 参数可以接受整数或 datetime.timedelta 对象。

保护复杂数据结构

如果您想保护列表、元组或字典,可以使用 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)[source]

返回 URL 安全的、已签名的 base64 压缩 JSON 字符串。使用 TimestampSigner 对序列化对象进行签名。

loads(string, key=None, salt='django.core.signing', serializer=JSONSerializer, max_age=None, fallback_keys=None)[source]

dumps() 的反向操作,如果签名失败则引发 BadSignature。如果给出,则检查 max_age(以秒为单位)。

返回顶部