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

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

Django 提供了用于签署值的低级 API 和用于设置和读取已签名 cookie 的高级 API，这是 Web 应用程序中签名的最常见用法之一。

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

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

## 保护 `SECRET_KEY`

当你使用 [`startproject`](/zh-hans/3.2/ref/django-admin/#django-admin-startproject) 创建一个新的Django项目时，`settings.py` 文件会自动生成，并随机得到一个 [`SECRET_KEY`](/zh-hans/3.2/ref/settings/#std-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'
```

If you wish to protect a list, tuple, or dictionary you can do so using the
`sign_object()` and `unsign_object()` methods:

```
>>> signed_obj = signer.sign_object({'message': 'Hello!'})
>>> signed_obj
'eyJtZXNzYWdlIjoiSGVsbG8hIn0:Xdc-mOFDjs22KsQAqfVfi8PQSPdo3ckWJxPWwQOFhR4'
>>> obj = signer.unsign_object(signed_obj)
>>> obj
{'message': 'Hello!'}
```

See [保护复杂的数据结构](#signing-complex-data) for more details.

如果签名或值被以任何方式修改，将引发 `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`](/zh-hans/3.2/ref/settings/#std-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 字母表**](https://datatracker.ietf.org/doc/html/rfc4648.html#section-5) 中。这个字母表包含字母数字字符、连字符和下划线。`algorithm` 必须是 [`hashlib`](https://docs.python.org/3/library/hashlib.html#module-hashlib) 支持的算法。默认为 `'sha256'`。

> **Changed in Django 3.1**
>
> 加入 `algorithm` 参数。

> **Changed in Django 3.2**
>
> The `sign_object()` and `unsign_object()` methods were added.

### 使用 `salt` 参数

如果你不希望一个特定字符串的每一次出现都有相同的签名哈希值，你可以使用 `Signer` 类的可选 `salt` 参数。使用盐会将盐和你的 [`SECRET_KEY`](/zh-hans/3.2/ref/settings/#std-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!'}
```

以这种方式使用盐，会将不同的签名放入不同的命名空间。 来自一个命名空间的签名（一个特定的盐值）不能用于验证在使用不同盐值设置的不同命名空间中的同一明文字符串。这样做的结果是防止攻击者将代码中某个地方生成的签名字符串作为输入，输入到使用不同盐值生成（和验证）签名的另一段代码中。

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

> **Changed in Django 3.2**
>
> The `sign_object()` and `unsign_object()` methods were added.

### 验证时间戳值

`TimestampSigner` 是 [`Signer`](#django.core.signing.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`](https://docs.python.org/3/library/datetime.html#datetime.timedelta) 对象。

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

> **New in Django 3.2**

Encode, optionally compress, append current timestamp, and sign complex
data structure (e.g. list, tuple, or dictionary).

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

> **New in Django 3.2**

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.

> **Changed in Django 3.1**
>
> 加入 `algorithm` 参数。

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

If you wish to protect a list, tuple or dictionary you can do so using the
`Signer.sign_object()` and `unsign_object()` methods, or signing module's
`dumps()` or `loads()` functions (which are shortcuts for
`TimestampSigner(salt='django.core.signing').sign_object()/unsign_object()`).
These use JSON serialization under the hood. JSON ensures that even if your
[`SECRET_KEY`](/zh-hans/3.2/ref/settings/#std-setting-SECRET_KEY) is stolen an attacker will not be able to execute
arbitrary commands by exploiting the pickle format:

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

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

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

> **Changed in Django 3.2**
>
> The `sign_object()` and `unsign_object()` methods were added.
