Django模型中的密码字段

Posted

技术标签:

【中文标题】Django模型中的密码字段【英文标题】:Password field in Django model 【发布时间】:2011-04-12 12:30:33 【问题描述】:

我正在尝试创建一个模型,我可以在其中存储其他应用程序的用户名和密码。如何在 Django 中设置密码字段,使其在管理员中不是纯文本?提前致谢。

【问题讨论】:

感谢大家的回答。我会接受 mlissner 和 Manoj 的建议,尽管要特别注意 rebus,因为他的方法适用于您只需要简单功能而没有太多安全性的简单应用程序。 这可能会做你想要的。 djangosnippets.org/snippets/1330 Ping - 你实现了吗?我正在考虑实施类似的事情,但我不确定从哪里开始你能指出正确的方向吗? 杰克 - 我有,但我让它有点不同。我使用像 Manoj 在他的回答中所说的 set_password ,从那里我可以检查用户输入的密码以通过拆分 algo/salt/hash 字符串来检查用户输入的密码,只留下哈希来比较它,或者使用静态盐和完全比较它们。 【参考方案1】:

如果您需要一个可逆的密码字段,您可以使用如下内容:

from django.db import models
from django.core.exceptions import ValidationError
from django.conf import settings

from os import urandom
from base64 import b64encode, b64decode
from Crypto.Cipher import ARC4
from django import forms



PREFIX = u'\u2620'


class EncryptedCharField(models.CharField): 
    __metaclass__ = models.SubfieldBase

    SALT_SIZE = 8

    def __init__(self, *args, **kwargs):
        self.widget = forms.TextInput       
        super(EncryptedCharField, self).__init__(*args, **kwargs)

    def get_internal_type(self):
        return 'TextField'    

    def to_python(self, value):
        if not value:
            return None
        if isinstance(value, basestring):
            if value.startswith(PREFIX):
                return self.decrypt(value)
            else:
                return value
        else:
            raise ValidationError(u'Failed to encrypt %s.' % value)

    def get_db_prep_value(self, value, connection, prepared=False):
        return self.encrypt(value)        

    def value_to_string(self, instance):
        encriptado = getattr(instance, self.name)
        return self.decrypt(encriptado) if encriptado else None

    @staticmethod
    def encrypt(plaintext):
        plaintext = unicode(plaintext)
        salt = urandom(EncryptedCharField.SALT_SIZE)
        arc4 = ARC4.new(salt + settings.SECRET_KEY)
        plaintext = u"%3d%s%s" % (len(plaintext), plaintext, b64encode(urandom(256-len(plaintext))))
        return PREFIX + u"%s$%s" % (b64encode(salt), b64encode(arc4.encrypt(plaintext.encode('utf-8-sig'))))

    @staticmethod
    def decrypt(ciphertext):
        salt, ciphertext = map(b64decode, ciphertext[1:].split('$'))
        arc4 = ARC4.new(salt + settings.SECRET_KEY)
        plaintext = arc4.decrypt(ciphertext).decode('utf-8-sig')
        return plaintext[3:3+int(plaintext[:3].strip())]

加密部分基于https://djangosnippets.org/snippets/1330/上的sn-p,我只是把它变成了一个字段模型,添加了utf-8支持并添加了前缀作为Django's silly use of to_python()的解决方法

【讨论】:

要使用上述内容,您可能需要pip install pycrypto 而不是pip install Crypto。此外,现在 pypi 上有一个包含许多字段的包,名为 django-encrypted-fields(或对于 python 3,django-encrypted-fields-python3)。 哈希的全部意义在于不可逆转!任何人都应该无法访问原始密码。 plaintextoffenders.com/faq/devs 是的,确实如此,如果您可以使用哈希解决问题,那么您绝对应该使用它。但是,有时,您可能需要检索密码:例如,假设您的程序需要登录到第 3 方服务器,而访问该第 3 方服务器的密码在您的数据库中。【参考方案2】:

很遗憾,这个问题没有一个简单的答案,因为它取决于您尝试对其进行身份验证的应用程序,还取决于您希望密码字段的安全程度。

如果您的 Django 应用程序将使用该密码对另一个需要发送明文密码的应用程序进行身份验证,那么您的选择是:

在您的 Django 模型中以纯文本形式存储密码(您的问题暗示您不想这样做) 从用户那里获取主密码,然后才能为其他应用程序解锁存储的密码 对模型中的密码进行模糊处理,以便任何拥有原始数据存储权限的人都可以访问它,但对于普通的普通查看者来说并不明显

如果您使用 Django 的内置用户模型,您可以使用 Django 用户密码作为主密码。这意味着您需要将该主密码保存在内存中,这可能会使您的某些操作变得困难,例如重新启动服务器或运行负载平衡的冗余服务器。

存储密码的替代方法

幸运的是,许多现代应用程序使用基于密钥而不是基于密码的访问令牌系统以另一种方式支持这一点。引导用户完成在两个应用程序之间建立链接的过程,并且在后台,应用程序生成密钥以永久地或具有定义的到期日期来相互验证。

例如,Facebook 支持此模型,并且他们拥有大量关于其工作原理的文档:

Facebook Developers: Access Tokens and Types

一旦您成功地使用 [OAuth 2.0](http://tools.ietf.org/html/draft-ietf-oauth-v2-12) 与 Facebook 链接,您可能会发现添加其他链接更容易使用相同协议的应用程序。

【讨论】:

【参考方案3】:

我认为您永远无法对以类似于普通 django 用户密码的方式存储的加密密码进行去哈希处理。部分安全性在于它们是不可去散列的。

【讨论】:

【参考方案4】:

作为@mlissner suggested auth.User 模型是一个不错的选择。如果您检查source code,您会看到password 字段是CharField

password = models.CharField(_('password'), max_length=128, help_text=_("Use 
'[algo]$[salt]$[hexdigest]' or use the <a href=\"password/\">change password form</a>."))

User 模型也有一个set_password 方法。

def set_password(self, raw_password):
    import random
    algo = 'sha1'
    salt = get_hexdigest(algo, str(random.random()), str(random.random()))[:5]
    hsh = get_hexdigest(algo, salt, raw_password)
    self.password = '%s$%s$%s' % (algo, salt, hsh)

您可以从这个方法中获得一些关于创建密码和保存密码的线索。

【讨论】:

那么,如果我要实现一个类似的方法,当我需要使用它来通过 Facebook 之类的 API 进行身份验证时,我将如何对密码进行去哈希处理? 正如 Marc 在下面回答的那样,你不能。但是您可以使用相同的算法和盐对用户提供的密码进行哈希处理,并检查该值是否等于您存储在数据库中的值。 您好,源代码链接导致404,请您提供更正的链接 自发布此答案以来,Django 已经更改了 lots——以允许覆盖和更改 User 模型——但要点仍然正确。新AbstractBaseUser 的密码设置和检查的繁重部分位于django.contrib.auth.hashers。同样,虽然这个答案的散文通常是正确的,但代码已经过时了。答案可以通过一些仔细的重写来完成。【参考方案5】:

您最好的选择(我知道)是深入研究 django 代码中的代码,看看它是如何完成的。我记得,它们生成了一个加盐的散列,因此纯文本值永远不会存储在任何地方,而是散列和盐存储。

如果您进入 django 安装程序,并四处寻找诸如 hash 和 salt 之类的词,您应该很快就会找到它。很抱歉这个含糊的答案,但也许它会让你走上正确的道路。

【讨论】:

以上是关于Django模型中的密码字段的主要内容,如果未能解决你的问题,请参考以下文章

从自定义 Django 用户模型中删除密码

在 GET 中隐藏密码字段,但在 Django REST Framework 中隐藏密码字段,其中序列化程序中的 depth=1

将不在模型中的字段添加到 Django REST 框架中的序列化程序

django 中的 ModelForms 是不是仅用于发布?

使用注册表单添加到自定义用户字段(django)

在 django 2.2.5 中使用用户名和密码登录