加密签名 — Django 文档

来自菜鸟教程
Django/docs/3.2.x/topics/signing
跳转至:导航、​搜索

加密签名

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

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

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

  • 生成“恢复我的帐户”URL 以发送给丢失密码的用户。
  • 确保存储在隐藏表单字段中的数据未被篡改。
  • 生成一次性秘密 URL 以允许临时访问受保护的资源,例如用户已付费的可下载文件。

保护 SECRET_KEY

当您使用 :djadmin:`startproject` 创建新的 Django 项目时,会自动生成 settings.py 文件并获取随机 :setting:`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'

如果您将非字符串值传递给 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 类使用 :setting:`SECRET_KEY` 设置来生成签名。 您可以通过将其传递给 Signer 构造函数来使用不同的秘密:

>>> signer = Signer('my-other-secret')
>>> value = signer.sign('My string')
>>> value
'My string:EkfQJafvGyiofrdGnuthdxImIJw'
class Signer(key=None, sep=':', salt=None, algorithm=None)

返回使用 key 生成签名并使用 sep 分隔值的签名者。 sep 不能在 URL 安全 base64 字母表 中。 此字母表包含字母数字字符、连字符和下划线。 algorithm必须是hashlib支持的算法,默认为'sha256'

3.1 版更改: 添加了 algorithm 参数。

3.2 版更改: 添加了 sign_object()unsign_object() 方法。


使用 salt 参数

如果您不希望特定字符串的每次出现都具有相同的签名哈希,您可以使用可选的 salt 参数到 Signer 类。 使用盐将使用盐和您的 :setting:`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 会将不同的签名放入不同的命名空间。 来自一个命名空间(特定盐值)的签名不能用于验证使用不同盐设置的不同命名空间中的相同纯文本字符串。 结果是防止攻击者使用在代码中的一个地方生成的签名字符串作为另一段代码的输入,该代码使用不同的盐生成(和验证)签名。

与您的 :setting:`SECRET_KEY` 不同,您的 salt 参数不需要保密。

3.2 版更改: 添加了 sign_object()unsign_object() 方法。


验证时间戳值

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')
sign(value)

签署 value 并将当前时间戳附加到它。

unsign(value, max_age=None)

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

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

3.2 版中的新功能。

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

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

3.2 版中的新功能。

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

3.1 版更改: 添加了 algorithm 参数。


保护复杂的数据结构

如果您希望保护列表、元组或字典,您可以使用 Signer.sign_object()unsign_object() 方法,或签名模块的 dumps()loads() 函数(这是 TimestampSigner(salt='django.core.signing').sign_object()/unsign_object() 的快捷方式)。 这些在幕后使用 JSON 序列化。 JSON 确保即使您的 :setting:`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 安全、签名的 base64 压缩 JSON 字符串。 序列化对象使用 TimestampSigner 签名。
loads(string, key=None, salt='django.core.signing', serializer=JSONSerializer, max_age=None)
dumps() 的反转,如果签名失败,则引发 BadSignature。 如果给出,检查 max_age(以秒为单位)。

3.2 版更改: 添加了 sign_object()unsign_object() 方法。