加密签名

Web应用程序安全的金科玉律是永远不要信任来自不可信来源的数据。有时,通过不受信任的介质传递数据可能很有用。在知道将检测到任何篡改的情况下,可以通过不受信任的通道安全地传递经过加密签名的值。

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

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

  • 生成“恢复我的帐户”URL,以发送给丢失密码的用户。

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

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

保护 SECRET_KEYSECRET_KEY_FALLBACKS

当您使用 startproject , the 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 safe base64 alphabet 。此字母表包含字母数字字符、连字符和下划线。 algorithm 必须是受支持的算法 hashlib ,则默认为 'sha256'fallback_keys 是用于验证签名数据的附加值的列表,默认为 SECRET_KEY_FALLBACKS

使用 salt 论点

如果不希望特定字符串的每个匹配项都具有相同的签名哈希,则可以使用可选的 salt 参数设置为 Signer 班级。使用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 你的盐的参数不需要保密。

验证时间戳值

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)

检查是否 signed_obj 签署的时间少于 max_age 秒前,否则将引发 SignatureExpired 。这个 max_age 参数可以接受整数或 datetime.timedelta 对象。

保护复杂的数据结构

如果您希望保护列表、元组或词典,可以使用 Signer.sign_object()unsign_object() 方法或签名模块的 dumps()loads() 函数(它们是 TimestampSigner(salt='django.core.signing').sign_object()/unsign_object() )。它们在幕后使用JSON序列化。JSON确保即使您的 SECRET_KEY 被盗后,攻击者将无法通过利用PICLE格式执行任意命令:

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