Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use sha256 hashes instead of plain text passwords

Tags:

django

My country's government has restricted HTTPS speed to block access to secure web services out side Iran. Now my clients are in pain for logging in to their accounts. I know that current account passwords are encrypted and salted using pbkdf2_sha256 algorithm and there are some javascript/jQuery libs to digest sha256 hashes.

My question: Is there any painless way (which does not require rewriting/changing the original django.contrib.auth) to use sha256 hashes sent by AJAX requests as login password?

Update: I'm planning to host my sites inside Iran (which is 5 times more expensive and of course controlled by government) but at least HTTPS protocol is not restricted. Sending passwords either in plain text or digested way via HTTP channel is insecure anyway. While as a web developer (and not a network expert) I think they can collect/sniff the hashes/session ids/cookies anytime they want, solving the problem needs sophisticated knowledge and efforts and after all my site does not require that level of security. We live in different planets.

like image 861
GhaghaSibil Avatar asked May 02 '13 13:05

GhaghaSibil


People also ask

Can I use SHA-256 for passwords?

TL;DR; SHA1, SHA256, and SHA512 are all fast hashes and are bad for passwords. SCRYPT and BCRYPT are both a slow hash and are good for passwords. Always use slow hashes, never fast hashes.

Why is it better to store passwords as hashes rather than in plaintext?

Hashing allows passwords to be stored in a format that can't be reversed at any reasonable amount of time or cost for a hacker. Hashing algorithms turn the plaintext password into an output of characters of a fixed length.

Is hashing better than encryption for passwords?

Hashing and encryption both provide ways to keep sensitive data safe. However, in almost all circumstances, passwords should be hashed, NOT encrypted. Hashing is a one-way function (i.e., it is impossible to "decrypt" a hash and obtain the original plaintext value). Hashing is appropriate for password validation.

Can you use a hash as a password?

You cannot directly turn a hashed value into the password, but you can work out what the password is if you continually generate hashes from passwords until you find one that matches, a so-called brute-force attack, or similar methods.


1 Answers

You can write your own authentication backend to use raw passwords:

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

In settings file:

AUTHENTICATION_BACKENDS = (
    # ModelBackend from project_root/auth/backends.py
    'auth.backends.ModelBackend',
)

Now when you authenticate users in your views, you will get RawPasswordUser instances. The same applies to login_required decorator, request.user will point to the proxy class.

See documentation for details.

For Django 1.5+ there is also an option to replace default User model with a custom one, but to keep existing users you will have to migrate them somehow, see this question.


Actually you won't be able to keep user passwords unchanged.

By default Django stores passwords in the following format:

algorithm$iterations$salt$hash

Which means:

  • You can't just regenerate passwords from hashes of originals, since you don't have the originals.

  • You also won't be able to generate the same hash on client-side, without knowing the salt. You could pass it to client-side, but salt is supposed to be a secret, so it's unwise to do it via unencrypted channel.

The easiest solution that I see is to keep current Django behaviour, as was suggested by Tadeck in the comments, add hashing to client-side and force users to change their passwords.

Well, it's not really a solution, because an attacker can intercept digested passwords and use them directly, but you mentioned it your question update. Since you don't care about security that much, you could also checkout public key encryption in JavaScript.


Another solution proposed by Tadeck is to use OAuth-like service, which might look somewhat like this:

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})

An attacked can still intercept an access token, but it's a bit better than intercepting password hash.

like image 104
gatto Avatar answered Nov 15 '22 03:11

gatto