7

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.

Top-Master
  • 7,611
  • 5
  • 39
  • 71
GhaghaSibil
  • 71
  • 1
  • 5

1 Answers1

3

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.

Community
  • 1
  • 1
gatto
  • 2,947
  • 1
  • 21
  • 24
  • I would place the `except` right below `user = User.objects.get(...)` – Oscar Mederos May 02 '13 at 15:28
  • Is it required to change the current backend records? I mean when using this new backend then current users should change their passwords? Can I write some script to convert them? – GhaghaSibil May 02 '13 at 15:41
  • Passwords will remain a hashes, you your client-side sends the same hashes, then you don't need to convert anything. If hashes are different, you can convert in `python manage.py shell`, import `django.contrib.auth.models.User` and modify as you wish. – gatto May 02 '13 at 15:47
  • @OscarMederos: yeah, but it was like this in original Django code, so I didn't change it. – gatto May 02 '13 at 15:55
  • 1
    That is a bad way to solve similar problems: you are decreasing security without necessity to do that (as far as I understand, OP's government does not require server-side vulnerabilities; it would be silly). What OP may need is actually **double** hashing. The backend could stay the same, but the frontend (during both registration and authentication; plus password reset) would hash the password consistently (it is important to ensure the same password will give the same output, preferably with some salting) and then this hash would be treated as password. However this is still not ideal. – Tadeck May 03 '13 at 00:21
  • 3
    The better way would be to use very light version of the page solely to authenticate, with use of OAuth-like services: OP's SSL site would serve as OAuth provider, while non-SSL site would serve as OAuth consumer, making the process secure enough, but allowing fast access through insecure, monitored-by-government channel. In addition, you could periodically redirect to SSL version in order to check authentication validity (if you have means to invalidate it later). That could give your consumers additional sense of security, making sure that their passwords are secure, while UX doesn't suffer. – Tadeck May 03 '13 at 00:26
  • @Tadeck, as long as client-side doesn't send passwords in plaintext (even with disabled js), how is it decreasing security? But I guess double hashing wouldn't hurt either, unless OP really wants to keep existing password hashes as they are now or slightly modified to match the client-side. As for OAuth idea, sounds great. – gatto May 03 '13 at 07:55
  • @Tadeck, Beside of that I'm not familiar with it, while it's a good way of increasing security, it's complicated somehow and requiring me to spend lot of time to learn it. Who know maybe they can restrict it too. Thank you. – GhaghaSibil May 03 '13 at 08:47