Use sha256 hashes instead of text passwords

My government has limited the speed of HTTPS to block access to secure web services from Iran. Now my clients are in pain to log into their accounts. I know that the passwords of the current account are encrypted and salted using the pbkdf2_sha256 algorithm, and there are some javascript / jQuery libs for digesting sha256 hashes.

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

Update: I plan to host my sites in Iran (which is 5 times more expensive and, of course, controlled by the government), but at least the HTTPS protocol is not limited. In any case, sending passwords in the form of plain text or a digested path through an HTTP channel is unsafe. Although I, as a web developer (and not a network expert), I think that they can collect / sniff hashes / session IDs / cookies anytime they want, solving the problem requires complex knowledge and efforts, and after my site does not require this level of security, We live on different planets.

+6
source share
1 answer

You can write your own authentication server 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 the settings file:

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

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

See the documentation for more details.

For Django 1.5+, there is also an option to replace the default user model with a user model, but to save existing users you will have to transfer them somehow, see this question .


In fact, you cannot save user passwords unchanged.

By default, Django stores passwords in the following format:

$ iteration algorithm $ salt $ hash

It means:

  • You cannot simply recover passwords from the hashes of the originals, since you do not have the originals.

  • You will also not be able to generate the same hash on the client side without knowing the salt. You can transfer it to the client side, but salt should be a secret, so it is unreasonable to do this through an unencrypted channel.

The simplest solution that I see is to keep Django’s current behavior, as suggested by Tadeck in the comments, add hashing to the client side and force users to change their passwords.

Well, this is not quite a solution, because an attacker can intercept digested passwords and use them directly, but you mentioned this in your question update. Since you don't really care about security, you can also check public key encryption in JavaScript.


Another solution Tadeck offers is to use an OAuth-like service, which might look something 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 attacker can still intercept an access token, but it is slightly better than intercepting a password hash.

+3
source

Source: https://habr.com/ru/post/944119/


All Articles