From a49fa746cdc056f0b660f47fbc55aa43fcd54bcc Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Mon, 21 Nov 2005 03:33:22 +0000 Subject: [PATCH] Fixed #273 -- BACKWARDS-INCOMPATIBLE CHANGE -- Changed auth.User.password field to add support for other password encryption algorithms. Renamed password_md5 to password and changed field length from 32 to 128. See http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges for upgrade information git-svn-id: http://code.djangoproject.com/svn/django/trunk@1327 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/models/auth.py | 41 +++++++++++++++++++++++++++++++---------- docs/authentication.txt | 28 +++++++++++++++++++++++++--- 2 files changed, 56 insertions(+), 13 deletions(-) diff --git a/django/models/auth.py b/django/models/auth.py index d0c13f66ce..21b4c2f146 100644 --- a/django/models/auth.py +++ b/django/models/auth.py @@ -34,7 +34,7 @@ class User(meta.Model): first_name = meta.CharField(_('first name'), maxlength=30, blank=True) last_name = meta.CharField(_('last name'), maxlength=30, blank=True) email = meta.EmailField(_('e-mail address'), blank=True) - password_md5 = meta.CharField(_('password'), maxlength=32, help_text=_("Use an MD5 hash -- not the raw password.")) + password = meta.CharField(_('password'), maxlength=128, help_text=_("Use '[algo]$[salt]$[hexdigest]")) is_staff = meta.BooleanField(_('staff status'), help_text=_("Designates whether the user can log into this admin site.")) is_active = meta.BooleanField(_('active'), default=True) is_superuser = meta.BooleanField(_('superuser status')) @@ -53,7 +53,7 @@ class User(meta.Model): exceptions = ('SiteProfileNotAvailable',) admin = meta.Admin( fields = ( - (None, {'fields': ('username', 'password_md5')}), + (None, {'fields': ('username', 'password')}), (_('Personal info'), {'fields': ('first_name', 'last_name', 'email')}), (_('Permissions'), {'fields': ('is_staff', 'is_active', 'is_superuser', 'user_permissions')}), (_('Important dates'), {'fields': ('last_login', 'date_joined')}), @@ -78,13 +78,35 @@ class User(meta.Model): return full_name.strip() def set_password(self, raw_password): - import md5 - self.password_md5 = md5.new(raw_password).hexdigest() + import sha, random + algo = 'sha1' + salt = sha.new(str(random.random())).hexdigest()[:5] + hsh = sha.new(salt+raw_password).hexdigest() + self.password = '%s$%s$%s' % (algo, salt, hsh) def check_password(self, raw_password): - "Returns a boolean of whether the raw_password was correct." - import md5 - return self.password_md5 == md5.new(raw_password).hexdigest() + """ + Returns a boolean of whether the raw_password was correct. Handles + encryption formats behind the scenes. + """ + # Backwards-compatibility check. Older passwords won't include the + # algorithm or salt. + if '$' not in self.password: + import md5 + is_correct = (self.password == md5.new(raw_password).hexdigest()) + if is_correct: + # Convert the password to the new, more secure format. + self.set_password(raw_password) + self.save() + return is_correct + algo, salt, hsh = self.password.split('$') + if algo == 'md5': + import md5 + return hsh == md5.new(salt+raw_password).hexdigest() + elif algo == 'sha1': + import sha + return hsh == sha.new(salt+raw_password).hexdigest() + raise ValueError, "Got unknown password algorithm type in password." def get_group_permissions(self): "Returns a list of permission strings that this user has through his/her groups." @@ -176,10 +198,9 @@ class User(meta.Model): def _module_create_user(username, email, password): "Creates and saves a User with the given username, e-mail and password." - import md5 - password_md5 = md5.new(password).hexdigest() now = datetime.datetime.now() - user = User(None, username, '', '', email.strip().lower(), password_md5, False, True, False, now, now) + user = User(None, username, '', '', email.strip().lower(), 'placeholder', False, True, False, now, now) + user.set_password(password) user.save() return user diff --git a/docs/authentication.txt b/docs/authentication.txt index f9093c81e2..475595e972 100644 --- a/docs/authentication.txt +++ b/docs/authentication.txt @@ -44,9 +44,9 @@ Fields * ``first_name`` -- Optional. 30 characters or fewer. * ``last_name`` -- Optional. 30 characters or fewer. * ``email`` -- Optional. E-mail address. - * ``password_md5`` -- Required. An MD5 hash of the password. (Django - doesn't store the raw password.) Raw passwords can be arbitrarily long - and can contain any character. + * ``password`` -- Required. A hash of, and metadata about, the password. + (Django doesn't store the raw password.) Raw passwords can be arbitrarily + long and can contain any character. See the "Passwords" section below. * ``is_staff`` -- Boolean. Designates whether this user can access the admin site. * ``is_active`` -- Boolean. Designates whether this user can log into the @@ -167,6 +167,28 @@ Change a password with ``set_password()``:: >>> u.set_password('new password') >>> u.save() +Passwords +--------- + +**This only applies to the Django development version.** Previous versions, +such as Django 0.90, used simple MD5 hashes without password salts. + +The ``password`` field of a ``User`` object is a string in this format:: + + hashtype$salt$hash + +That's hashtype, salt and hash, separated by the dollar-sign character. + +Hashtype is either ``sha1`` (default) or ``md5``. Salt is a random string +used to salt the raw password to create the hash. + +For example:: + + sha1$a1976$a36cc8cbf81742a8fb52e221aaeab48ed7f58ab4 + +The ``User.set_password()`` and ``User.check_password()`` functions handle +the setting and checking of these values behind the scenes. + Anonymous users ---------------