[Checkins] SVN: zope.password/trunk/ Add a crypt password manager.

Martijn Pieters mj at zopatista.com
Sun Feb 20 08:37:09 EST 2011


Log message for revision 120461:
  Add a crypt password manager.

Changed:
  U   zope.password/trunk/CHANGES.txt
  U   zope.password/trunk/src/zope/password/configure.zcml
  A   zope.password/trunk/src/zope/password/legacy.py
  U   zope.password/trunk/src/zope/password/testing.py
  U   zope.password/trunk/src/zope/password/tests/test_password.py

-=-
Modified: zope.password/trunk/CHANGES.txt
===================================================================
--- zope.password/trunk/CHANGES.txt	2011-02-20 13:00:11 UTC (rev 120460)
+++ zope.password/trunk/CHANGES.txt	2011-02-20 13:37:08 UTC (rev 120461)
@@ -12,6 +12,9 @@
 - Use {SHA} as the prefix for SHA1-encoded passwords to be compatible with
   RFC 2307, but support matching against {SHA1} for backwards compatibility.
 
+- Add a crypt password manager to fully support all methods named in RFC 2307.
+  It is contained in the 'legacy' module however, to flag crypt's status.
+
 3.6.1 (2010-05-27)
 ------------------
 

Modified: zope.password/trunk/src/zope/password/configure.zcml
===================================================================
--- zope.password/trunk/src/zope/password/configure.zcml	2011-02-20 13:00:11 UTC (rev 120460)
+++ zope.password/trunk/src/zope/password/configure.zcml	2011-02-20 13:37:08 UTC (rev 120461)
@@ -27,7 +27,15 @@
       factory=".password.SSHAPasswordManager"
       />
 
+  <configure zcml:condition="installed crypt">
   <utility
+      name="Crypt"
+      provides=".interfaces.IPasswordManager"
+      factory=".legacy.CryptPasswordManager"
+      />
+  </configure>
+
+  <utility
       component=".vocabulary.PasswordManagerNamesVocabulary"
       provides="zope.schema.interfaces.IVocabularyFactory"
       name="Password Manager Names"
@@ -51,6 +59,10 @@
       <allow interface=".interfaces.IPasswordManager" />
     </class>
 
+    <class class=".password.SSHAPasswordManager">
+      <allow interface=".interfaces.IPasswordManager" />
+    </class>
+
   </configure>
 
 </configure>

Added: zope.password/trunk/src/zope/password/legacy.py
===================================================================
--- zope.password/trunk/src/zope/password/legacy.py	                        (rev 0)
+++ zope.password/trunk/src/zope/password/legacy.py	2011-02-20 13:37:08 UTC (rev 120461)
@@ -0,0 +1,114 @@
+##############################################################################
+#
+# Copyright (c) 2009 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Legacy password managers, using now-outdated, insecure methods for hashing
+"""
+__docformat__ = 'restructuredtext'
+
+from codecs import getencoder
+
+try:
+    from crypt import crypt
+    from random import choice
+except ImportError:
+    # The crypt module is not universally available, apparently
+    crypt = None
+
+from zope.interface import implements
+from zope.password.interfaces import IPasswordManager
+
+_encoder = getencoder("utf-8")
+
+
+if crypt is not None:
+    class CryptPasswordManager(object):
+        """Crypt password manager.
+        
+        Implements a UNIX crypt(3) hashing scheme. Note that crypt is 
+        considered far inferior to more modern schemes such as SSHA hashing,
+        and only uses the first 8 characters of a password.
+        
+        >>> from zope.interface.verify import verifyObject
+
+        >>> manager = CryptPasswordManager()
+        >>> verifyObject(IPasswordManager, manager)
+        True
+
+        >>> password = u"right \N{CYRILLIC CAPITAL LETTER A}"
+        >>> encoded = manager.encodePassword(password, salt="..")
+        >>> encoded
+        '{CRYPT}..I1I8wps4Na2'
+        >>> manager.match(encoded)
+        True
+        >>> manager.checkPassword(encoded, password)
+        True
+
+        Unfortunately, crypt only looks at the first 8 characters, so matching
+        against an 8 character password plus suffix always matches. Our test
+        password (including utf-8 encoding) is exactly 8 characters long, and
+        thus affixing 'wrong' to it tests as a correct password::
+        
+        >>> manager.checkPassword(encoded, password + u"wrong")
+        True
+
+        Using a completely different password is rejected as expected::
+
+        >>> manager.checkPassword(encoded, 'completely wrong')
+        False
+
+        Using the `openssl passwd` command-line utility to encode ``secret``, 
+        we get ``erz50QD3gv4Dw`` as seeded hash.
+
+        Our password manager generates the same value when seeded with the
+        same salt, so we can be sure, our output is compatible with
+        standard LDAP tools that also use crypt::
+
+        >>> salt = 'er'
+        >>> password = 'secret'
+        >>> encoded = manager.encodePassword(password, salt)
+        >>> encoded
+        '{CRYPT}erz50QD3gv4Dw'
+
+        >>> manager.checkPassword(encoded, password)
+        True
+        >>> manager.checkPassword(encoded, password + u"wrong")
+        False
+
+        >>> manager.encodePassword(password) != manager.encodePassword(password)
+        True
+
+        The manager only claims to implement CRYPT encodings, anything not 
+        starting with the string {CRYPT} returns False::
+
+        >>> manager.match('{MD5}someotherhash')
+        False
+
+        """
+
+        implements(IPasswordManager)
+
+        def encodePassword(self, password, salt=None):
+            if salt is None:
+                choices = ("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                           "abcdefghijklmnopqrstuvwxyz"
+                           "0123456789./")
+                salt = choice(choices) + choice(choices)
+            return '{CRYPT}%s' % crypt(_encoder(password)[0], salt)
+
+        def checkPassword(self, encoded_password, password):
+            return encoded_password == self.encodePassword(password, 
+                encoded_password[7:9])
+
+        def match(self, encoded_password):
+            return encoded_password.startswith('{CRYPT}')
+


Property changes on: zope.password/trunk/src/zope/password/legacy.py
___________________________________________________________________
Added: svn:eol-style
   + native

Modified: zope.password/trunk/src/zope/password/testing.py
===================================================================
--- zope.password/trunk/src/zope/password/testing.py	2011-02-20 13:00:11 UTC (rev 120460)
+++ zope.password/trunk/src/zope/password/testing.py	2011-02-20 13:37:08 UTC (rev 120461)
@@ -25,7 +25,12 @@
 from zope.password.password import SSHAPasswordManager
 from zope.password.vocabulary import PasswordManagerNamesVocabulary
 
+try:
+    from zope.password.legacy import CryptPasswordManager
+except ImportError:
+    CryptPasswordManager = None
 
+
 def setUpPasswordManagers():
     """Helper function for setting up password manager utilities for tests
     
@@ -41,6 +46,16 @@
     >>> getUtility(IPasswordManager, 'SHA1')
     <zope.password.password.SHA1PasswordManager object at 0x...>
 
+    >>> try:
+    ...     import crypt
+    ... except ImportError:
+    ...     CryptPasswordManager = None
+    ...     True
+    ... else:
+    ...     from zope.password.legacy import CryptPasswordManager
+    ...     getUtility(IPasswordManager, 'Crypt') is CryptPasswordManager
+    True
+
     >>> voc = getUtility(IVocabularyFactory, 'Password Manager Names')
     >>> voc = voc(None)
     >>> voc
@@ -53,12 +68,18 @@
     True
     >>> 'MD5' in voc
     True
+
+    >>> CryptPasswordManager is None or 'Crypt' in voc
+    True
     
     """
     provideUtility(PlainTextPasswordManager(), IPasswordManager, 'Plain Text')
     provideUtility(SSHAPasswordManager(), IPasswordManager, 'SSHA')
     provideUtility(MD5PasswordManager(), IPasswordManager, 'MD5')
     provideUtility(SHA1PasswordManager(), IPasswordManager, 'SHA1')
+    
+    if CryptPasswordManager is not None:
+        provideUtility(CryptPasswordManager, IPasswordManager, 'Crypt')
 
     provideUtility(PasswordManagerNamesVocabulary,
                    IVocabularyFactory, 'Password Manager Names')

Modified: zope.password/trunk/src/zope/password/tests/test_password.py
===================================================================
--- zope.password/trunk/src/zope/password/tests/test_password.py	2011-02-20 13:00:11 UTC (rev 120460)
+++ zope.password/trunk/src/zope/password/tests/test_password.py	2011-02-20 13:37:08 UTC (rev 120461)
@@ -19,6 +19,7 @@
 def test_suite():
     return unittest.TestSuite((
         doctest.DocTestSuite('zope.password.password'),
+        doctest.DocTestSuite('zope.password.legacy'),
         doctest.DocTestSuite(
             'zope.password.testing',
             optionflags=doctest.ELLIPSIS),



More information about the checkins mailing list