使Django后端接受前端的SHA256加密密码而非明文密码

7
我的国家政府限制了HTTPS速度,以阻止访问伊朗以外的安全网络服务。现在我的客户为登录到他们的帐户而烦恼。我知道当前帐户密码使用pbkdf2_sha256算法进行加密和加盐,并且有一些javascript / jQuery库用于摘要sha256哈希。

我的问题是:是否有无痛的方法(不需要重新编写/更改原始的django.contrib.auth),可以使用通过AJAX请求发送的sha256哈希作为登录密码?

更新:我计划将我的网站托管在伊朗境内(这比境外贵5倍,当然由政府控制),但至少HTTPS协议没有受到限制。通过HTTP通道以明文或摘要方式发送密码都是不安全的。虽然作为一个Web开发人员(而不是网络专家),我认为他们随时都可以收集/嗅探哈希/会话ID / cookie,但解决问题需要复杂的知识和努力,毕竟我的网站不需要那种级别的安全性。我们生活在不同的星球上。

1个回答

3

您可以编写自己的身份验证后端以使用原始密码:

from django.contrib.auth import backends
from django.contrib.auth.models import User

class RawPasswordUser(User):
    class Meta:
        proxy = True

    def set_password(self, raw_password):
        # default implementation made a hash from raw_password,
        # we don't want this
        self.password = raw_password

    def check_password(self, raw_password):
        # same here, don't make hash out of raw_password
        return self.password == raw_password

class ModelBackend(backends.ModelBackend):
    def authenticate(self, username=None, password=None):
        try:
            user = RawPasswordUser.objects.get(username=username)
            if user.check_password(password):
                return user
        except RawPasswordUser.DoesNotExist:
            return None

    def get_user(self, user_id):
        try:
            return RawPasswordUser.objects.get(pk=user_id)
        except RawPasswordUser.DoesNotExist:
            return None

在设置文件中:
AUTHENTICATION_BACKENDS = (
    # ModelBackend from project_root/auth/backends.py
    'auth.backends.ModelBackend',
)

现在,当您在视图中对用户进行身份验证时,您将获得RawPasswordUser实例。对于login_required装饰器也是如此,request.user将指向代理类。
有关详细信息,请参见文档
对于Django 1.5+,还有一个选项可以使用自定义模型替换默认的User模型,但要保留现有用户,您需要以某种方式对其进行迁移,请参见此问题这里也有相应的说明
实际上,您无法保持用户密码不变。默认情况下,Django以以下格式存储密码: 算法$迭代次数$盐$哈希值。这意味着: 您不能仅从原始哈希重新生成密码,因为您没有原始密码。您也无法在客户端生成相同的哈希值,而不知道盐。您可以将其传递到客户端,但是盐应该是一个秘密,所以通过未加密的通道进行传递是不明智的。我看到的最简单的解决方案是保持当前的Django行为,如评论中Tadeck所建议的,添加客户端哈希并强制用户更改密码。好吧,这不是真正的解决方案,因为攻击者可以拦截摘要密码并直接使用它们,但是您在问题更新中提到了它。由于您并不太关心安全性,因此您还可以在JavaScript中查看公钥加密

Tadeck提出的另一个解决方案是使用类似OAuth的服务,可能看起来像这样:

def index(request):
    access_token = request.REQUEST.get('token', None)
    if not access_token:
        return redirect('login')

    # Custom authentication backend that accepts a token
    # and searches for a user with that token in database.
    user = authenticate(access_token)
    if not user:
        return redirect('login')

    return render(...)

def auth(request):
    ''' This ajax-view has to be encrypted with SSL.'''
    # Normal Django authentication.
    user = authenticate(request.POST['username'], request.POST['password'])

    # Authentication failed
    if user is None:
        return json.dumps({'error': '...'})

    # generate, save and return token in json response
    token = UserToken(user=user, value=generate_token())
    # token.expires_at = datetime.now() + timedelta(days=1)
    token.save()

    return json.dumps({'token': token.value})

攻击者仍然可以截取访问令牌,但相对于截取密码哈希来说要好一些。


我会把except放在user = User.objects.get(...)的下面。 - Oscar Mederos
需要更改当前后端记录吗?我的意思是,当使用这个新后端时,当前用户是否应更改他们的密码?我能写一些脚本来转换它们吗? - GhaghaSibil
@OscarMederos:是的,但这是原始Django代码中的内容,所以我没有更改它。 - gatto
1
这是解决类似问题的不好方式:你在没有必要的情况下降低了安全性(据我所知,OP的政府并不要求服务器端漏洞;这样做很愚蠢)。OP可能需要的实际上是双重哈希。后端可以保持不变,但前端(在注册和身份验证以及密码重置期间)将一致地对密码进行哈希处理(确保相同的密码会产生相同的输出,最好加入一些盐),然后将此哈希视为密码。但是这仍然不是理想的。 - Tadeck
3
更好的方法是使用非常轻量级的页面进行身份验证,通过OAuth类似的服务实现:OP的SSL站点作为OAuth提供者,而非SSL站点则作为OAuth消费者,这样可以确保过程足够安全,同时允许通过不安全但受到政府监控的通道快速访问。此外,您可以定期重定向到SSL版本以检查身份验证的有效性(如果您有后续使其无效的手段)。这可以给您的消费者额外的安全感,确保他们的密码是安全的,同时不影响用户体验。 - Tadeck
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接