[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