I'm using WTForms-Alchemy to define forms from model objects. I defined a field as a password thus:
password = db.Column(PasswordType(schemes=['pbkdf2_sha512']), nullable=True)
I persist the form to PostgreSQL and I always end up with the wrong hash in the database. Interestingly, this method worked flawlessly on a previous project that used MySQL.
I've now decided to encrypt my passwords by hand by calling pbkdf2_sha512.encrypt and pbkdf2_sha512.verify manually and the hashes are stored correctly.
Am I missing a configuration parameter? Could this be a bug?
I'm not entirely sure what the issue here is, but I wanted to mention that using pbkdf2 is not recommended now-a-days -- if you're storing user password hashes you should preferably be storing passwords using bcrypt. bcrypt is a cpu hard hashing algorithm, which makes it much harder to brute force for potential attackers.
Related
I am making a Django project that will be hosted locally in different environments.
I want users to be able to login by just entering a six-digit PIN on a touch screen or keyboard instead of having to type out a lengthy username/password.
I need to store a PIN for users in the DB. I want the PIN to be hashed or encrypted in some way so that it is not visible in the database. The PIN (and therefore its hash) must be unique but it also must be converted to the same value each time. For instance, every time 123456 is entered it needs to be converted to "jhs8d67RandomString34kds" so that no two users can save the same PIN as the DB column will be unique.
I need to know how to change a user-entered integer and hash it to save in the database.
Then I need to know how to compare it when a user enters the PIN.
I really need some examples on how to implement this and not a lesson in telling me why this is "insecure" or won't work.
Any ideas would be greatly appreciated.
Hashing something doesn't make it secure
All hash function have clashes, the only difference is the probability
Integers have hash function implemented, just use that
Note that for security reasons hashing for strings in randomized in each python process. so those hashes cannot be used for persistent data
You can use module-hashlib:
import hashlib
pincode = "123456"
hashlib.md5(pincode).hexdigest()
'e10adc3949ba59abbe56e057f20f883e'
And the to compare you can do the same:
if hashlib.md5(pincode).hexdigest() == 'e10adc3949ba59abbe56e057f20f883e':
you code here
...
Or use hashlib.pbkdf2_hmac with salt:
hashlib.pbkdf2_hmac(hash_name, password, salt, iterations, dklen=None)
import hashlib
dk = hashlib.pbkdf2_hmac('sha256', b'password', b'salt', 100000)
dk.hex()
'0394a2ede332c9a13eb82e9b24631604c31df978b4e2f0fbd2c549944f9d79a5'
I have an old database where user passwords were hashed with md5 without salt. Now I am converting the project into django and need to update passwords without asking users to log in.
I wrote this hasher:
from django.contrib.auth.hashers import PBKDF2PasswordHasher
class PBKDF2WrappedMD5PasswordHasher(PBKDF2PasswordHasher):
algorithm = 'pbkdf2_wrapped_md5'
def encode_md5_hash(self, md5_hash, salt):
return super().encode(md5_hash, salt)
and converting password like:
for data in old_user_data:
hasher = PBKDF2WrappedMD5PasswordHasher()
random_salt = get_random_string(length=8)
# data['password'] is e.g. '972131D979FF69F96DDFCC7AE3769B31'
user.password = hasher.encode_md5_hash(data['password'], random_salt)
but I can't login with my test-user.
any ideas? :/
I'm afraid you cannot do what you want with this. Hashing is strictly one-way, so there is no way to convert from one hash to another. You WILL have to update these passwords to the new hash one-by-one as users log in.
A decent strategy for implementing this change is:
Mark all of your existing hashes as md5. You can just use some kind of boolean flag/column, but there is an accepted standard for this: https://passlib.readthedocs.io/en/stable/modular_crypt_format.html
When the user logs in, authenticate them by first checking which type of hash they have, and then calculating that hash. If they are still md5, calculate the md5 to log them in; if they are now using pbkdf2, calculate that hash instead.
After authenticating the password, if they are still flagged as md5, calculate the new format hash and replace it - making sure to now flag this as pbkdf2.
IMPORTANT: You will want to test this thoroughly before you release it to the wild. If you make a mistake, you might destroy the credentials of any user logging in. I would recommend temporarily retaining a copy of the old md5 hashes until you confirm production is stable, but make absolutely certain you destroy this copy completely. Your users passwords are not safe as long as the md5 hashes exist whatsoever.
I am using Django 1.97. The encrypted passwords are significantly different (in terms of the format).
Some passwords are of format $$$:
pbkdf2_sha256$24000$61Rm3LxOPsCA$5kV2bzD32bpXoF6OO5YuyOlr5UHKUPlpNKwcNVn4Bt0=
While others are of format :
!9rPYViI1oqrSMfkDCZSDeJxme4juD2niKcyvKdpB
Passwords are set either using User.objects.create_user() or user.set_password(). Is this difference an expected one ?
You'll be fine. You just have some blank passwords in your database.
Going back as far as V0.95, django used the $ separators for delimiting algorithm/salt/hash. These days, django pulls out the algorithm first by looking at what is in front of the first $ and then passes the whole lot to the hasher to decode. This allows for a wider set of formats, including the one for PBKDF2 which adds an extra iterations parameter in this list (as per your first example).
However, it also recognises that some users may not be allowed to login and/or have no password. This is encoded using the second format you've seen. As you can see here:
If password is None then a concatenation of UNUSABLE_PASSWORD_PREFIX and a random string will be returned which disallows logins.
You can also see that the random string is exactly 40 characters long - just like your second example.
In short, then, this is all as expected.
There is no significant difference between User.objects.create_user() and user.set_password() since first uses second.
Basically, passwords are in string with format <algorithm>$<iterations>$<salt>$<hash> according to docs. The differences might come from PASSWORD_HASHERS settings variable. May be one password was created with one hasher and other password with another. But if you'll keep those hashers in variable mentioned above all should be fine, users will able to change it etc. You can read about it in little notice after bcrypt section.
Also docs for django.contrib.auth package might be helpful too. Link.
UPDATE:
If you find documentation of an old django versions (1.3 for example), you will see that
Previous Django versions, such as 0.90, used simple MD5 hashes without password salts. For backwards compatibility, those are still supported; they'll be converted automatically to the new style the first time check_password() works correctly for a given user.
So I think that the answer might be somewhere here. But it really depends on how legacy your project is, so you can decide if it's normal or what. Anyway you can issue check_password() to be sure. Or you can just email your user with "change password please" notification. There are many factors involved really.
I've got a passwords on a datastore that were hashed using the method SecureSocialPasswordHasher.passwordHash from the package securesocial.utils.SecureSocialPasswordHasher of SecureSocial, and I have to validate them through Python.
Therefore, the use of SecureSocial (or the whole Play Framework) is out of the question. The question is: What does it use for hashing when calling that method? From the documentation it seems it is Bcrypt, but it wasn't clear enough for me to be sure.
---------EDIT---------
I've been told on SecureSocial forums that indeed it uses Bcrypt with work factor 10 default. However it doens't reflect what I see on the datastore.
There are 2 columns there, one for salt, and another one fro the hashed password. Neither of them have the Bcrypt header (such as $2a$10$). Also, the salt size is only 11 characters long, and the hashed password is only 22 characters long (and no signs of having the salt inside the string).
Found out the default for hashing passwords on SecureSocial is indeed Bcrypt.
The default implementation for it's hash method is:
def hash(plainPassword: String): PasswordInfo = {
PasswordInfo(id, BCrypt.hashpw(plainPassword, BCrypt.gensalt(logRounds)))
}
This applies to the latest version of SecureSocial.
On my specific problem, the main issue was that I was not communicated that the code I was dealing with was using an older version of SecureSocial, and that the has method was overriden.
So an emergency project was dumped on me to merge a MySQL user database into an existing Django user database.
I've figured just about everything out except how to handle the passwords as they use different hashes. I don't know Python, the Django backend, or very much about hashing techniques.
I do have a way to verify users with their emails, I just need a way to take the passwords they give me and save them into the database in a Django-acceptable way. It will be have to be done in Perl since that's the only language I know on the server.
I found this page talking about how Django handles passwords, but I sadly don't understand most of what they're saying. Also, I don't know if it's any help, but the admin area of the Django site gives the "hint" of
"Use '[algo]$[salt]$[hexdigest]'" for the password.
That doesn't mean much to me either, but maybe it does to one of you?
There are basically two ways to handle this: convert existing passwords to a format acceptable by Django, or write your own Django password hasher.
For the first way, as you found, the password field consists of three parts, each separated by a $. (Django 1.6 passwords may have 4 parts, but let's ignore that extra part for now, since Django 1.6 also supports the more traditional 3-part passwords.) The parts are
algorithm, which describes the password hashing algorithm; it will look like md5, pbkdf2, etc.
salt, the salt for the hash algorithm
hexdigest, the hashed password
So, assuming your passwords are already salted and hashed, your script needs to take the hashed/salted passwords in your existing database, separate the salt from the hash, then store them into the database with the appropriate algorithm string prefixed. There should be Perl modules for doing password hashing using various algorithms. Django's recommended algorithm is PBKDF2. bcrypt is also good. Any hash algorithm is fine, though, as far as Django is concerned, as long as it has a built-in hasher for that algorithm (Django has hashers for the most common hashing algorithms).
If your existing passwords are not salted and hashed…well, now would be a good time to do that. ;-)
The alternative way is to just copy the passwords over to the new database as-is, and write your own password hasher to handle them in your Django app. Of course, that would require writing some Python code.