---
title: "加密签名"
version: 6.1
locale: zh-hans
source: https://docs.djangoproject.com/zh-hans/6.1/topics/signing/
canonical: https://djangodocs.dev/zh-hans/6.1/topics/signing/
---
# 加密签名

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

Django 提供了用于签名值的低级 API 和用于设置和读取签名 cookie 的高级 API，签名在 web 应用程序中最常见的用途之一就是签名 cookie。

你可能还发现签名对以下方面很有用：

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

## 保护 `SECRET_KEY` 和 `SECRET_KEY_FALLBACKS`

当你使用 [`startproject`](/zh-hans/6.1/ref/django-admin/#django-admin-startproject) 创建一个新的Django项目时，`settings.py` 文件会自动生成，并随机得到一个 [`SECRET_KEY`](/zh-hans/6.1/ref/settings/#std-setting-SECRET_KEY) 值。这个值是保证签名数据安全的关键——你必须保证这个值的安全，否则攻击者可以用它来生成自己的签名值。

[`SECRET_KEY_FALLBACKS`](/zh-hans/6.1/ref/settings/#std-setting-SECRET_KEY_FALLBACKS) 可以用于轮换密钥。这些值不会用于签署数据，但如果指定了，它们将用于验证签署的数据，并必须保持安全。

## 使用低级 API

Django 的签名方法位于 `django.core.signing` 模块中。要签署一个值，首先实例化一个 `Signer` 实例：

```pycon
>>> from django.core.signing import Signer
>>> signer = Signer()
>>> value = signer.sign("My string")
>>> value
'My string:v9G-nxfz3iQGTXrePqYPlGvH79WTcIgj1QIQSUODTW0'
```

签名被追加到字符串的末尾，跟在冒号后面。你可以使用 `unsign` 方法检索原始值：

```pycon
>>> original = signer.unsign(value)
>>> original
'My string'
```

如果你将一个非字符串值传递给 `sign`，该值将在签名之前被强制转换为字符串，而 `unsign` 的结果将给你该字符串值：

```pycon
>>> signed = signer.sign(2.5)
>>> original = signer.unsign(signed)
>>> original
'2.5'
```

如果你希望保护一个列表、元组或字典，可以使用 `sign_object()` 和 `unsign_object()` 方法来实现：

```pycon
>>> signed_obj = signer.sign_object({"message": "Hello!"})
>>> signed_obj
'eyJtZXNzYWdlIjoiSGVsbG8hIn0:bzb48DBkB-bwLaCnUVB75r5VAPUEpzWJPrTb80JMIXM'
>>> obj = signer.unsign_object(signed_obj)
>>> obj
{'message': 'Hello!'}
```

详见 [保护复杂的数据结构](#signing-complex-data)。

如果签名或值已经被以任何方式更改，将会引发 `django.core.signing.BadSignature` 异常：

```pycon
>>> from django.core import signing
>>> value += "m"
>>> try:
...     original = signer.unsign(value)
... except signing.BadSignature:
...     print("Tampering detected!")
...
```

默认情况下，`Signer` 类使用 [`SECRET_KEY`](/zh-hans/6.1/ref/settings/#std-setting-SECRET_KEY) 设置来生成签名。你可以通过将不同的秘钥传递给 `Signer` 构造函数来使用不同的秘钥：

```pycon
>>> signer = Signer(key="my-other-secret")
>>> value = signer.sign("My string")
>>> value
'My string:o3DrrsT6JRB73t-HDymfDNbTSxfMlom2d8TiUlb1hWY'
```

#### `class Signer(* (Keyword-only parameters separator (PEP 3102)), key=None, sep=':', salt=None, algorithm=None, fallback_keys=None)`

Returns a signer which uses `key` to generate signatures and `sep` to
separate values. `sep` cannot be in the [**URL safe base64 alphabet**](https://datatracker.ietf.org/doc/html/rfc4648.html#section-5). This alphabet contains alphanumeric characters, hyphens,
and underscores. `algorithm` must be an algorithm supported by
[`hashlib`](https://docs.python.org/3/library/hashlib.html#module-hashlib), it defaults to `'sha256'`. `fallback_keys` is a list
of additional values used to validate signed data, defaults to
[`SECRET_KEY_FALLBACKS`](/zh-hans/6.1/ref/settings/#std-setting-SECRET_KEY_FALLBACKS).

### 使用 `salt` 参数

如果你不希望每次出现特定字符串时都具有相同的签名哈希值，可以使用 `Signer` 类的可选参数 `salt`。使用盐会为签名哈希函数提供盐和你的 [`SECRET_KEY`](/zh-hans/6.1/ref/settings/#std-setting-SECRET_KEY)：

```pycon
>>> signer = Signer()
>>> signer.sign("My string")
'My string:v9G-nxfz3iQGTXrePqYPlGvH79WTcIgj1QIQSUODTW0'
>>> signer.sign_object({"message": "Hello!"})
'eyJtZXNzYWdlIjoiSGVsbG8hIn0:bzb48DBkB-bwLaCnUVB75r5VAPUEpzWJPrTb80JMIXM'
>>> signer = Signer(salt="extra")
>>> signer.sign("My string")
'My string:YMD-FR6rof3heDkFRffdmG4pXbAZSOtb-aQxg3vmmfc'
>>> signer.unsign("My string:YMD-FR6rof3heDkFRffdmG4pXbAZSOtb-aQxg3vmmfc")
'My string'
>>> signer.sign_object({"message": "Hello!"})
'eyJtZXNzYWdlIjoiSGVsbG8hIn0:-UWSLCE-oUAHzhkHviYz3SOZYBjFKllEOyVZNuUtM-I'
>>> signer.unsign_object(
...     "eyJtZXNzYWdlIjoiSGVsbG8hIn0:-UWSLCE-oUAHzhkHviYz3SOZYBjFKllEOyVZNuUtM-I"
... )
{'message': 'Hello!'}
```

Using salt in this way puts the different signatures into different
namespaces. A signature that comes from one namespace (a particular salt
value) cannot be used to validate the same plaintext string in a different
namespace that is using a different salt setting. The result is to prevent an
attacker from using a signed string generated in one place in the code as input
to another piece of code that is generating (and verifying) signatures using a
different salt.

与你的 [`SECRET_KEY`](/zh-hans/6.1/ref/settings/#std-setting-SECRET_KEY) 不同，你的盐参数不需要保密。

### 验证时间戳值

`TimestampSigner` 是 [`Signer`](#django.core.signing.Signer) 的子类，它会在值后附加一个带签名的时间戳。这允许你确认一个签名值是否在指定的时间段内创建：

```pycon
>>> from datetime import timedelta
>>> from django.core.signing import TimestampSigner
>>> signer = TimestampSigner()
>>> value = signer.sign("hello")
>>> value
'hello:1stLqR:_rvr4oXCgT4HyfwjXaU39QvTnuNuUthFRCzNOy4Hqt0'
>>> 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(* (Keyword-only parameters separator (PEP 3102)), key=None, sep=':', salt=None, algorithm='sha256')`

#### `sign(value)`

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

#### `unsign(value, max_age=None)`

Checks if `value` was signed less than `max_age` seconds ago,
otherwise raises `SignatureExpired`. The `max_age` parameter can
accept an integer or a [`datetime.timedelta`](https://docs.python.org/3/library/datetime.html#datetime.timedelta) object.

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

对复杂的数据结构（例如列表、元组或字典）进行编码，可选地压缩数据，追加当前时间戳，并对其进行签名。

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

Checks if `signed_obj` was signed less than `max_age` seconds ago,
otherwise raises `SignatureExpired`. The `max_age` parameter can
accept an integer or a [`datetime.timedelta`](https://docs.python.org/3/library/datetime.html#datetime.timedelta) object.

### 保护复杂的数据结构

如果你希望保护一个列表、元组或字典，可以使用 `Signer.sign_object()` 和 `unsign_object()` 方法，或者使用签名模块的 `dumps()` 或 `loads()` 函数（它们是 `TimestampSigner(salt='django.core.signing').sign_object()/unsign_object()` 的快捷方式）。这些方法在底层使用了 JSON 序列化。JSON 确保即使你的 [`SECRET_KEY`](/zh-hans/6.1/ref/settings/#std-setting-SECRET_KEY) 被窃取，攻击者也无法通过利用 pickle 格式来执行任意命令：

```pycon
>>> from django.core import signing
>>> signer = signing.TimestampSigner()
>>> value = signer.sign_object({"foo": "bar"})
>>> value
'eyJmb28iOiJiYXIifQ:1stLrZ:_QiOBHafwucBF9FyAr54qEs84ZO1UdsO1XiTJCvvdno'
>>> signer.unsign_object(value)
{'foo': 'bar'}
>>> value = signing.dumps({"foo": "bar"})
>>> value
'eyJmb28iOiJiYXIifQ:1stLsC:JItq2ZVjmAK6ivrWI-v1Gk1QVf2hOF52oaEqhZHca7I'
>>> signing.loads(value)
{'foo': 'bar'}
```

由于 JSON 的性质（没有本地区分列表和元组的方式），如果你传递一个元组，你将从 `signing.loads(object)` 得到一个列表：

```pycon
>>> 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`](#django.core.signing.TimestampSigner) 对序列化对象进行签名。

#### `loads(string, key=None, salt='django.core.signing', serializer=JSONSerializer, max_age=None, fallback_keys=None)`

与 `dumps()` 相反，如果签名失败引发 `BadSignature`。如果给定，则检查 `max_age` （以秒为单位）。
