Django Password Generator - django

I have imported a heap of users and their data to a django project. I need to assign a password to each. Is the such a snippet out there for password generation that will cope with the Django hash and salt?

You can also use the built in function make_random_password
for user in new_users:
password = User.objects.make_random_password()
user.set_password(password)
user.save(update_fields=['password'])
# email/print password

Also you can use from django.utils.crypto import get_random_string out of auth module, it accepts keyword arguments length and allowed_chars as well.

If you need only a Django`s solutions, then try next:
For generate a normal password try use BaseUserManager.
In [341]: from django.contrib.auth.base_user import BaseUserManager
# simple password, it length is 10, and it contains ascii letters and digits
In [344]: BaseUserManager().make_random_password()
Out[344]: 'aYMX5Wk7Cu'
In [345]: BaseUserManager().make_random_password()
Out[345]: 'rM7759hw96'
In [346]: BaseUserManager().make_random_password()
Out[346]: 'EkbZxEXyAm'
# passed length of a password
In [347]: BaseUserManager().make_random_password(45)
Out[347]: 'dtM9vhSBL9WSFeEdPqj8jVPMH9ytsjPXrkaHUNUQu4zVH'
In [348]: BaseUserManager().make_random_password(45)
Out[348]: 'jypVaXuw9Uw8mD4CXtEhtj2E4DVYx23YTMwy8jGTKsreR'
# passed length of a password and symbols for choice
In [349]: BaseUserManager().make_random_password(45, 'abcdf')
Out[349]: 'daacbfabfccfdbdddbbcddcfcfbfcdabbaccbfcadbccd'
In [351]: import string
# password contains only digits
In [352]: BaseUserManager().make_random_password(50, string.digits)
Out[352]: '00526693878168774026398080457185060971935025500935'
# password contains only ascii symbols in lowercase
In [353]: BaseUserManager().make_random_password(50, string.ascii_lowercase)
Out[353]: 'nvftisuezofnashdhlalfmscnmqtvigwjpfwsyycsefekytmar'
# password contains only ascii symbols in uppercase
In [354]: BaseUserManager().make_random_password(50, string.ascii_uppercase)
Out[354]: 'APKSUHHHTAAJCFEUONIXWWAKJGXIBHTQDZBTSYFTPDFOSRYEQR'
If you need strong and power password, then try built-in "hashers" in the Django
In [355]: from django.contrib.auth.hashers import make_password
In [357]: make_password('')
Out[357]: 'pbkdf2_sha256$30000$JuKXdW3shCjL$PsPJX7Zale5JUBkWpIJI/+QlsuVWhz9Q+GQWVtTpQ/Y='
In [358]: make_password('text')
Out[358]: 'pbkdf2_sha256$30000$lSv8kQ39BHE7$KQC5hRhuphYBXmBrXZBJGC+nxygfNWTDf8zQf/NNgY8='
In [360]: make_password('text', salt=['simething'])
Out[360]: "pbkdf2_sha256$30000$['simething']$D+1vJQx9W2/c9sIz/J+7iEz4d4KFPg/R+0S87n/RKR4="
In [361]: make_password('text', salt=['something'])
Out[361]: "pbkdf2_sha256$30000$['something']$NIcmOkEyg6mnH5Ljt+KvI2LVgZWg6sXS6Rh865rbhSc="
Notes:
Used Django 1.10 and Python 3.4

Just use the API - django.contrib.auth.models.User has a .set_password() method. Here's an example (that I haven't tested, but you should get the idea):
from random import choice
from string import digits, letters
from django.contrib.auth.models import User
def _pw(length=6):
s = ''
for i in range(length):
s += random.choice(digits + letters)
return s
for user in User.objects.all(): # or .filter(...)
user.set_password(_pw())
user.save()

import random
import string
user.set_password(''.join([random.choice(string.digits + string.letters) for i in range(0, 10)]))
user.save()

Related

Django upgrading unsalted MD5 password not matching

I am migrating an old system that uses unsalted MD5 passwords (the horror!).
I know Django handles automatically password upgrading, as users log in, by adding additional hashers to the PASSWORD_HASHERS list in settings.py.
But, I would like to upgrade the passwords without requiring users to log in, also explained in the docs.
So, I've followed the example in the docs and implemented a custom hasher, legacy/hasher.py:
import secrets
from django.contrib.auth.hashers import PBKDF2PasswordHasher, UnsaltedMD5PasswordHasher
class PBKDF2WrappedMD5PasswordHasher(PBKDF2PasswordHasher):
algorithm = "pbkdf2_wrapped_md5"
def encode_md5_hash(self, md5_hash):
salt = secrets.token_hex(16)
return super().encode(md5_hash, salt)
def encode(self, password, salt, iterations=None):
md5_hash = UnsaltedMD5PasswordHasher().encode(password, salt="")
return self.encode_md5_hash(md5_hash)
and add it to settings.py:
PASSWORD_HASHERS = [
"django.contrib.auth.hashers.PBKDF2PasswordHasher",
"legacy.hashers.PBKDF2WrappedMD5PasswordHasher",
]
However, testing this in the Django shell check_password is returning False for the upgraded password.
>>> from django.contrib.auth.hashers import check_password, UnsaltedMD5PasswordHasher
>>> from legacy.hashers import PBKDF2WrappedMD5PasswordHasher
>>> hasher = PBKDF2WrappedMD5PasswordHasher()
>>> test_pwd = '123456'
>>> test_pwd_unsalted_md5 = UnsaltedMD5PasswordHasher().encode(test_pwd, salt='')
>>> print(test_pwd_unsalted_md5)
'827ccb0eea8a706c4c34a16891f84e7b' # this is an example of a password I want to upgrade
>>> upgraded_test_pwd = hasher.encode_md5_hash(test_pwd)
>>> print(upgraded_test_pwd)
pbkdf2_wrapped_md5$150000$f3aae83b02e8727a2477644eb0aa6560$brqCWW5QuGUoSQ28YNPGUwTLEwZOuMNheN2RxVZGtHQ=
>>> check_password(test_pwd, upgraded_test_pwd)
False
I've looked into other similar SO questions, but didn't found a proper solution there as well.
Short answer: by not taking the provided salt into account, when verifying Django can not (likely) come up with the same encoded password.
The reason this happens is because you generate "salt" out of thin air, and ignore the salt that is passed. Indeed, if we take a look at your implementation, we see:
class PBKDF2WrappedMD5PasswordHasher(PBKDF2PasswordHasher):
algorithm = "pbkdf2_wrapped_md5"
def encode_md5_hash(self, md5_hash):
salt = secrets.token_hex(16) # generating random salt
return super().encode(md5_hash, salt)
def encode(self, password, salt, iterations=None):
md5_hash = UnsaltedMD5PasswordHasher().encode(password, salt='')
return self.encode_md5_hash(md5_hash)
The salt that is passed to the encode(..) method is thus ignored.
This means that if you later want to verify the password, Django will call encode(..) with the salt that it stored (in your case, that is the second part of the encoded password, so f3aae83b02e8727a2477644eb0aa6560), but you decide to throw that away, and generate the password with different salt, and therefore the encoded password, does no longer match with the password you did store in the database.
I advice to use the salt, for example with:
class PBKDF2WrappedMD5PasswordHasher(PBKDF2PasswordHasher):
algorithm = "pbkdf2_wrapped_md5"
def encode_md5_hash(self, md5_hash, salt):
return super().encode(md5_hash, salt)
def encode(self, password, salt, iterations=None):
md5_hash = UnsaltedMD5PasswordHasher().encode(password, salt='')
return self.encode_md5_hash(md5_hash, salt)

Extend number of characters used by Django SessionStore from 32 to 64

Django==1.11.17, Python==3.6.7
I would like to extend the number of characters used for the session key.
Looking in django.contrib.sessions.backends.base.py:
class SessionBase(object):
...
def _get_new_session_key(self):
"Returns session key that isn't being used."
while True:
session_key = get_random_string(32, VALID_KEY_CHARS)
if not self.exists(session_key):
break
return session_key
...
I would like to modify 32 -> 64.
I tried monkey-patching, in one of my files:
import django.contrib.sessions.backends.base as sessions_base
import django.contrib.sessions.backends.file as sessions_file
from django.utils.crypto import get_random_string
def _get_new_session_key(self):
while True:
session_key = get_random_string(64, sessions_base.VALID_KEY_CHARS)
print("SESSION KEY: {}".format(session_key))
if not sessions_file.SessionStore.exists(self, session_key):
break
return session_key
sessions_file.SessionStore._get_new_session_key = _get_new_session_key
django.contrib.sessions.backends.file.py implements SessionStore, and exists().
This solution is just ignored by Django, and I get no print statement showing.
Just fyi, this does print the session id (with 64 chars) but errors out:
sessions_file.SessionStore._get_new_session_key = _get_new_session_key(sessions_file.SessionStore)
File "/Users/.../lib/python3.6/site-packages/django/contrib/sessions/backends/file.py", line 51, in _key_to_file
session_key = self._get_or_create_session_key()
AttributeError: 'str' object has no attribute '_get_or_create_session_key'
Questions:
This does not work. Is it due to the fact that the SessionStore object is instantiated due to a middleware, before this file is accessed?
Should my solution be a middleware? (I'd rather monkey-patch)
NOTE:
Also tried:
1. sessions_file.SessionStore.__dict__["_get_new_session_key"] = _get_new_session_key <-- error is raised
2. setattr(sessions_file.SessionStore, '_get_new_session_key', _get_new_session_key)` <-- same issue, ignored
I managed to do override the session key length successfully, using this code:
import django.contrib.sessions.backends.base as sessions_base
from django.contrib.sessions.backends.db import SessionStore as OriginalSessionStore
from django.utils.crypto import get_random_string
def _get_new_session_key(self):
"Returns session key that isn't being used."
while True:
session_key = get_random_string(40, sessions_base.VALID_KEY_CHARS)
print("*********** SESSION KEY: {}, {}".format(len(session_key), session_key))
if not OriginalSessionStore.exists(self, session_key=session_key):
break
return session_key
OriginalSessionStore._get_new_session_key = _get_new_session_key
What I did find out in the process, is that there is a limit to the session key length, 40 chars.
Anything above 40 chars would trigger an error:
django.db.utils.DataError: value too long for type character varying(40)
I will keep looking for a way to modify the type of the session key to hold a value > 40 chars, so any guidance would be welcome.
Thank you!
EDIT:
I found the model AbstractBaseSession in Django.contrib.sessions.base_session.py:
#python_2_unicode_compatible
class AbstractBaseSession(models.Model):
session_key = models.CharField(_('session key'), max_length=40, primary_key=True)
Currently evaluating the implications of overriding it, necessary migration, etc.

How can I capture a password typed on screen and pass to Python script ?

I have a python script I am running and one of the commands asks for a passcode to be inputted by the user.
I want to capture (save) that passcode and pass it back to the script for later use.
How can I achieve that ?
You can use the getpass module.
import getpass
pw = getpass.getpass() # prompts user for password
pw is now a string that contains the password entered by the user.
from argparse import ArgumentParser
from getpass import getpass
def main():
parser = ArgumentParser(description="arg parser hidden password input.")
parser.add_argument('-sp', '--secure_password', action='store_true', dest='password',
help='hidden password prompt')
args=parser.parse_args()
if args.password:
password = getpass()
print(password)
if __name__ == "__main__":
main()

Can't authenticate with custom PASSWORD_HASHERS

I am working on the migration of one website with php to Django framework.
There is used to a specific hash passwords algorithm, so I had to write:
#settings.py
PASSWORD_HASHERS = (
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
'project.hashers.SHA1ProjPasswordHasher', # that's mine
'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
...
)
and:
#hashers.py
import hashlib
from django.contrib.auth.hashers import (BasePasswordHasher, mask_hash)
from django.utils.datastructures import SortedDict
from django.utils.encoding import force_bytes
from django.utils.crypto import constant_time_compare
from django.utils.translation import ugettext_noop as _
class SHA1ProjPasswordHasher(BasePasswordHasher):
"""
Special snowflake algorithm from the first version.
php code: $pass=substr(sha1(trim($_POST['password'])),0,8);
"""
algorithm = "unsalted_and_trimmed_sha1"
def salt(self):
return ''
def encode(self, password, salt):
return hashlib.sha1(force_bytes(salt + password)).hexdigest()[:8]
def verify(self, password, encoded):
encoded_2 = self.encode(password, '')
return constant_time_compare(encoded, encoded_2)
def safe_summary(self, encoded):
return SortedDict([
(_('algorithm'), self.algorithm),
(_('hash'), mask_hash(encoded, show=3)),
])
It's works well when PBKDF2PasswordHasher is first:
>>> from django.contrib.auth import authenticate
>>> u = authenticate(username='root', password='test')
>>> u.password
u'pbkdf2_sha256$10000$EX8BcgPFjygx$HvB6NmZ7uX1rWOOPbHRKd8GLYD3cAsQtlprXUq1KGMk='
>>> exit()
Then I put my SHA1ProjPasswordHasher on the first place, first authentication works great. The hash was changed.:
>>> from django.contrib.auth import authenticate
>>> u = authenticate(username='root', password='test')
>>> u.password
'a94a8fe5'
>>> exit()
Second authentication is failed. Can't authenticate with new hash.
>>> from django.contrib.auth import authenticate
>>> u = authenticate(username='root', password='test')
>>> u.password
Traceback (most recent call last):
File "<console>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'password'
What could be the problem? Thanks.
UPDATE: Ok, the problem became more clear. When I remove slice from here:
return hashlib.sha1(force_bytes(salt + password)).hexdigest()[:8]
everything working fine. I can't get why..
Although years have passed, i' m putting my solution here for future reference
Vlad is partially right; The following method from django.contrib.auth.hashers seems to be forcing you to use a hash format that includes the dollar $ sign to mark the algorithm used for django to decide which hasher to use
def identify_hasher(encoded):
"""
Returns an instance of a loaded password hasher.
Identifies hasher algorithm by examining encoded hash, and calls
get_hasher() to return hasher. Raises ValueError if
algorithm cannot be identified, or if hasher is not loaded.
"""
# Ancient versions of Django created plain MD5 passwords and accepted
# MD5 passwords with an empty salt.
if ((len(encoded) == 32 and '$' not in encoded) or
(len(encoded) == 37 and encoded.startswith('md5$$'))):
algorithm = 'unsalted_md5'
# Ancient versions of Django accepted SHA1 passwords with an empty salt.
elif len(encoded) == 46 and encoded.startswith('sha1$$'):
algorithm = 'unsalted_sha1'
else:
algorithm = encoded.split('$', 1)[0]
return get_hasher(algorithm)
There is a way though to "trick" django without hacking your django installation. You 'll have to create an authentication backend to use for your authentication. There you'll override django's check_password method. I had a db where hashes were {SSHA512}hash and i couldn't change this because i had to be able to communicate with dovecot. So i put the following in my backends.py class:
def check_password(self, raw_password, user):
"""
Returns a boolean of whether the raw_password was correct. Handles
hashing formats behind the scenes.
"""
def setter(raw_password):
user.set_password(raw_password)
user.save(update_fields=["password"])
return check_password(raw_password, "SSHA512$" + user.password, setter)
That way when django has to check if a password is correct it'll do the following:
-Get the hash from the db {SSHA512}hash
-Append it temporarily a SSHA512$ string on the beginning and then check
So while you'll be having {SSHA512}hash in your database, when django is using this backend it'll see SSHA512${SSHA512}hash.
This way in your hashers.py you can set in your class algorithm = "SSHA512" which will hint django to use this hasher for that case.
Your def encode(self, password, salt, iterations=None) method in your hashers.py will save hashes the way dovecot needs {SSHA512}hash (you don't have to do anything weird in your encode method).
Your def verify(self, password, encoded) method though will have to strip the SSHA512$ "trick" from the encoded string that it is passed to compare it with the one that encode will create.
So there you have it! Django will use your hasher for checking hashes that don't contain a dollar $ sign and you don't have to break anything inside django :)
Only unsalted md5 hashes can not include a dollar sign:
# django/contrib/auth/hashers.py
def identify_hasher(encoded):
"""
Returns an instance of a loaded password hasher.
Identifies hasher algorithm by examining encoded hash, and calls
get_hasher() to return hasher. Raises ValueError if
algorithm cannot be identified, or if hasher is not loaded.
"""
if len(encoded) == 32 and '$' not in encoded:
algorithm = 'unsalted_md5'
else:
algorithm = encoded.split('$', 1)[0]
return get_hasher(algorithm)
So the best way is convert the current password hashes to the format: alg$salt$hash
class SHA1ProjPasswordHasher(BasePasswordHasher):
"""
Special snowflake algorithm from the first version.
php code: $pass=substr(sha1(trim($_POST['password'])),0,8);
"""
algorithm = "unsalted_and_trimmed_sha1"
def salt(self):
return ''
def encode(self, password, salt):
assert password
assert '$' not in salt
hash = hashlib.sha1(force_bytes(salt + password)).hexdigest()[:8]
return "%s$%s$%s" % (self.algorithm, salt, hash)
def verify(self, password, encoded):
algorithm, salt, hash = encoded.split('$', 2)
assert algorithm == self.algorithm
encoded_2 = self.encode(password, salt)
return constant_time_compare(encoded, encoded_2)
def safe_summary(self, encoded):
algorithm, salt, hash = encoded.split('$', 2)
assert algorithm == self.algorithm
return SortedDict([
(_('algorithm'), algorithm),
(_('salt'), mask_hash(salt, show=2)),
(_('hash'), mask_hash(hash)),
])
.
>>> from django.contrib.auth import authenticate
>>> x = authenticate(username='root', password='test')
>>> x
<User: root>
>>> x.password
u'unsalted_and_trimmed_sha1$$a94a8fe5'

Enforcing password strength requirements with django.contrib.auth.views.password_change

We have a Django application that requires a specific level of password complexity. We currently enforce this via client-side JavaScript which can easily be defeated by someone who is appropriately motivated.
I cannot seem to find any specific information about setting up server-side password strength validation using the django contrib built in views. Before I go about re-inventing the wheel, is there a proper way to handle this requirement?
I also went with a custom form for this. In urls.py specify your custom form:
(r'^change_password/$', 'django.contrib.auth.views.password_change',
{'password_change_form': ValidatingPasswordChangeForm}),
Inherit from PasswordChangeForm and implement validation:
from django import forms
from django.contrib import auth
class ValidatingPasswordChangeForm(auth.forms.PasswordChangeForm):
MIN_LENGTH = 8
def clean_new_password1(self):
password1 = self.cleaned_data.get('new_password1')
# At least MIN_LENGTH long
if len(password1) < self.MIN_LENGTH:
raise forms.ValidationError("The new password must be at least %d characters long." % self.MIN_LENGTH)
# At least one letter and one non-letter
first_isalpha = password1[0].isalpha()
if all(c.isalpha() == first_isalpha for c in password1):
raise forms.ValidationError("The new password must contain at least one letter and at least one digit or" \
" punctuation character.")
# ... any other validation you want ...
return password1
Django 1.9 offers a built-in password validation to help prevent the usage of weak passwords by users. It's enabled by modifing the AUTH_PASSWORD_VALIDATORS setting in our project. By default Django comes with following validators:
UserAttributeSimilarityValidator, which checks the similarity between
the password and a set of attributes of the user.
MinimumLengthValidator, which simply checks whether the password
meets a minimum length. This validator is configured with a custom
option: it now requires the minimum length to be nine characters,
instead of the default eight.
CommonPasswordValidator, which checks
whether the password occurs in a list of common passwords. By
default, it compares to an included list of 1000 common passwords.
NumericPasswordValidator, which checks whether the password isn’t
entirely numeric.
This example enables all four included validators:
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
'OPTIONS': {
'min_length': 9,
}
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
As some eluded to with the custom validators, here's the approach I would take...
Create a validator:
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext as _
def validate_password_strength(value):
"""Validates that a password is as least 7 characters long and has at least
1 digit and 1 letter.
"""
min_length = 7
if len(value) < min_length:
raise ValidationError(_('Password must be at least {0} characters '
'long.').format(min_length))
# check for digit
if not any(char.isdigit() for char in value):
raise ValidationError(_('Password must contain at least 1 digit.'))
# check for letter
if not any(char.isalpha() for char in value):
raise ValidationError(_('Password must contain at least 1 letter.'))
Then add the validator to the form field you're looking to validate:
from django.contrib.auth.forms import SetPasswordForm
class MySetPasswordForm(SetPasswordForm):
def __init__(self, *args, **kwargs):
super(MySetPasswordForm, self).__init__(*args, **kwargs)
self.fields['new_password1'].validators.append(validate_password_strength)
I'd just install django-passwords and let that handle it for you: https://github.com/dstufft/django-passwords
After that you can simply subclass the registration form and replace the field with a PasswordField.
I think you should just write your own validator ( or use RegexValidator, see: http://docs.djangoproject.com/en/dev/ref/validators/ ) if you use forms or write some other script checking for regular expressions. This should be a simple task. Also I don't think there is any builtin mechanism, simply because each person understands the concept of "strong password" a little bit different.