Django`SECRET_KEY`设置

som*_*one 5 django settings secret-key

我对SECRET_KEYDjango中的设置有一些“简单”的问题:

  • 最小,最大和建议长度是多少?

    • 我可以留空吗?
    • 如果我使用了很长时间怎么办SECRET_KEY,其中的一些会被“浪费”吗?
  • 允许使用哪些字符?

    • 是否允许不可打印的字符(包括但不限于空格)?
  • 我应该何时更改?

    • 我知道,当我改变它,所有现有的cookies/ sessionssignature等将无效。
  • 是否可以从数据库(或其他来源)中检索它,而不是直接将其写在settings.py中?

ben*_*ben 13

让我们把这个问题分成两部分:

\n
    \n
  1. 密钥存储和轮换
  2. \n
  3. 密钥长度和允许的字符
  4. \n
\n

1) 密钥存储和密钥轮换

\n

密钥存储

\n

将密钥放入数据库是一个非常糟糕的主意。不要这样做。

\n

理想情况下,您也不希望在应用程序中的任何位置对密钥进行硬编码。settings.py这意味着您应该找到一种方法来用对(通常)环境变量(例如)的引用来替换硬编码密钥SECRET_KEY = os.getenv(\'SECRET_KEY\')

\n

至少,您至少应该将密钥从文件加载到环境变量中.env。python 中有多种方法可以实现这一点。

\n

但是,理想情况下,您不想将密钥存储在环境中的任何位置。更好的解决方案是使用秘密管理服务(例如,Hashicorp Vault、Doppler 等,或 IaaS/PaaS 提供的秘密管理器),在运行时获取密钥,并且仅将其存储在内存中。

\n

钥匙轮换

\n

旋转SECRET_KEYDjango<4.1将立即注销所有用户,使密码重置和电子邮件验证链接等失效

\n

在大多数情况下,这意味着您实际上只想在出现漏洞时轮换密钥。

\n

然而Django>=4.1SECRET_KEY_FALLBACKS已经引入了一个设置。

\n

现在,轮换密钥通常是一种很好的做法,因此您可能想要开始这样做。但是,正如文档中所述,回退到旧密钥的成本很高,因此您还需要限制回退密钥的数量。

\n

最终,答案将取决于您的特定安全模型以及您愿意在计算成本(以及在这种情况下给用户带来的不便Django<4.1)方面做出的权衡。

\n

2) 密钥长度和允许的字符

\n

虽然Jontas CD正确识别了 Django 如何自动生成密钥,但答案是不正确的。

\n

正确的答案是,除了少数限制之外,您的 DjangoSECRET_KEY可以是任意的strbytes任意长度。

\n

约束条件

\n

长度、禁止前缀和唯一字符

\n

以下检查(来自django.core.checks.security.base模块)定义了一些关于长度、前缀和唯一字符的约束:

\n
SECRET_KEY_INSECURE_PREFIX = "django-insecure-"\nSECRET_KEY_MIN_LENGTH = 50\nSECRET_KEY_MIN_UNIQUE_CHARACTERS = 5\n\ndef _check_secret_key(secret_key):\n    return (\n        len(set(secret_key)) >= SECRET_KEY_MIN_UNIQUE_CHARACTERS\n        and len(secret_key) >= SECRET_KEY_MIN_LENGTH\n        and not secret_key.startswith(SECRET_KEY_INSECURE_PREFIX)\n    )\n
Run Code Online (Sandbox Code Playgroud)\n

要将其用简单的英语表达,您SECRET_KEY必须:

\n
    \n
  1. 长度至少为 50 个字符
  2. \n
  3. 至少包含 5 个唯一字符
  4. \n
  5. 不以“django-insecure-”为前缀
  6. \n
\n

源编码

\n

正如 Django 文档中提到的SECRET_KEY,开发人员不应假设密钥是strbytes。因此,每次使用都应该根据所需的类型SECRET_KEY传递force_str()或。force_bytes()

\n

这隐含地表明密钥必须以有效字符串的形式提供,或者如果bytes提供的话,这些字节必须解码为有效字符串。

\n

至于什么构成有效字符串,自从Python 3.0引入了PEP3120以来,字符串的默认源编码一直是 UTF-8。

\n

这在 Django 中得到了进一步的加强。例如,django.utils.encoding.force_str()显式将 UTF-8 设置为源编码。

\n
def force_str(s, encoding="utf-8", strings_only=False, errors="strict"):\n\n    ...\n\n    try:\n        if isinstance(s, bytes):\n            s = str(s, encoding, errors)\n        else:\n            s = str(s)\n    except UnicodeDecodeError as e:\n        raise DjangoUnicodeDecodeError(s, *e.args)\n    return s\n
Run Code Online (Sandbox Code Playgroud)\n

如果由于某种原因您仍然停留在旧版本的 Python 上,那么任何 ASCII 字符大概都是公平的游戏。这将包括空格。

\n

为什么没有最大长度?

\n

在密码学中,我们通常使用固定长度的二进制密钥。我们从可变长度字符串(例如密码,SECRET_KEY等)转换为固定长度二进制密钥的方式是通过密钥派生函数。

\n

实际上,这意味着我们“散列”SECRET_KEY在任何加密函数中使用它之前对其进行“散列”。

\n

密钥的散列方式很大程度上取决于您所需的密钥长度(例如 128 位、256 位、512 位等),以及您希望通过密钥派生函数购买的针对暴力攻击的安全程度。它或多或少类似于密码散列,事实上,常见的密码散列算法也通常用作密钥派生函数。

\n

要了解 Django 中的情况,我们可以查看django.crypto.utils.salted_hmac()

\n
def salted_hmac(key_salt, value, secret=None, *, algorithm="sha1"):\n    ...\n    key_salt = force_bytes(key_salt)\n    secret = force_bytes(secret)\n    # snip the wrapping try/except block for brevity\n    hasher = getattr(hashlib, algorithm)  # returns hashlib.sha1\n    key = hasher(key_salt + secret).digest()\n    return hmac.new(key, msg=force_bytes(value), digestmod=hasher)\n
Run Code Online (Sandbox Code Playgroud)\n

在这里,您的SECRET_KEY(以及随机salt)要么被拉伸,要么被缩减为 160 位 SHA1 哈希值。

\n

换句话说,你的时间有多长并不重要SECRET_KEY;重要的是你的时间有多长。当实际需要使用它时,它总是会被拉伸/减少到正确的位数。

\n

这么长的钥匙会浪费吗?

\n

正如我们在上面看到的,Django 在签名消息时默认将密钥哈希为 160 位 sha1 哈希。

\n

然而,库存标准get_random_secret_key()会输出一个长度为 50 个字符的密钥,并从一组 50 个字符中随机选择。这给出了 log2(50^50) 的熵,即大约 282 位。

\n

那么这些额外的部分会被浪费吗?嗯,是的,也不是。

\n

从计算成本的角度来看,二进制编码和散列的时间和内存成本差异(比方说,50 个字符的字符串与 500 个字符的字符串)可以忽略不计。是的,如果您需要对一堆随机字符串执行数千次,您就会开始看到差异。然而,它不会对 Django 性能产生明显的影响。

\n

就浪费的熵而言(即,将具有 282 位熵的 SECRET_KEY 哈希为 160 位哈希),那么是的,如果超长密钥是真正随机的,那么它就是一种浪费。但是,我认为在使用伪随机方法生成密钥的情况下,生成一些额外的熵可能会带来一些额外的安全性。

\n

太长了;博士

\n

字符/长度

\n

DjangoSECRET_KEY可以是任何str(或bytes解码为 UTF-8 strlen(str) > 49,包含至少 5 个唯一字符,并且没有“django-insecure-”作为前缀。

\n

具体来说,这意味着:

\n
# Valid\nSECRET_KEY = b\'\\x20\\x20\\x20\\x63\\x6f\\x52\\x72\\x45\\x43\\x54\\x42\\x41\\x54\\x74\\x65\\x52\\x79\\x68\\x6f\\x52\\x53\\x65\\x73\\x54\\x41\\x70\\x6c\\x65\\xf0\\x9f\\x92\\xa9\\xf0\\x9f\\x8d\\x86\\xf0\\x9f\\x92\\xa6\\xf0\\x9f\\xa6\\xb4\\xf0\\x9f\\x8d\\x92\\xf0\\x9f\\x8d\\x91 \\xc2\\xb6\\xc2\\xbc\\xc3\\x8b\\xc3\\x9f\\xc3\\xb1\\xc4\\x86\\xc4\\x9c\\xc5\\x94\\xc7\\xb8\\xc8\\x8f\\xc8\\xa4\\xd0\\x82\\xf0\\x9f\\x91\\x89\\xf0\\x9f\\x91\\x8c\\xf0\\x9f\\x91\\x85 \\xf0\\x9f\\x91\\x84\\x21\\x20\\x61\\x6e\\x64\\x20\\x6f\\x6e\\x20\\x61\\x6e\\x64\\x20\\x6f\\x6e\\x20\\x61\\x6e\\x64\\x20\\x6f\\x6e\\x2e\\x2e\\x2e\'\n\n# Valid\nSECRET_KEY = "   coRrECTBATteRyhoRSesTAple \xc2\xb6\xc2\xbc\xc3\x8b\xc3\x9f\xc3\xb1\xc4\x86\xc4\x9c\xc5\x94\xc7\xb8\xc8\x8f\xc8\xa4\xd0\x82 ! and on and on and on..."\n\n# Invalid: the first byte (0xFF) is not a valid UTF-8 code unit\n# Note: Django never checks this, so Django will seem okay at first. However,\n# this will throw a UnicodeDecodeError as soon as anything attempts to\n# force_str() it.\nSECRET_KEY = b\'\\xFF68(*ctf1e2r=##e+nl=7(&w*v5z!%h=dej)ypjv&p=n9is4d04\'\n\n# Invalid: To short\nSECRET_KEY = \'jfieo0w\'\n\n# Invalid: Not enough unique characters\nSECRET_KEY = \'abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdab\'\n
Run Code Online (Sandbox Code Playgroud)\n


Jon*_* CD 5

DjangoSECRET_KEY每次启动项目时都会生成一个 ,所以,不,你不能将其留空

SECRET_KEY长度始终为 50 个字符。

没有空格。这是 Django 用于生成它的方法。

def get_random_secret_key():
    """
    Return a 50 character random string usable as a SECRET_KEY setting value.
    """
    chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)'
    return get_random_string(50, chars)
Run Code Online (Sandbox Code Playgroud)

是的,您可以将其存储在其他地方,例如使用env设置方法,例如

当然,您始终可以查看有关代码片段的文档SECRET_KEY或代码片段,例如:

除此之外,还可以选择创建票证来改进 SECRET_KEY文档 - 如果您认为是这样的话。

  • 我找不到任何对 SECRET_KEY 始终为 50 个字符的引用。文档说它是一个字符串,没有额外的内容,所以我认为它可以是任意长度。 (3认同)
  • 我知道“django”在项目创建过程中自动生成 SECRET_KEY,但当然,我可以手动编辑它。 (2认同)