I've migrated an old joomla installation over to django. The password hashes is an issue though. I had to modify the get_hexdigest in contrib.auth.models to have an extra if statement to reverse the way the hash is generated.
# Custom for Joomla
if algorithm == 'joomla':
return md5_constructor(raw_password + salt).hexdigest()
# Djangos original md5
if algorithm == 'md5':
return md5_constructor(salt + raw_password).hexdigest()
I also added the following to the User model to update the passwords after login if they have the old joomla style:
# Joomla Backwards compat
algo, salt, hsh = self.password.split('$')
if algo == 'joomla':
is_correct = (hsh == get_hexdigest(algo, salt, raw_password))
if is_correct:
# Convert the password to the new more secure format.
self.set_password(raw_password)
self.save()
return is_correct
Everything is working perfectly but I'd rather not edit this code directly in the django tree. Is there a cleaner way to do this in my own project?
Thanks
Your best bet would be to roll a custom auth backend and rewrite get_hexdigest in there. Never done it myself, but documentation on how to do so is available at http://docs.djangoproject.com/en/dev/topics/auth/#authentication-backends.
Thanks for the guidance. For anyone who needs to go the other way (DJango to Joomla) with DJ passwords, the DJ format is Sha1$salt$crypt.
Joomla standard auth plugin and joomla core JUserHelper do not implement the same SHA1 algorithum but it is fairly easy to patch into joomla.php in that plugin, where the plugin normally does an explode on ':'. Do a three-part explode with '$' and use salt = [1], compare that against $encrypted = sha1($salt.$plaintext), match that against the crypt [2].
Related
I'm currently trying to migrate my Flask Users over to a Django Backend.
However, when I'm using passlib to verify the hash, I can't figure out why it won't verify.
Our flask app settings
SECURITY_PASSWORD_HASH = "pbkdf2_sha512"
SECURITY_PASSWORD_SALT = "stackoverflow" # this is an example
An example of a hash I pulled from a database
flask_hash =
"$pbkdf2sha512$12000$ZQyhNEbIOSfk/J/T2vs/Bw$j.yxtixV.DqAcpsY9XTnJZZb3lCkR2fMWmV329Uc7Y/vz5Z0yMshEkYlUsE2Y9xm8TICwYkG55RgAplzZzLl7g"
So I created a custom pbkdf2_sha512 with the the rounds and salt
from passlib.hash import pbkdf2_sha512
rounds = 12000
salt = "stackoverflow".encode() # assume I swapped this out with the right salt
custom_pbkdf2 = pbkdf2_sha512.using(rounds=rounds, salt=salt)
verify_result = custom_pbkdf2.verify(hash=flask_hash, secret=password)
print (verify_result) # false
But if I create a new hash ... it does work
test_hash = custom_pbkdf2.hash('testing-if-this-works')
test_hash_confirm = custom_pbkdf2.verify('testing-if-this-works', hash=test_hash)
Is there something I'm missing? Thank you so much for any help here ... I know the password to this -- it's a dummy account I used for testing.
I was struck in exactly the same situation, luckily found this reddit thread, which had the explanation.
Basically, what you have to do verify the user is:
from flask_security.utils import verify_password
verify_password(<plain text password>, <password hash>)
More details here
I'm trying to do some authentication from my previous django web app in node. I got PBKDF2-sha256 working but I'm not able to get the BCryptSHA256PasswordHasher working in Node. I tried the following:
var Bcrypt = require('bcrypt');
var sha256 = require('sha256');
var pass = sha256("test password")
// from django ("bcrypt_sha256$$2b$12$mUg9hoKn0tt2/VwWaNb6Euie4.jtQjfU6.CY1pT0EH8GPORqAsh66")
var hash = "$2b$12$mUg9hoKn0tt2/VwWaNb6Euie4.jtQjfU6.CY1pT0EH8GPORqAsh66"
Bcrypt.compare(pass, hash, function (err, isMatch) {
if (err) {
return console.error(err);
}
console.log('do they match?', isMatch);
});
Is there something i'm missing with the above? I'm taking the sha256 of the password and testing with bcrypt. The corresponding code in Django is below:
def verify(self, password, encoded):
algorithm, data = encoded.split('$', 1)
assert algorithm == self.algorithm
bcrypt = self._load_library()
# Hash the password prior to using bcrypt to prevent password truncation
# See: https://code.djangoproject.com/ticket/20138
if self.digest is not None:
# We use binascii.hexlify here because Python3 decided that a hex encoded
# bytestring is somehow a unicode.
password = binascii.hexlify(self.digest(force_bytes(password)).digest())
else:
password = force_bytes(password)
# Ensure that our data is a bytestring
data = force_bytes(data)
# force_bytes() necessary for py-bcrypt compatibility
hashpw = force_bytes(bcrypt.hashpw(password, data))
return constant_time_compare(data, hashpw)
UPDATE
I have no idea why, but when I change the salt slightly to the following:
var hash = "$2a$12$mUg9hoKn0tt2/VwWaNb6Euie4.jtQjfU6.CY1pT0EH8GPORqAsh66"
everything works! I changed the 2b to 2a at the beginning. Why is this working and the other isn't? Is there something i'm missing?
From the excellent Passlib library:
ident (str) – Specifies which version of the BCrypt algorithm will be used when creating a new hash. Typically this option is not needed,
as the default ("2a") is usually the correct choice. If specified, it
must be one of the following:
"2" - the first revision of BCrypt, which suffers from a minor security flaw and is generally not used anymore. "2a" - some
implementations suffered from a very rare security flaw. current
default for compatibility purposes.
"2y" - format specific to the crypt_blowfish BCrypt implementation, identical to "2a" in all but name.
"2b" - latest revision of the official BCrypt algorithm (will be default in Passlib 1.7).
I need to rewrite url www.example.com/product/1 to www.example.com/en/product/1 when user chooses english language. (he will click on a select box that will toggle the language and set a session called 'language')
I cannot choose the django 1.4 which supports this feature. We are advised to stick with django 1.3.
Hence I tried a middleware, but as it turns out, the middleware runs for each request resulting in a endless loop.
class urlrewrite():
def process_request(self, request):
if 'i' in request.session:
if request.session.get('i','') != 0:
print "session"
request.session['i'] = request.session['i'] + 1
else:
request.session['i'] = 0
else:
request.session['i'] = 0
print "request.session['i']", request.session['i']
if request.session.get('i','') == SOME_CONSTANT and request.session.get('django_language','') == 'en':
del request.session['i']
return HttpResponseRedirect("en/"+request.META['PATH_INFO'])
Ofcourse, it doesnt work. This runs for every single request.
Kindly help me out.
Thank you
Don't write this yourself, use someone else's hard work.
Try django-cms's solution first.
==== EDIT ====
You do not need to used django-cms, just have it installed and use their Multilingual URL Middleware. This interfaces with django's regular internationalisation machinery.
http://django-cms.readthedocs.org/en/2.3.3/advanced/i18n.html
This problem can be solved by using a little trick in your urls.py file, as shown by this part of the docs: https://docs.djangoproject.com/en/1.4/ref/generic-views/#django-views-generic-simple-redirect-to.
You keep the same view, it will simple have a different URL. I think thats what you want. Make sure you choose the 1.3 version of the docs, I believe there has been some changes.
I create a user in my view.py using this simple code.
if not errors:
user = User.objects.create_user(username, email, password)
user.save()
Except for the validation, there is nothing that I do to the username and password values before creating the object.
But I find this in the User class in Django API. I don't know how to use the help text. If it is help text what does it print? How do I find the default values of algo, salt and hexdigest?
password = models.CharField(_('password'), max_length=128, help_text=_("Use '[algo]$[salt]$[hexdigest]' or use the change password form."))
"If it is help text what does it print?"
-> it prints exactly this: Use '[algo]$[salt]$[hexdigest]'
when you create a user, it will automatically call make_password(password[, salt, hashers])
which: Creates a hashed password in the format used by this application. It takes one mandatory argument: the password in plain-text. Optionally, you can provide a salt and a hashing algorithm to use, if you don't want to use the defaults (first entry of PASSWORD_HASHERS setting). Currently supported algorithms are: 'pbkdf2_sha256', 'pbkdf2_sha1', 'bcrypt' (see Using bcrypt with Django), 'sha1', 'md5', 'unsalted_md5'
are you facing any problems with this?
create_user will automatically generate password hash and it will create user in the database (thus you don't need that user.save())
See docs on creating users.
The help text is basicly just code for the message that shows up in the django admin, when editing a User object. It's meant to explain to someone looking at the edit form, why the password field has something like sha1$12345$1234567890abcdef1234567890abcdef12345678 instead of the password that was set for that user. The reason is, of course that the password is hashed for security, and that representation holds all the information required to verify a user-typed password later.
The admin user edit form has a special page for editing passwords. If you want to edit the users password in your code use the set_password method of the User object, the check_password method is for verifying a supplied password.
The documentation for make_password has more information about the algorithms Django uses and can use. The default for Django <1.3 was sha1, Django 1.4 changed the default to PBKDF2. The default value for salt is a random string (it's there so that two identical passwords don't look the same in the database). Hexdigest is the value of the password string and the salt string hashed with the hashing algorithm. You can read the details in the code on github.
Before somebody marks this question as a duplicate of this question Can django's auth_user.username be varchar(75)? How could that be done? or other such questions on SO, please read this question. The question I linked to asks this question precisely but unfortunately the answers don't address the question that was asked.
Can I change the auth_user.username field to be 100 characters long by doing the following:
Run ALTER table in DB for the username field
Change the max_length here: username = models.CharField(_('username'), max_length=30, unique=True, help_text=_("Required. 30 characters or fewer. Letters, numbers and #/./+/-/_ characters"))
Would it break anything in Django if I were to do this?
That this will break when I update Django to a higher version is not a problem. I'm also not looking at writing other authentication methods.I just want to know if I would break anything if I were to do this.
You need to monkey-patch max-length in several places: model-field's description, form-field's description and max-length validators. Max-length validators are attached to form-fields as well as model-fields.
Here is a code snippet, which will patch everything:
https://gist.github.com/1143957 (tested with django 1.2 and 1.3)
Update:
Good news! Since django 1.5 you can override user model: Customizing the User model
There is no harm in doing that.
Just change the length in the model and in the database :)
Create a user profile model, add your very-long-username field there, and use it. of course this renders the genuine username field useless, but it is much better than hacking Django code, which will get you into trouble when you need to upgrade it.
For me, the code below works and is simple well.
from django.contrib.auth.models import AbstractUser
class MyUser(AbstractUser):
....
# your custom fields
MyUser._meta.get_field('username').max_length = 255 # or 100
after you can run your migration
If you change the database manually as well as the model accordingly then there should be no problem.
You can also change back otherwise, and I would say make a backup just in case but I'm not sure its even necessary
for future needs this is the best and easiest way i found out:
https://github.com/GoodCloud/django-longer-username
Another solution is to change your authentication code. When a new user signs up, they enter an email, you save this in the email field so you have it, and if their email is >30 characters, you shorten it before putting it into the user field. Then, change your login authentication for new logins.