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.
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.
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With