jamadden: I'm seeing [a mysterious drop in coverage](https://coveralls.io/builds/12899080/source?filename=src%2Fzope%2Fpassword%2Fzpasswd.py#L262) in `zpasswd.py`. I'm not sure why or how that happened...my best guess is that it's something to do with the cleanup code tearing down the component registry, which would suggest that we were relying on an accidental side-effect before.

I can fix it, but I'd hope that #12 gets merged first, otherwise there will be conflicts.
diff --git a/CHANGES.rst b/CHANGES.rst
index 421388f..1375b4f 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -5,8 +5,13 @@
 4.3.0 (unreleased)
 ==================
 
-- Added a ``bcrypt``-based password manager (available only if the ``bcrypt``
-  library is importable).
+- Added a ``bcrypt``-based password manager (available only if the
+  `bcrypt <https://pypi.python.org/pypi/bcrypt>`_ library is
+  importable). This manager can also check passwords that were encoded
+  with `z3c.bcrypt <https://pypi.python.org/pypi/z3c.bcrypt>`_. If
+  that package is *not* installed, then ``configure.zcml`` will
+  install this manager as a utility with both the ``BCRYPT``
+  (preferred) and ``bcrypt`` names for compatibility with it.
 
 - Add support for Python 3.6.
 
diff --git a/src/zope/password/configure.zcml b/src/zope/password/configure.zcml
index 035c27f..26e2362 100644
--- a/src/zope/password/configure.zcml
+++ b/src/zope/password/configure.zcml
@@ -3,6 +3,8 @@
     xmlns:zcml="http://namespaces.zope.org/zcml"
     >
 
+  <include package="zope.component" file="meta.zcml" />
+
   <utility
       name="Plain Text"
       provides=".interfaces.IMatchingPasswordManager"
@@ -45,6 +47,16 @@
        provides=".interfaces.IMatchingPasswordManager"
        factory=".password.BCRYPTPasswordManager"
        />
+    <!--
+    Also install it under the same name as z3c.bcrypt does
+    for compatibility with that library.
+    -->
+    <utility
+       zcml:condition="not-installed z3c.bcrypt"
+       name="bcrypt"
+       provides=".interfaces.IMatchingPasswordManager"
+       factory=".password.BCRYPTPasswordManager"
+       />
   </configure>
 
   <configure zcml:condition="installed crypt">
diff --git a/src/zope/password/legacy.py b/src/zope/password/legacy.py
index 703d02b..5721755 100644
--- a/src/zope/password/legacy.py
+++ b/src/zope/password/legacy.py
@@ -32,98 +32,97 @@ _encoder = getencoder("utf-8")
 PY2 = sys.version_info[0] == 2
 
 
-if crypt is not None:
-    @implementer(IMatchingPasswordManager)
-    class CryptPasswordManager(object):
-        """Crypt password manager.
+@implementer(IMatchingPasswordManager)
+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.
+    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
-        >>> from zope.password.interfaces import IMatchingPasswordManager
-        >>> from zope.password.legacy import CryptPasswordManager
+    >>> from zope.interface.verify import verifyObject
+    >>> from zope.password.interfaces import IMatchingPasswordManager
+    >>> from zope.password.legacy import CryptPasswordManager
 
-        >>> manager = CryptPasswordManager()
-        >>> verifyObject(IMatchingPasswordManager, manager)
-        True
+    >>> manager = CryptPasswordManager()
+    >>> verifyObject(IMatchingPasswordManager, 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
+    >>> 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
 
-        Note that this object fails to return bytes from the ``encodePassword``
-        function on Python 3:
+    Note that this object fails to return bytes from the ``encodePassword``
+    function on Python 3:
 
-        >>> isinstance(encoded, str)
-        True
+    >>> isinstance(encoded, str)
+    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:
+    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
+    >>> manager.checkPassword(encoded, password + u"wrong")
+    True
 
-        Using a completely different password is rejected as expected:
+    Using a completely different password is rejected as expected:
 
-        >>> manager.checkPassword(encoded, 'completely wrong')
-        False
+    >>> manager.checkPassword(encoded, 'completely wrong')
+    False
 
-        Using the `openssl passwd` command-line utility to encode ``secret``,
-        we get ``erz50QD3gv4Dw`` as seeded hash.
+    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:
+    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'
+    >>> 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.checkPassword(encoded, password)
+    True
+    >>> manager.checkPassword(encoded, password + u"wrong")
+    False
 
-        >>> manager.encodePassword(password) != manager.encodePassword(password)
-        True
+    >>> manager.encodePassword(password) != manager.encodePassword(password)
+    True
 
-        The manager only claims to implement CRYPT encodings, anything not
-        starting with the string {CRYPT} returns False:
+    The manager only claims to implement CRYPT encodings, anything not
+    starting with the string {CRYPT} returns False:
 
-        >>> manager.match('{MD5}someotherhash')
-        False
+    >>> manager.match('{MD5}someotherhash')
+    False
 
-        """
+    """
 
 
-        def encodePassword(self, password, salt=None):
-            if salt is None:
-                choices = ("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
-                           "abcdefghijklmnopqrstuvwxyz"
-                           "0123456789./")
-                salt = choice(choices) + choice(choices)
-            if PY2:
-                # Py3: Python 2 can only handle ASCII for crypt.
-                password = _encoder(password)[0]
-            return '{CRYPT}%s' % crypt(password, salt)
+    def encodePassword(self, password, salt=None):
+        if salt is None:
+            choices = ("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                       "abcdefghijklmnopqrstuvwxyz"
+                       "0123456789./")
+            salt = choice(choices) + choice(choices)
+        if PY2:
+            # Py3: Python 2 can only handle ASCII for crypt.
+            password = _encoder(password)[0]
+        return '{CRYPT}%s' % crypt(password, salt)
 
-        def checkPassword(self, encoded_password, password):
-            return encoded_password == self.encodePassword(password,
-                                                           encoded_password[7:9])
+    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}')
+    def match(self, encoded_password):
+        return encoded_password.startswith('{CRYPT}')
 
 
 @implementer(IMatchingPasswordManager)
diff --git a/src/zope/password/password.py b/src/zope/password/password.py
index c544927..6fe6b01 100644
--- a/src/zope/password/password.py
+++ b/src/zope/password/password.py
@@ -23,6 +23,8 @@ from codecs import getencoder
 from hashlib import md5, sha1
 from os import urandom
 
+import re
+
 try:
     import bcrypt
 except ImportError: # pragma: no cover
@@ -501,6 +503,10 @@ class BCRYPTPasswordManager(PlainTextPasswordManager):
     """
     BCRYPT password manager.
 
+    In addition to the passwords encoded by this class,
+    this class can also recognize passwords encoded by :mod:`z3c.bcrypt`
+    and properly match and check them.
+
     .. note:: This uses the :mod:`bcrypt` library in its
         implementation, which `only uses the first 72 characters
         <https://pypi.python.org/pypi/bcrypt/3.1.3#maximum-password-length>`_
@@ -508,6 +514,10 @@ class BCRYPTPasswordManager(PlainTextPasswordManager):
     """
 
     _prefix = b'{BCRYPT}'
+    # This is the same regex that z3c.bcrypt uses, via way of cryptacular
+    # The $2a$ is a prefix.
+    _z3c_bcrypt_syntax = re.compile(br'\$2a\$[0-9]{2}\$[./A-Za-z0-9]{53}')
+
 
     def _to_bytes(self, password, encoding):
         if not isinstance(password, bytes):
@@ -536,7 +546,10 @@ class BCRYPTPasswordManager(PlainTextPasswordManager):
         if not self.match(hashed_password):
             return False
         pw_bytes = self._clean_clear(clear_password)
-        pw_hash = hashed_password[len(self._prefix):]
+        pw_hash = hashed_password
+        if hashed_password.startswith(self._prefix):
+            pw_hash = hashed_password[len(self._prefix):]
+
         try:
             ok = bcrypt.checkpw(pw_bytes, pw_hash)
         except ValueError: # pragma: no cover
@@ -569,7 +582,8 @@ class BCRYPTPasswordManager(PlainTextPasswordManager):
         :rtype: bool
         :returns: True iif the password was hashed with this manager.
         """
-        return hashed_password.startswith(self._prefix)
+        return (hashed_password.startswith(self._prefix)
+                or self._z3c_bcrypt_syntax.match(hashed_password) is not None)
 
 
 # Simple registry

Support reading the format that z3c.bcrypt produces?
Now that we have a bcrypt password manager (and maybe a KDF manager, see #9) it might be nice if we could read the format for those two managers that z3c.bcrypt produces (they have distinct prefixes, just different from the prefixes our existing bcrypt manager uses). (I'm not 100% sure that the KDF would be compatible, but bcrypt should be.)

That way z3c.bcrypt can eventually be phased out, reducing the amount of dependencies for people that want bcrypt/kdf passwords. (The cryptacular library it uses doesn't seem to be as maintained as the bcrypt library this package uses.)
diff --git a/src/zope/password/testing.py b/src/zope/password/testing.py
index bb97a29..9cdbba9 100644
--- a/src/zope/password/testing.py
+++ b/src/zope/password/testing.py
@@ -15,28 +15,15 @@
 """
 __docformat__ = "reStructuredText"
 
-from zope.component import provideUtility
-from zope.schema.interfaces import IVocabularyFactory
-
-from zope.password.interfaces import IMatchingPasswordManager
-from zope.password.password import PlainTextPasswordManager
-from zope.password.password import MD5PasswordManager
-from zope.password.password import SMD5PasswordManager
-from zope.password.password import SHA1PasswordManager
-from zope.password.password import SSHAPasswordManager
-from zope.password.legacy import MySQLPasswordManager
-from zope.password.vocabulary import PasswordManagerNamesVocabulary
-
-try:
-    from zope.password.legacy import CryptPasswordManager
-except ImportError: # pragma: no cover
-    CryptPasswordManager = None
-
+import zope.password
+from zope.configuration import xmlconfig
 
 def setUpPasswordManagers():
     """Helper function for setting up password manager utilities for tests
 
     >>> from zope.component import getUtility
+    >>> from zope.password.interfaces import IMatchingPasswordManager
+    >>> from zope.schema.interfaces import IVocabularyFactory
     >>> setUpPasswordManagers()
 
     >>> getUtility(IMatchingPasswordManager, 'Plain Text')
@@ -58,8 +45,8 @@ def setUpPasswordManagers():
     ...     CryptPasswordManager = None
     ...     True
     ... else:
-    ...     from zope.password.legacy import CryptPasswordManager as cpm
-    ...     getUtility(IMatchingPasswordManager, 'Crypt') is cpm
+    ...     from zope.password.legacy import CryptPasswordManager
+    ...     getUtility(IMatchingPasswordManager, 'Crypt') is not None
     True
 
     >>> voc = getUtility(IVocabularyFactory, 'Password Manager Names')
@@ -83,16 +70,4 @@ def setUpPasswordManagers():
     True
 
     """
-    provideUtility(PlainTextPasswordManager(), IMatchingPasswordManager,
-                   'Plain Text')
-    provideUtility(SSHAPasswordManager(), IMatchingPasswordManager, 'SSHA')
-    provideUtility(MD5PasswordManager(), IMatchingPasswordManager, 'MD5')
-    provideUtility(SMD5PasswordManager(), IMatchingPasswordManager, 'SMD5')
-    provideUtility(SHA1PasswordManager(), IMatchingPasswordManager, 'SHA1')
-    provideUtility(MySQLPasswordManager(), IMatchingPasswordManager, 'MySQL')
-
-    if CryptPasswordManager is not None:
-        provideUtility(CryptPasswordManager, IMatchingPasswordManager, 'Crypt')
-
-    provideUtility(PasswordManagerNamesVocabulary,
-                   IVocabularyFactory, 'Password Manager Names')
+    xmlconfig.file('configure.zcml', zope.password)
diff --git a/src/zope/password/tests/test_password.py b/src/zope/password/tests/test_password.py
index cf11627..7323b15 100644
--- a/src/zope/password/tests/test_password.py
+++ b/src/zope/password/tests/test_password.py
@@ -20,6 +20,7 @@ import unittest
 
 import bcrypt
 
+from zope.component.testing import PlacelessSetup
 from zope.interface.verify import verifyObject
 
 from zope.password.interfaces import IMatchingPasswordManager
@@ -118,13 +119,58 @@ class TestBCRYPTPasswordManager(unittest.TestCase):
         self.assertTrue(pw_mgr.match(b'{BCRYPT}'))
 
 
+class TestZ3cBcryptCompatible(unittest.TestCase):
+
+    password = u"right \N{CYRILLIC CAPITAL LETTER A}"
+    z3c_encoded = b'$2a$10$dzfwtSW1sFx5Q.9/8.3dzOyvIBz6xu4Y00kJWZpOrQ1eH4amFtHP6'
+
+
+    def _make_one(self):
+        from zope.password.password import BCRYPTPasswordManager
+        return BCRYPTPasswordManager()
+
+    def test_checkPassword(self):
+        pw_mgr = self._make_one()
+        self.assertTrue(pw_mgr.checkPassword(self.z3c_encoded, self.password))
+        # Mess with the hashed password, should not match
+        encoded = self.z3c_encoded[:-1]
+        self.assertFalse(pw_mgr.checkPassword(encoded, self.password))
+
+    def test_match(self):
+        pw_mgr = self._make_one()
+        self.assertTrue(pw_mgr.match(self.z3c_encoded))
+
+
+class TestConfiguration(PlacelessSetup,
+                        unittest.TestCase):
+
+    def setUp(self):
+        from zope.configuration import xmlconfig
+        import zope.password
+        xmlconfig.file('configure.zcml', zope.password)
+
+    def test_crypt_utility_names(self):
+        from zope.password.password import BCRYPTPasswordManager
+        from zope.password.interfaces import IPasswordManager
+        from zope import component
+
+        self.assertIsInstance(component.getUtility(IPasswordManager, 'BCRYPT'),
+                              BCRYPTPasswordManager)
+        self.assertIsInstance(component.getUtility(IPasswordManager, 'bcrypt'),
+                              BCRYPTPasswordManager)
+
+
+
 def test_suite():
+    from zope.component.testing import setUp, tearDown
     suite = unittest.TestSuite((
         doctest.DocTestSuite('zope.password.password'),
         doctest.DocTestSuite('zope.password.legacy'),
         doctest.DocTestSuite(
             'zope.password.testing',
-            optionflags=doctest.ELLIPSIS),
-        ))
+            optionflags=doctest.ELLIPSIS,
+            setUp=setUp,
+            tearDown=tearDown),
+    ))
     suite.addTests(unittest.defaultTestLoader.loadTestsFromName(__name__))
     return suite
diff --git a/src/zope/password/tests/test_zpasswd.py b/src/zope/password/tests/test_zpasswd.py
index 3c33f66..a3d92ab 100644
--- a/src/zope/password/tests/test_zpasswd.py
+++ b/src/zope/password/tests/test_zpasswd.py
@@ -41,7 +41,10 @@ class TestBase(unittest.TestCase):
                                          suffix=".zcml",
                                          delete=False) as f:
             f.write(
-                b'<configure xmlns="http://namespaces.zope.org/zope"/>\n')
+                b"""<configure xmlns="http://namespaces.zope.org/zope">
+                  <include file="configure.zcml" package="zope.password" />
+                </configure>
+                """)
             self.config = f.name
             self.addCleanup(os.remove, f.name)
 

pip install -e .[test]
parse_log_pytest
pytest
coverage pytest
3.6
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs @ file:///opt/conda/conda-bld/attrs_1642510447205/work
bcrypt==4.0.1
certifi==2021.5.30
coverage==6.2
importlib-metadata @ file:///tmp/build/80754af9/importlib-metadata_1631916693255/work
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
more-itertools @ file:///tmp/build/80754af9/more-itertools_1637733554872/work
packaging @ file:///tmp/build/80754af9/packaging_1637314298585/work
pluggy @ file:///tmp/build/80754af9/pluggy_1615976315926/work
py @ file:///opt/conda/conda-bld/py_1644396412707/work
pyparsing @ file:///tmp/build/80754af9/pyparsing_1635766073266/work
pytest==6.2.4
six==1.17.0
toml @ file:///tmp/build/80754af9/toml_1616166611790/work
typing_extensions @ file:///opt/conda/conda-bld/typing_extensions_1647553014482/work
zipp @ file:///tmp/build/80754af9/zipp_1633618647012/work
zope.component==5.1.0
zope.configuration==4.4.1
zope.event==4.6
zope.exceptions==4.6
zope.hookable==5.4
zope.i18nmessageid==5.1.1
zope.interface==5.5.2
-e git+https://github.com/zopefoundation/zope.password.git@f18b5f9371ceb5e7af68c917f2ae9f7de3d91cad#egg=zope.password
zope.schema==6.2.1
zope.testing==5.0.1
zope.testrunner==5.6

name: zope.password
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - attrs=21.4.0=pyhd3eb1b0_0
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - importlib-metadata=4.8.1=py36h06a4308_0
  - importlib_metadata=4.8.1=hd3eb1b0_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - more-itertools=8.12.0=pyhd3eb1b0_0
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - packaging=21.3=pyhd3eb1b0_0
  - pip=21.2.2=py36h06a4308_0
  - pluggy=0.13.1=py36h06a4308_0
  - py=1.11.0=pyhd3eb1b0_0
  - pyparsing=3.0.4=pyhd3eb1b0_0
  - pytest=6.2.4=py36h06a4308_2
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - toml=0.10.2=pyhd3eb1b0_0
  - typing_extensions=4.1.1=pyh06a4308_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zipp=3.6.0=pyhd3eb1b0_0
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - bcrypt==4.0.1
      - coverage==6.2
      - six==1.17.0
      - zope-component==5.1.0
      - zope-configuration==4.4.1
      - zope-event==4.6
      - zope-exceptions==4.6
      - zope-hookable==5.4
      - zope-i18nmessageid==5.1.1
      - zope-interface==5.5.2
      - zope-schema==6.2.1
      - zope-testing==5.0.1
      - zope-testrunner==5.6
prefix: /opt/conda/envs/zope.password

src/zope/password/tests/test_password.py::TestZ3cBcryptCompatible::test_checkPassword src/zope/password/tests/test_password.py::TestZ3cBcryptCompatible::test_match src/zope/password/tests/test_password.py::TestConfiguration::test_crypt_utility_names src/zope/password/tests/test_zpasswd.py::ArgumentParsingTestCase::test_config_long src/zope/password/tests/test_zpasswd.py::ArgumentParsingTestCase::test_config_short
src/zope/password/tests/test_password.py::TestBCRYPTPasswordManager::test_checkPassword src/zope/password/tests/test_password.py::TestBCRYPTPasswordManager::test_encodePassword_with_different_salts src/zope/password/tests/test_password.py::TestBCRYPTPasswordManager::test_encodePassword_with_no_salt src/zope/password/tests/test_password.py::TestBCRYPTPasswordManager::test_encodePassword_with_same_salt src/zope/password/tests/test_password.py::TestBCRYPTPasswordManager::test_encodePassword_with_unicode_salts src/zope/password/tests/test_password.py::TestBCRYPTPasswordManager::test_interface_compliance src/zope/password/tests/test_password.py::TestBCRYPTPasswordManager::test_match src/zope/password/tests/test_password.py::test_suite src/zope/password/tests/test_zpasswd.py::ArgumentParsingTestCase::test_destination_long src/zope/password/tests/test_zpasswd.py::ArgumentParsingTestCase::test_destination_short src/zope/password/tests/test_zpasswd.py::ArgumentParsingTestCase::test_help_long src/zope/password/tests/test_zpasswd.py::ArgumentParsingTestCase::test_help_short src/zope/password/tests/test_zpasswd.py::ArgumentParsingTestCase::test_main src/zope/password/tests/test_zpasswd.py::ArgumentParsingTestCase::test_no_arguments src/zope/password/tests/test_zpasswd.py::ArgumentParsingTestCase::test_too_many_arguments src/zope/password/tests/test_zpasswd.py::ArgumentParsingTestCase::test_version_long src/zope/password/tests/test_zpasswd.py::InputCollectionTestCase::test_principal_information src/zope/password/tests/test_zpasswd.py::TestDestination::test_principal_information src/zope/password/tests/test_zpasswd.py::TestRunAndApplication::test_exit src/zope/password/tests/test_zpasswd.py::TestRunAndApplication::test_get_passwd_empty src/zope/password/tests/test_zpasswd.py::TestRunAndApplication::test_get_passwd_spaces src/zope/password/tests/test_zpasswd.py::TestRunAndApplication::test_get_passwd_verify_fail src/zope/password/tests/test_zpasswd.py::TestRunAndApplication::test_get_password_manager_bad src/zope/password/tests/test_zpasswd.py::TestRunAndApplication::test_get_password_manager_default src/zope/password/tests/test_zpasswd.py::TestRunAndApplication::test_get_value src/zope/password/tests/test_zpasswd.py::TestRunAndApplication::test_keyboard_interrupt src/zope/password/tests/test_zpasswd.py::TestRunAndApplication::test_read_input src/zope/password/tests/test_zpasswd.py::TestRunAndApplication::test_read_password src/zope/password/tests/test_zpasswd.py::TestRunAndApplication::test_read_password_cancel src/zope/password/tests/test_zpasswd.py::test_suite
src/zope/password/password.py src/zope/password/configure.zcml src/zope/password/legacy.py CHANGES.rst
src/zope/password/password.py src/zope/password/configure.zcml src/zope/password/legacy.py CHANGES.rst