密码管理通常不应被不必要地重新发明,Django提供一套安全灵活的工具来管理用户密码。如何存储密码,如何配置存储哈希以及一些使用哈希密码的实用程序。

也可看看

即使用户可能使用强密码,攻击者也可能能够窃听其连接。使用HTTPS避免通过纯HTTP连接发送密码(或任何其他敏感数据),因为它们很容易被密码嗅探。

Django如何存储密码

Django提供了一个灵活的密码存储系统,默认情况下使用PBKDF2。

对象的password属性 User是以下格式的字符串:

<algorithm>$<iterations>$<salt>$<hash>

这些是用于存储用户密码的组件,由美元符号字符分隔,包括以下各项:哈希算法,算法迭代次数(工作因子),随机盐和所得的密码哈希。该算法是Django可以使用的多种单向哈希或密码存储算法之一;见下文。迭代描述算法在哈希上运行的次数。盐是所使用的随机种子,散列是单向函数的结果。

默认情况下,Django将PBKDF2算法与SHA256哈希结合使用,这是NIST推荐的密码扩展机制。对于大多数用户来说,这应该足够了:这是相当安全的,需要大量的计算时间才能中断。

但是,根据您的要求,您可以选择其他算法,甚至可以使用自定义算法来匹配您的特定安全情况。同样,大多数用户不需要这样做-如果您不确定,则可能不需要。如果您这样做,请继续阅读:

Django通过咨询PASSWORD_HASHERS设置来选择要使用的算法 。这是此Django安装支持的哈希算法类的列表。此列表中的第一个条目(即settings.PASSWORD_HASHERS[0])将用于存储密码,所有其他条目都是有效的哈希表,可用于检查现有密码。这意味着,如果要使用其他算法,则需要进行修改PASSWORD_HASHERS以在列表中首先列出首选算法。

的默认PASSWORD_HASHERS值为:

PASSWORD_HASHERS = [
    'django.contrib.auth.hashers.PBKDF2PasswordHasher',
    'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
    'django.contrib.auth.hashers.Argon2PasswordHasher',
    'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
]

这意味着Django将使用PBKDF2来存储所有密码,但将支持检查PBKDF2SHA1,argon2bcrypt存储的密码。

接下来的几节介绍了高级用户可能希望修改此设置的几种常用方法。

将Argon2与Django一起使用

Argon2是2015年密码哈希竞赛的赢家,该竞赛是一个社区组织的公开竞赛,旨在选择下一代哈希算法。它的设计不比在普通CPU上计算更容易在自定义硬件上计算。

Argon2不是Django的默认设置,因为它需要第三方库。但是,“密码哈希竞赛”面板建议立即使用Argon2,而不是Django支持的其他算法。

要将Argon2用作默认存储算法,请执行以下操作:

  1. 安装argon2-cffi库。这可以通过运行来完成,它等效于 (以及Django的任何版本要求)。pip install django[argon2]``pip install argon2-cffi``setup.py

  2. 修改PASSWORD_HASHERSArgon2PasswordHasher首先列出。也就是说,在您的设置文件中,您将:

    PASSWORD_HASHERS = [
        'django.contrib.auth.hashers.Argon2PasswordHasher',
        'django.contrib.auth.hashers.PBKDF2PasswordHasher',
        'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
        'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
    ]
    

    如果您需要Django 升级密码,请在此列表中保留和/或添加任何条目。

bcrypt与Django一起使用

Bcrypt是一种流行的密码存储算法,专门用于长期密码存储。它不是Django的默认设置,因为它需要使用第三方库,但是由于许多人可能想要使用它,因此Django只需最小的努力即可支持bcrypt。

要将Bcrypt用作默认存储算法,请执行以下操作:

  1. 安装bcrypt库。这可以通过运行来完成,它等效于 (以及Django的任何版本要求)。pip install django[bcrypt]``pip install bcrypt``setup.py

  2. 修改PASSWORD_HASHERSBCryptSHA256PasswordHasher 首先列出。也就是说,在您的设置文件中,您将:

    PASSWORD_HASHERS = [
        'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
        'django.contrib.auth.hashers.PBKDF2PasswordHasher',
        'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
        'django.contrib.auth.hashers.Argon2PasswordHasher',
    ]
    

    如果您需要Django 升级密码,请在此列表中保留和/或添加任何条目。

就是这样–现在,您的Django安装将使用Bcrypt作为默认存储算法。

增加工作系数

PBKDF2和bcrypt

PBKDF2和bcrypt算法使用许多迭代或哈希运算。这故意降低了攻击者的速度,使对散列密码的攻击更加困难。但是,随着计算能力的提高,迭代次数需要增加。我们选择了一个合理的默认值(并将在每个Django版本中增加默认值),但您可能希望根据您的安全需求和可用的处理能力对其进行调高或调低。为此,您将子类化适当的算法并覆盖iterations 参数。例如,要增加默认PBKDF2算法使用的迭代次数:

  1. 创建一个子类django.contrib.auth.hashers.PBKDF2PasswordHasher

    from django.contrib.auth.hashers import PBKDF2PasswordHasher
    
    class MyPBKDF2PasswordHasher(PBKDF2PasswordHasher):
        """
        A subclass of PBKDF2PasswordHasher that uses 100 times more iterations.
        """
        iterations = PBKDF2PasswordHasher.iterations * 100
    

    将此保存在项目中的某个位置。例如,您可以将其放在类似的文件中myproject/hashers.py

  2. 将新的哈希器添加为中的第一个条目PASSWORD_HASHERS

    PASSWORD_HASHERS = [
        'myproject.hashers.MyPBKDF2PasswordHasher',
        'django.contrib.auth.hashers.PBKDF2PasswordHasher',
        'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
        'django.contrib.auth.hashers.Argon2PasswordHasher',
        'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
    ]
    

就是这样–现在,当您使用PBKDF2存储密码时,您的Django安装将使用更多迭代。

Argon2

Argon2具有三个可以自定义的属性:

  1. time_cost 控制哈希中的迭代次数。
  2. memory_cost 控制哈希计算期间必须使用的内存大小。
  3. parallelism 控制可以并行处理哈希计算的CPU数量。

这些属性的默认值可能适合您。如果确定密码哈希值太快或太慢,则可以按以下方式对其进行调整:

  1. 选择parallelism您可以节省计算哈希的线程数。
  2. 选择memory_cost作为您可以保留的内存KiB。
  3. 调整time_cost并测量散列密码所需的时间。选择一个time_cost需要您接受的时间。如果time_cost设置为1太慢,则降低memory_cost

memory_cost 解释

argon2命令行实用程序和其他一些库对memory_cost参数的解释与 Django使用的值不同。转换由给出。memory_cost == 2 ** memory_cost_commandline

密码升级

当用户登录时,如果他们的密码与首选算法不同,则Django会自动将其升级为首选算法。这意味着当用户登录时,旧版本的Django安装将自动变得更加安全,这也意味着您可以在发明新的(更好的)存储算法时切换到新的存储算法。

但是,Django只能升级使用中提到的算法的密码 PASSWORD_HASHERS,因此在升级到新系统时,应确保切勿从该列表中删除条目。如果这样做,使用未提及算法的用户将无法升级。当增加(或减少)PBKDF2迭代次数或bcrypt轮次时,哈希密码将被更新。

请注意,如果数据库中的所有密码均未使用默认的哈希算法进行编码,则由于用户登录请求的持续时间与密码中编码的用户的持续时间之间存在差异,因此您可能容易受到用户枚举定时攻击的攻击。非默认算法以及不存在的用户(运行默认哈希器)的登录请求的持续时间。您可以通过升级较旧的密码哈希来减轻这种情况。

无需登录即可升级密码

如果您现有的数据库具有较旧的弱哈希(例如MD5或SHA1),则可能需要自己升级这些哈希,而不是等待用户登录时进行升级(如果用户未登录则可能永远不会发生)返回您的网站)。在这种情况下,您可以使用“包装的”密码哈希器。

在此示例中,我们将迁移SHA1哈希的集合以使用PBKDF2(SHA1(password))并添加相应的密码哈希,以检查用户在登录时输入的密码是否正确。我们假设我们正在使用内置User模型,并且我们的项目有一个accounts应用程序。您可以修改模式以与任何算法或自定义用户模型一起使用。

首先,我们将添加自定义哈希器:

账户/ hashers.py

from django.contrib.auth.hashers import (
    PBKDF2PasswordHasher, SHA1PasswordHasher,
)


class PBKDF2WrappedSHA1PasswordHasher(PBKDF2PasswordHasher):
    algorithm = 'pbkdf2_wrapped_sha1'

    def encode_sha1_hash(self, sha1_hash, salt, iterations=None):
        return super().encode(sha1_hash, salt, iterations)

    def encode(self, password, salt, iterations=None):
        _, _, sha1_hash = SHA1PasswordHasher().encode(password, salt).split('$', 2)
        return self.encode_sha1_hash(sha1_hash, salt, iterations)

数据迁移可能类似于:

账户/迁移/ 0002_migrate_sha1_passwords.py

from django.db import migrations

from ..hashers import PBKDF2WrappedSHA1PasswordHasher


def forwards_func(apps, schema_editor):
    User = apps.get_model('auth', 'User')
    users = User.objects.filter(password__startswith='sha1$')
    hasher = PBKDF2WrappedSHA1PasswordHasher()
    for user in users:
        algorithm, salt, sha1_hash = user.password.split('$', 2)
        user.password = hasher.encode_sha1_hash(sha1_hash, salt)
        user.save(update_fields=['password'])


class Migration(migrations.Migration):

    dependencies = [
        ('accounts', '0001_initial'),
        # replace this with the latest migration in contrib.auth
        ('auth', '####_migration_name'),
    ]

    operations = [
        migrations.RunPython(forwards_func),
    ]

请注意,根据硬件的速度,数千名用户的迁移过程需要几分钟的时间。

最后,我们将添加一个PASSWORD_HASHERS设置:

mysite的/的settings.py

PASSWORD_HASHERS = [
    'django.contrib.auth.hashers.PBKDF2PasswordHasher',
    'accounts.hashers.PBKDF2WrappedSHA1PasswordHasher',
]

在此列表中包括您的站点使用的任何其他哈希。

包含的哈希器

Django中包含的哈希表的完整列表为:

[
    'django.contrib.auth.hashers.PBKDF2PasswordHasher',
    'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
    'django.contrib.auth.hashers.Argon2PasswordHasher',
    'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
    'django.contrib.auth.hashers.BCryptPasswordHasher',
    'django.contrib.auth.hashers.SHA1PasswordHasher',
    'django.contrib.auth.hashers.MD5PasswordHasher',
    'django.contrib.auth.hashers.UnsaltedSHA1PasswordHasher',
    'django.contrib.auth.hashers.UnsaltedMD5PasswordHasher',
    'django.contrib.auth.hashers.CryptPasswordHasher',
]

相应的算法名称为:

  • pbkdf2_sha256
  • pbkdf2_sha1
  • argon2
  • bcrypt_sha256
  • bcrypt
  • sha1
  • md5
  • unsalted_sha1
  • unsalted_md5
  • crypt

编写自己的哈希器

如果编写包含工作因子(例如多次迭代)的自己的密码哈希器,则应实现一种 方法,以弥合密码中提供的工作因子与哈希器的默认工作因子之间的运行时差距。这样可以防止由于使用较旧迭代次数编码密码的用户的登录请求与不存在的用户(运行默认哈希器的默认迭代次数)之间的登录请求不同而导致的用户枚举计时攻击。harden_runtime(self, password, encoded)``encoded

以PBKDF2为例,如果encoded包含20,000次迭代,并且哈希器的默认iterations值为30,000,则该方法应再运行password 10,000次PBKDF2迭代。

如果您的哈希器没有工作因素,则将该方法实现为无操作(pass)。

手动管理用户密码

django.contrib.auth.hashers模块提供了一组用于创建和验证哈希密码的功能。您可以独立于User模型使用它们。

  • check_password密码编码)[源代码]

    如果您想通过将纯文本密码与数据库中的哈希密码进行比较来手动验证用户身份,请使用便捷功能check_password()。它有两个参数:要检查的纯文本密码和要检查的用户password字段在数据库中的完整值,True如果不匹配False 则返回。

  • make_passwordpasswordsalt = Nonehasher ='default')[源代码]

    以此应用程序使用的格式创建哈希密码。它有一个强制性参数:纯文本密码。(可选)如果不想使用默认值(PASSWORD_HASHERS设置的第一项),则可以提供使用salt和哈希算法的方法。有关每个哈希器的算法名称,请参见 随附的哈希器。如果password参数为None,则返回一个不可用的密码(永远不会被接受check_password())。

  • is_password_usableencode_password)[源代码]

    返回False密码是否为的结果 User.set_unusable_password()。在Django 2.1中进行了更改:在较旧的版本中,False如果密码为 None或空字符串,或者密码使用的密码不在PASSWORD_HASHERS设置中,则会返回此值。该行为被认为是一个错误,因为它会阻止使用这种密码的用户请求重置密码。