Skip to content

Commit

Permalink
Remove the completely useless 'cosmetic' salt from the MD5 manager.
Browse files Browse the repository at this point in the history
The generated salt was not being used to generate the actual hash and had no
cryptographic meaning. It only served to make the output incompatible with
RFC 2307 MD5 implementations. Any encoded input with the salt still in place are still supported for password checks.
  • Loading branch information
Martijn Pieters committed Feb 20, 2011
1 parent 03bc51c commit e4f0fb1
Show file tree
Hide file tree
Showing 3 changed files with 22 additions and 22 deletions.
5 changes: 5 additions & 0 deletions CHANGES.txt
Expand Up @@ -19,6 +19,11 @@ CHANGES
- Add a MySQL PASSWORD() (versions before 4.1) password manager, as also found
in Zope2's AccessControl.AuthEncoding module.

- Remove the useless, cosmetic salt from the MD5 password manager. This makes
this manager compatible with other MD5 hash implementations such as RFC 2307
but doesn't lower it's security in any way. Old, still 'salted' password
hashes are still supported.

3.6.1 (2010-05-27)
------------------

Expand Down
5 changes: 1 addition & 4 deletions README.txt
Expand Up @@ -13,10 +13,7 @@ six implementations:
more secure implementations.

* MD5PasswordManager - a password manager that uses MD5 algorithm to
encode passwords. It adds salt to the encoded password, but the salt
is not used for encoding the password itself, so the use of salt in
it is purely cosmetic. It's generally weak against dictionary
attacks.
encode passwords. It's generally weak against dictionary attacks.

* SHA1PasswordManager - a password manager that uses SHA1 algorithm to
encode passwords. It has the same salt weakness as the
Expand Down
34 changes: 16 additions & 18 deletions src/zope/password/password.py
Expand Up @@ -168,17 +168,14 @@ def match(self, encoded_password):
class MD5PasswordManager(PlainTextPasswordManager):
"""MD5 password manager.
Note: use of salt in this password manager is purely
cosmetical. Use SSHA if you want increased security.
>>> from zope.interface.verify import verifyObject
>>> manager = MD5PasswordManager()
>>> verifyObject(IMatchingPasswordManager, manager)
True
>>> password = u"right \N{CYRILLIC CAPITAL LETTER A}"
>>> encoded = manager.encodePassword(password, salt="")
>>> encoded = manager.encodePassword(password)
>>> encoded
'{MD5}86dddccec45db4599f1ac00018e54139'
>>> manager.match(encoded)
Expand All @@ -189,22 +186,19 @@ class MD5PasswordManager(PlainTextPasswordManager):
False
>>> encoded = manager.encodePassword(password)
>>> encoded[-32:]
'86dddccec45db4599f1ac00018e54139'
>>> encoded
'{MD5}86dddccec45db4599f1ac00018e54139'
>>> manager.match(encoded)
True
>>> manager.checkPassword(encoded, password)
True
>>> manager.checkPassword(encoded, password + u"wrong")
False
>>> manager.encodePassword(password) != manager.encodePassword(password)
True
The old version of this password manager didn't add the {MD5} to
passwords. Let's check if it can work with old stored passwords.
>>> encoded = manager.encodePassword(password, salt="")
>>> encoded = manager.encodePassword(password)
>>> encoded = encoded[5:]
>>> encoded
'86dddccec45db4599f1ac00018e54139'
Expand All @@ -218,19 +212,23 @@ class MD5PasswordManager(PlainTextPasswordManager):
>>> manager.match(encoded)
False
A previous version of this manager also created a cosmetic salt, added
to the start of the hash, but otherwise not used in creating the hash
itself. To still support these 'hashed' passwords, only the last 32 bytes
of the pre-existing hash are used:
>>> manager.checkPassword('salt' + encoded, password)
True
"""

def encodePassword(self, password, salt=None):
if salt is None:
salt = "%08x" % randint(0, 0xffffffff)
return '{MD5}%s%s' % (salt, md5(_encoder(password)[0]).hexdigest())
# The salt argument only exists for backwards compatibility and is
# ignored on purpose.
return '{MD5}%s' % (md5(_encoder(password)[0]).hexdigest())

def checkPassword(self, encoded_password, password):
if encoded_password.startswith('{MD5}'):
salt = encoded_password[5:-32]
return encoded_password == self.encodePassword(password, salt)
salt = encoded_password[:-32]
return encoded_password == self.encodePassword(password, salt)[5:]
return encoded_password[-32:] == self.encodePassword(password)[-32:]

def match(self, encoded_password):
return encoded_password.startswith('{MD5}')
Expand Down

0 comments on commit e4f0fb1

Please sign in to comment.