[Checkins] SVN: zope.password/trunk/ Add a 'match' method to the IPasswordManager interface, which returns True if a given password hash was encdoded with the scheme implemented by the specific manager.
Martijn Pieters
mj at zopatista.com
Sun Feb 20 06:23:47 EST 2011
Log message for revision 120458:
Add a 'match' method to the IPasswordManager interface, which returns True if a given password hash was encdoded with the scheme implemented by the specific manager.
Note that the plain-text manager always returns False for this method, as the alternative is to always return True and thus also validate hashed password against their literal values, a security risk.
Changed:
U zope.password/trunk/CHANGES.txt
U zope.password/trunk/README.txt
U zope.password/trunk/setup.py
U zope.password/trunk/src/zope/password/interfaces.py
U zope.password/trunk/src/zope/password/password.py
-=-
Modified: zope.password/trunk/CHANGES.txt
===================================================================
--- zope.password/trunk/CHANGES.txt 2011-02-20 10:39:28 UTC (rev 120457)
+++ zope.password/trunk/CHANGES.txt 2011-02-20 11:23:47 UTC (rev 120458)
@@ -2,10 +2,12 @@
CHANGES
=======
-3.6.2 (unreleased)
+4.0.0 (unreleased)
------------------
-- Nothing changed yet.
+- Add a 'match' method to the IPasswordManager interface, which returns True
+ if a given password hash was encdoded with the scheme implemented by the
+ specific manager.
3.6.1 (2010-05-27)
Modified: zope.password/trunk/README.txt
===================================================================
--- zope.password/trunk/README.txt 2011-02-20 10:39:28 UTC (rev 120457)
+++ zope.password/trunk/README.txt 2011-02-20 11:23:47 UTC (rev 120458)
@@ -39,7 +39,7 @@
It's very easy to use password managers. The
``zope.password.interfaces.IPasswordManager`` interface defines only
-two methods::
+three methods::
def encodePassword(password):
"""Return encoded data for the given password"""
@@ -47,6 +47,13 @@
def checkPassword(encoded_password, password):
"""Return whether the given encoded data coincide with the given password"""
+ def match(encoded_password):
+ """
+ Returns True when the given data was encoded with the scheme
+ implemented by this password manager.
+
+ """
+
The implementations mentioned above are in the
``zope.password.password`` module.
Modified: zope.password/trunk/setup.py
===================================================================
--- zope.password/trunk/setup.py 2011-02-20 10:39:28 UTC (rev 120457)
+++ zope.password/trunk/setup.py 2011-02-20 11:23:47 UTC (rev 120458)
@@ -17,7 +17,7 @@
setup(name='zope.password',
- version='3.6.2dev',
+ version='4.0.0dev',
author='Zope Foundation and Contributors',
author_email='zope-dev at zope.org',
description='Password encoding and checking utilities',
Modified: zope.password/trunk/src/zope/password/interfaces.py
===================================================================
--- zope.password/trunk/src/zope/password/interfaces.py 2011-02-20 10:39:28 UTC (rev 120457)
+++ zope.password/trunk/src/zope/password/interfaces.py 2011-02-20 11:23:47 UTC (rev 120458)
@@ -23,3 +23,10 @@
def checkPassword(encoded_password, password):
"""Does the given encoded data coincide with the given password"""
+
+ def match(encoded_password):
+ """
+ Returns True when the given data was encoded with the scheme
+ implemented by this password manager.
+
+ """
Modified: zope.password/trunk/src/zope/password/password.py
===================================================================
--- zope.password/trunk/src/zope/password/password.py 2011-02-20 10:39:28 UTC (rev 120457)
+++ zope.password/trunk/src/zope/password/password.py 2011-02-20 11:23:47 UTC (rev 120458)
@@ -50,6 +50,15 @@
True
>>> manager.checkPassword(encoded, password + u"wrong")
False
+
+ The plain text password manager *never* claims to implement the scheme,
+ because this would open a security hole, where a hash from a different
+ scheme could be used as-is as a plain-text password. Authentication code
+ that needs to support plain-text passwords need to explicitly check for
+ plain-text password matches after all other options have been tested for::
+
+ >>> manager.match(encoded)
+ False
"""
implements(IPasswordManager)
@@ -60,7 +69,15 @@
def checkPassword(self, encoded_password, password):
return encoded_password == self.encodePassword(password)
+ def match(self, encoded_password):
+ # We always return False for PlainText because it was a) not encrypted
+ # and b) matching against actual encryption methods would result in
+ # the ability to authenticate with the un-encrypted hash as a password.
+ # For example, you should not be able to authenticate with a literal
+ # SSHA hash.
+ return False
+
class SSHAPasswordManager(PlainTextPasswordManager):
"""SSHA password manager.
@@ -83,6 +100,8 @@
>>> encoded
'{SSHA}BLTuxxVMXzouxtKVb7gLgNxzdAI='
+ >>> manager.match(encoded)
+ True
>>> manager.checkPassword(encoded, password)
True
>>> manager.checkPassword(encoded, password + u"wrong")
@@ -118,6 +137,12 @@
>>> manager.checkPassword(unicode(manager.encodePassword(passwd)), passwd)
True
+ The manager only claims to implement SSHA encodings, anything not starting
+ with the string {SSHA} returns False::
+
+ >>> manager.match('{MD5}someotherhash')
+ False
+
"""
implements(IPasswordManager)
@@ -138,7 +163,10 @@
salt = byte_string[20:]
return encoded_password == self.encodePassword(password, salt)
+ def match(self, encoded_password):
+ return encoded_password.startswith('{SSHA}')
+
class MD5PasswordManager(PlainTextPasswordManager):
"""MD5 password manager.
@@ -155,6 +183,8 @@
>>> encoded = manager.encodePassword(password, salt="")
>>> encoded
'{MD5}86dddccec45db4599f1ac00018e54139'
+ >>> manager.match(encoded)
+ True
>>> manager.checkPassword(encoded, password)
True
>>> manager.checkPassword(encoded, password + u"wrong")
@@ -163,6 +193,8 @@
>>> encoded = manager.encodePassword(password)
>>> encoded[-32:]
'86dddccec45db4599f1ac00018e54139'
+ >>> manager.match(encoded)
+ True
>>> manager.checkPassword(encoded, password)
True
>>> manager.checkPassword(encoded, password + u"wrong")
@@ -181,6 +213,13 @@
>>> manager.checkPassword(encoded, password)
True
+
+ However, because the prefix is missing, the password manager cannot claim
+ to implement the scheme:
+
+ >>> manager.match(encoded)
+ False
+
"""
implements(IPasswordManager)
@@ -197,7 +236,10 @@
salt = encoded_password[:-32]
return encoded_password == self.encodePassword(password, salt)[5:]
+ def match(self, encoded_password):
+ return encoded_password.startswith('{MD5}')
+
class SHA1PasswordManager(PlainTextPasswordManager):
"""SHA1 password manager.
@@ -214,6 +256,8 @@
>>> encoded = manager.encodePassword(password, salt="")
>>> encoded
'{SHA1}04b4eec7154c5f3a2ec6d2956fb80b80dc737402'
+ >>> manager.match(encoded)
+ True
>>> manager.checkPassword(encoded, password)
True
>>> manager.checkPassword(encoded, password + u"wrong")
@@ -222,6 +266,8 @@
>>> encoded = manager.encodePassword(password)
>>> encoded[-40:]
'04b4eec7154c5f3a2ec6d2956fb80b80dc737402'
+ >>> manager.match(encoded)
+ True
>>> manager.checkPassword(encoded, password)
True
>>> manager.checkPassword(encoded, password + u"wrong")
@@ -241,6 +287,12 @@
>>> manager.checkPassword(encoded, password)
True
+ However, because the prefix is missing, the password manager cannot claim
+ to implement the scheme:
+
+ >>> manager.match(encoded)
+ False
+
"""
implements(IPasswordManager)
@@ -257,7 +309,10 @@
salt = encoded_password[:-40]
return encoded_password == self.encodePassword(password, salt)[6:]
+ def match(self, encoded_password):
+ return encoded_password.startswith('{SHA1}')
+
# Simple registry
managers = [
('Plain Text', PlainTextPasswordManager()),
More information about the checkins
mailing list