summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEli Collins <elic@assurancetechnologies.com>2011-07-08 16:12:53 -0400
committerEli Collins <elic@assurancetechnologies.com>2011-07-08 16:12:53 -0400
commit1cea29b9f5d957aca436031f770f9f27d505ae43 (patch)
treeea84595b809fae1b423bb3cb4bc393745ad6c1f8
parent4475fba1e1f6b5fc22283fcf4011c17dcd2f85f8 (diff)
downloadpasslib-1cea29b9f5d957aca436031f770f9f27d505ae43.tar.gz
stripped trailing whitespace from a bunch of files
-rw-r--r--LICENSE2
-rw-r--r--docs/lib/passlib.apps.rst6
-rw-r--r--docs/lib/passlib.hash.atlassian_pbkdf2_sha1.rst2
-rw-r--r--docs/lib/passlib.hash.cta_pbkdf2_sha1.rst2
-rw-r--r--docs/lib/passlib.hash.dlitz_pbkdf2_sha1.rst2
-rw-r--r--docs/lib/passlib.hash.fshp.rst12
-rw-r--r--docs/lib/passlib.hash.oracle10.rst2
-rw-r--r--docs/lib/passlib.utils.handlers.rst2
-rw-r--r--docs/lib/passlib.utils.pbkdf2.rst2
-rw-r--r--docs/modular_crypt_format.rst6
-rw-r--r--docs/new_app_quickstart.rst4
-rw-r--r--docs/password_hash_api.rst24
-rw-r--r--passlib/apache.py40
-rw-r--r--passlib/handlers/bcrypt.py6
-rw-r--r--passlib/handlers/fshp.py18
-rw-r--r--passlib/handlers/ldap_digests.py4
-rw-r--r--passlib/handlers/md5_crypt.py12
-rw-r--r--passlib/handlers/misc.py2
-rw-r--r--passlib/handlers/nthash.py2
-rw-r--r--passlib/handlers/sha2_crypt.py2
-rw-r--r--passlib/tests/test_apache.py12
-rw-r--r--passlib/tests/test_context.py2
-rw-r--r--passlib/tests/test_utils.py64
-rw-r--r--passlib/tests/test_win32.py4
-rw-r--r--passlib/utils/__init__.py66
-rw-r--r--passlib/utils/des.py12
-rw-r--r--passlib/utils/handlers.py14
-rw-r--r--passlib/utils/md4.py2
-rw-r--r--passlib/win32.py2
-rw-r--r--setup.cfg2
-rw-r--r--setup.py4
31 files changed, 168 insertions, 168 deletions
diff --git a/LICENSE b/LICENSE
index 998bbf3..87ec798 100644
--- a/LICENSE
+++ b/LICENSE
@@ -65,7 +65,7 @@ The source file ``passlib/utils/des.py`` contains code derived from
a pure-java implementation of the historic unix-crypt password hash algorithm.
It is available under the following license::
- UnixCrypt.java 0.9 96/11/25
+ UnixCrypt.java 0.9 96/11/25
Copyright (c) 1996 Aki Yoshida. All rights reserved.
Permission to use, copy, modify and distribute this software
for non-commercial or commercial purposes and without fee is
diff --git a/docs/lib/passlib.apps.rst b/docs/lib/passlib.apps.rst
index 1273094..b5da280 100644
--- a/docs/lib/passlib.apps.rst
+++ b/docs/lib/passlib.apps.rst
@@ -111,7 +111,7 @@ It is found in a wide range of PHP applications, including Drupal and Wordpress.
BCrypt is used as the default if support is available,
otherwise the Portable Hash will be used as the default.
-
+
.. versionchanged:: 1.5
Now uses Portable Hash as fallback if BCrypt isn't available.
Previously used BSDI-Crypt as fallback
@@ -163,8 +163,8 @@ The following contexts are available for reading Roundup password hash fields:
.. data:: roundup15_context
Roundup 1.4.17 adds support for :class:`~passlib.hash.ldap_pbkdf2_sha1`
- as it's preferred hash format.
- This context supports all the :data:`roundup10_context` hashes,
+ as it's preferred hash format.
+ This context supports all the :data:`roundup10_context` hashes,
but adds that hash as well (and uses it as the default).
.. data:: roundup_context
diff --git a/docs/lib/passlib.hash.atlassian_pbkdf2_sha1.rst b/docs/lib/passlib.hash.atlassian_pbkdf2_sha1.rst
index d6edadb..a2a0a05 100644
--- a/docs/lib/passlib.hash.atlassian_pbkdf2_sha1.rst
+++ b/docs/lib/passlib.hash.atlassian_pbkdf2_sha1.rst
@@ -4,7 +4,7 @@
.. index::
pair: atlassian; pbkdf2 hash
-
+
.. currentmodule:: passlib.hash
This class provides an implementation of
diff --git a/docs/lib/passlib.hash.cta_pbkdf2_sha1.rst b/docs/lib/passlib.hash.cta_pbkdf2_sha1.rst
index 2814764..526c408 100644
--- a/docs/lib/passlib.hash.cta_pbkdf2_sha1.rst
+++ b/docs/lib/passlib.hash.cta_pbkdf2_sha1.rst
@@ -15,7 +15,7 @@ variable length salts, variable number of rounds.
:doc:`passlib.hash.pbkdf2_digest <passlib.hash.pbkdf2_digest>`
for some other PBKDF2-based hashes.
-
+
:doc:`passlib.hash.dlitz_pbkdf2_sha1 <passlib.hash.dlitz_pbkdf2_sha1>`
for another hash which looks almost exactly like this one.
diff --git a/docs/lib/passlib.hash.dlitz_pbkdf2_sha1.rst b/docs/lib/passlib.hash.dlitz_pbkdf2_sha1.rst
index 9967385..3151b23 100644
--- a/docs/lib/passlib.hash.dlitz_pbkdf2_sha1.rst
+++ b/docs/lib/passlib.hash.dlitz_pbkdf2_sha1.rst
@@ -15,7 +15,7 @@ variable length salts, variable number of rounds.
:doc:`passlib.hash.pbkdf2_digest <passlib.hash.pbkdf2_digest>`
for some other PBKDF2-based hashes.
-
+
:doc:`passlib.hash.cta_pbkdf2_sha1 <passlib.hash.cta_pbkdf2_sha1>`
for another hash which looks almost exactly like this one.
diff --git a/docs/lib/passlib.hash.fshp.rst b/docs/lib/passlib.hash.fshp.rst
index 69a8c4e..95f52fb 100644
--- a/docs/lib/passlib.hash.fshp.rst
+++ b/docs/lib/passlib.hash.fshp.rst
@@ -3,10 +3,10 @@
==========================================================
.. index:: fshp
-
+
.. currentmodule:: passlib.hash
-The Fairly Secure Hashed Password (FSHP) scheme [#home]_
+The Fairly Secure Hashed Password (FSHP) scheme [#home]_
is a cross-platform hash based on PBKDF1 [#pbk]_, and uses an LDAP-style hash format.
It features a variable length salt, variable rounds, and support for cryptographic
hashes from SHA-1 up to SHA-512.
@@ -26,7 +26,7 @@ as well as a special digest keyword for selecting the variant of FSHP to use.
This class can be used directly as follows::
>>> from passlib.hash import fshp
-
+
>>> #generate new salt, encrypt password
>>> h = fshp.encrypt("password")
>>> h
@@ -72,7 +72,7 @@ A example hash (of ``password``) is:
* :samp:`<rounds>` is a decimal integer identifying the number
of rounds to apply when calculating the checksum (see below).
``16384`` in the example.
-
+
* :samp:`<data>` is a base64-encoded string which, when decoded,
contains a salt string of the specified size, followed
by the checksum. In the example, the data portion decodes to
@@ -89,7 +89,7 @@ The checksum is calculated using :func:`~passlib.utils.pbkdf2.pbkdf1`,
passing in the password, the decoded salt string, the number of
rounds, and hash function specified by the variant identifier.
FSHP has one quirk in that the password is passed in as the pbkdf1 salt,
-and the salt is passed in as the pbkdf1 password.
+and the salt is passed in as the pbkdf1 password.
Security Issues
===============
@@ -97,7 +97,7 @@ Security Issues
from what is described in the PBKDF1 standard.
This issue is mainly noted in order to dismiss it:
while the swap permits an attacker to pre-calculate part of the initial digest,
- the impact of this is negligible when a large number of rounds is used.
+ the impact of this is negligible when a large number of rounds is used.
* Since PBKDF1 is based on repeated composition of a hash,
it is vulnerable to any first-preimage attacks on the underlying hash.
diff --git a/docs/lib/passlib.hash.oracle10.rst b/docs/lib/passlib.hash.oracle10.rst
index 832082f..06941ff 100644
--- a/docs/lib/passlib.hash.oracle10.rst
+++ b/docs/lib/passlib.hash.oracle10.rst
@@ -112,7 +112,7 @@ References
PassLib uses ``utf-16-be``, as this is both compatible with existing test vectors
and supports unicode input.
-.. [#flaws] Whitepaper analyzing flaws in this algorithm -
+.. [#flaws] Whitepaper analyzing flaws in this algorithm -
`<http://www.isg.rhul.ac.uk/~ccid/publications/oracle_passwd.pdf>`_.
.. [#] Description of Oracle10g and Oracle11g algorithms -
diff --git a/docs/lib/passlib.utils.handlers.rst b/docs/lib/passlib.utils.handlers.rst
index 4670fe7..301448c 100644
--- a/docs/lib/passlib.utils.handlers.rst
+++ b/docs/lib/passlib.utils.handlers.rst
@@ -103,7 +103,7 @@ Some additional notes:
For faster identification purposes, subclasses may fill in the :attr:`~GenericHandler.ident` attribute
with the hash's identifying prefix, which :meth:`~GenericHandler.identify` will then test for
instead of calling :meth:`~GenericHandler.from_string`.
- For more complex situations, a custom implementation should be used;
+ For more complex situations, a custom implementation should be used;
the :class:`HasManyIdents` mixin may also be helpful.
* This class does not support context kwds of any type,
diff --git a/docs/lib/passlib.utils.pbkdf2.rst b/docs/lib/passlib.utils.pbkdf2.rst
index d4fb46e..7e7904d 100644
--- a/docs/lib/passlib.utils.pbkdf2.rst
+++ b/docs/lib/passlib.utils.pbkdf2.rst
@@ -27,6 +27,6 @@ Helper Functions
================
.. autofunction:: get_prf
-..
+..
given how this module is expanding in scope,
perhaps it should be renamed "kdf"?
diff --git a/docs/modular_crypt_format.rst b/docs/modular_crypt_format.rst
index b91ab51..da01d9c 100644
--- a/docs/modular_crypt_format.rst
+++ b/docs/modular_crypt_format.rst
@@ -123,7 +123,7 @@ Identifiers & Platform Support
==============================
The following table lists of all the major MCF hashes supported by passlib,
-and indicates which operating systems offer native support.
+and indicates which operating systems offer native support.
==================================== ==================== =========== =========== =========== =========== =======
Scheme Prefix Linux FreeBSD NetBSD OpenBSD Solaris
@@ -136,7 +136,7 @@ Scheme Prefix Linux FreeBSD
:class:`~passlib.hash.nthash` ``$3$`` y
:class:`~passlib.hash.sha256_crypt` ``$5$`` y y
:class:`~passlib.hash.sha512_crypt` ``$6$`` y y
-:class:`~passlib.hash.sha1_crypt` ``$sha1$`` y
+:class:`~passlib.hash.sha1_crypt` ``$sha1$`` y
==================================== ==================== =========== =========== =========== =========== =======
The following table lists the other MCF hashes supported by passlib,
@@ -159,4 +159,4 @@ Scheme Prefix Known Uses
the same identifier. They can be distinguished
by the fact that cta hashes will always end in ``=``, while dlitz
hashes contain no ``=`` at all.
- \ No newline at end of file
+
diff --git a/docs/new_app_quickstart.rst b/docs/new_app_quickstart.rst
index b86042f..622bfa8 100644
--- a/docs/new_app_quickstart.rst
+++ b/docs/new_app_quickstart.rst
@@ -53,14 +53,14 @@ All of these hashes share the following properties:
* no known vulnerabilties.
* based on documented & widely reviewed algorithms.
- * basic architecture has been under heavy scrutiny and use for at least 10 years.
+ * basic architecture has been under heavy scrutiny and use for at least 10 years.
* public-domain or BSD-licensed reference implementations available.
* in use across a number of OSes and/or a wide variety of applications.
* variable rounds for configuring flexible cpu cost on a per-hash basis.
* at least 96 bits of salt.
The following comparison should help you choose which hash is
-most appropriate for your application.
+most appropriate for your application.
BCrypt
------
diff --git a/docs/password_hash_api.rst b/docs/password_hash_api.rst
index fe3b499..ae929b6 100644
--- a/docs/password_hash_api.rst
+++ b/docs/password_hash_api.rst
@@ -114,7 +114,7 @@ Required Attributes
``salt``
If present, this means the algorithm contains some number of bits of salt
- which should vary with every new hash created.
+ which should vary with every new hash created.
Additionally, this means
:meth:`~PasswordHash.genconfig` and :meth:`~PasswordHash.encrypt`
@@ -239,7 +239,7 @@ which scheme a hash belongs to when multiple schemes are in use.
:func:`~PasswordHash.verify` or :func:`~PasswordHash.genhash`.
Because of this, applications should rely on this method only for identification,
not confirmation that a hash is correctly formed.
-
+
.. classmethod:: PasswordHash.verify(secret, hash, \*\*context_kwds)
verify a secret against an existing hash.
@@ -453,7 +453,7 @@ the following attributes are usually exposed.
string containing list of all characters which are allowed
to be specified in salt parameter.
for most hashes, this is equal to :data:`passlib.utils.h64.CHARS`.
-
+
this must be a unicode string if the salt is encoded,
or (rarely) bytes if the salt is unencoded raw bytes.
@@ -497,7 +497,7 @@ For the application developer in a hurry:
While they may be provided as :class:`bytes`,
in that case it is strongly suggested
they be encoded using ``utf-8`` or ``ascii``.
-
+
* Passlib will always return hashes as native python strings.
This means :class:`unicode` under Python 3,
and ``ascii``-encoded :class:`bytes` under Python 2.
@@ -525,23 +525,23 @@ the following issues:
For handlers implementing such hashes,
passwords provided as :class:`unicode` should be encoded to ``utf-8``,
and passwords provided as :class:`bytes` should be treated as opaque.
-
+
A few of these hashes officially specify this behavior;
the rest have no preferred encoding at all,
so this was chosen as a sensible standard behavior.
Unless the underlying algorithm specifies an alternate policy,
handlers should always encode unicode to ``utf-8``.
-
+
* Because of the above behavior for :class:`unicode` inputs,
applications which encode their passwords are urged
to use ``utf-8`` or ``ascii``,
so that hashes they generate with encoded bytes
will verify correctly if/when they start using unicode.
-
+
Applications which need to verify existing hashes
using an alternate encoding such as ``latin-1``
- should be wary of this future "gotcha".
-
+ should be wary of this future "gotcha".
+
* A few hashes operate on :class:`unicode` strings instead.
For handlers implementing such hashes:
passwords provided as :class:`unicode` should be handled as appropriate,
@@ -568,7 +568,7 @@ by design requirements, and more by compatibility
and ease of implementation issues:
* Handlers should accept hashes as either :class:`unicode` or
- as ``ascii``-encoded :class:`bytes`.
+ as ``ascii``-encoded :class:`bytes`.
This behavior allows applications to provide hashes
as unicode or as bytes, as they please; making
@@ -587,12 +587,12 @@ and ease of implementation issues:
* Handlers should return hashes as native python strings.
This means :class:`unicode` under Python 3,
and ``ascii``-encoded :class:`bytes` under Python 2.
-
+
This behavior was chosen to fit with Python 3's
unicode-oriented philosophy, while retaining
backwards compatibility with Passlib 1.4 and earlier
under Python 2.
-
+
Handlers should use the :func:`passlib.utils.to_hash_str` function
to coerce their unicode hashes to whatever is appropriate
for the platform before returning them.
diff --git a/passlib/apache.py b/passlib/apache.py
index 55786e1..0069ef3 100644
--- a/passlib/apache.py
+++ b/passlib/apache.py
@@ -83,7 +83,7 @@ class _CommonFile(object):
entry_map = self._entry_map = {}
for line in lines:
#XXX: found mention that "#" comment lines may be supported by htpasswd,
- # should verify this.
+ # should verify this.
key, value = pl(line)
if key in entry_map:
#XXX: should we use data from first entry, or last entry?
@@ -155,7 +155,7 @@ class _CommonFile(object):
def _encode_ident(self, ident, errname="user/realm"):
"ensure identifier is bytes encoded using specified encoding, or rejected"
- encoding = self.encoding
+ encoding = self.encoding
if encoding:
if isinstance(ident, unicode):
return ident.encode(encoding)
@@ -179,7 +179,7 @@ class _CommonFile(object):
#FIXME: htpasswd doc sez passwords limited to 255 chars under Windows & MPE,
# longer ones are truncated. may be side-effect of those platforms
# supporting plaintext. we don't currently check for this.
-
+
#=========================================================
#htpasswd editing
#=========================================================
@@ -217,29 +217,29 @@ class HtpasswdFile(_CommonFile):
if set to ``None``,
user names must be specified as bytes,
and will be returned as bytes.
-
+
if set to an encoding,
user names must be specified as unicode,
and will be returned as unicode.
when stored, then will use the specified encoding.
-
+
for backwards compatibility with passlib 1.4,
this defaults to ``None`` under Python 2,
and ``utf-8`` under Python 3.
-
+
.. note::
this is not the encoding for the entire file,
just for the usernames within the file.
this must be an encoding which is compatible
- with 7-bit ascii (which is used by rest of file).
+ with 7-bit ascii (which is used by rest of file).
:param context:
:class:`~passlib.context.CryptContext` instance used to handle
hashes in this file.
-
+
.. warning::
-
+
this should usually be left at the default,
though it can be overridden to implement non-standard hashes
within the htpasswd file.
@@ -280,7 +280,7 @@ class HtpasswdFile(_CommonFile):
def _render_line(self, user, hash):
return render_bytes("%s:%s\n", user, hash)
-
+
def users(self):
"return list of all users in file"
return map(self._decode_ident, self._entry_order)
@@ -335,16 +335,16 @@ class HtdigestFile(_CommonFile):
:param encoding:
optionally specify encoding used for usernames / realms.
-
+
if set to ``None``,
user names & realms must be specified as bytes,
and will be returned as bytes.
-
+
if set to an encoding,
user names & realms must be specified as unicode,
and will be returned as unicode.
when stored, then will use the specified encoding.
-
+
for backwards compatibility with passlib 1.4,
this defaults to ``None`` under Python 2,
and ``utf-8`` under Python 3.
@@ -354,7 +354,7 @@ class HtdigestFile(_CommonFile):
this is not the encoding for the entire file,
just for the usernames & realms within the file.
this must be an encoding which is compatible
- with 7-bit ascii (which is used by rest of file).
+ with 7-bit ascii (which is used by rest of file).
Loading & Saving
================
@@ -389,16 +389,16 @@ class HtdigestFile(_CommonFile):
# until it causes problems - in which case stopgap of setting this attr
# per-instance can be used.
password_encoding = "utf-8"
-
- #XXX: provide rename() & rename_realm() ?
-
+
+ #XXX: provide rename() & rename_realm() ?
+
def _parse_line(self, line):
user, realm, hash = line.rstrip().split(BCOLON)
return (user, realm), hash
def _render_line(self, key, hash):
return render_bytes("%s:%s:%s\n", key[0], key[1], hash)
-
+
#TODO: would frontend to calc_digest be useful?
##def encrypt(self, password, user, realm):
## user = self._norm_user(user)
@@ -463,7 +463,7 @@ class HtdigestFile(_CommonFile):
def find(self, user, realm):
"""return digest hash for specified user+realm; returns ``None`` if not found
-
+
:returns: htdigest hash or None
:rtype: bytes or None
"""
@@ -474,7 +474,7 @@ class HtdigestFile(_CommonFile):
#decode hash if in unicode mode
hash = hash.decode("ascii")
return hash
-
+
def verify(self, user, realm, password):
"""verify password for specified user + realm.
diff --git a/passlib/handlers/bcrypt.py b/passlib/handlers/bcrypt.py
index 5ced795..a363ab6 100644
--- a/passlib/handlers/bcrypt.py
+++ b/passlib/handlers/bcrypt.py
@@ -135,7 +135,7 @@ class bcrypt(uh.HasManyIdents, uh.HasRounds, uh.HasSalt, uh.HasManyBackends, uh.
@classproperty
def _has_backend_bcryptor(cls):
return bcryptor_engine is not None
-
+
@classproperty
def _has_backend_os_crypt(cls):
return (
@@ -181,11 +181,11 @@ class bcrypt(uh.HasManyIdents, uh.HasRounds, uh.HasSalt, uh.HasManyBackends, uh.
# returns ascii bytes
# py3: can't get to install
if isinstance(secret, unicode):
- secret = secret.encode("utf-8")
+ secret = secret.encode("utf-8")
hash = bcryptor_engine(False).hash_key(secret,
self.to_string(native=False))
return hash[-31:].decode("ascii")
-
+
#=========================================================
#eoc
#=========================================================
diff --git a/passlib/handlers/fshp.py b/passlib/handlers/fshp.py
index 91e064a..edf58e1 100644
--- a/passlib/handlers/fshp.py
+++ b/passlib/handlers/fshp.py
@@ -31,7 +31,7 @@ class fshp(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
:param salt:
Optional raw salt string.
If not specified, one will be autogenerated (this is recommended).
-
+
:param salt_size:
Optional number of bytes to use when autogenerating new salts.
Defaults to 16 bytes, but can be any non-negative value.
@@ -39,15 +39,15 @@ class fshp(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
:param rounds:
Optional number of rounds to use.
Defaults to 40000, must be between 1 and 4294967295, inclusive.
-
+
:param variant:
Optionally specifies variant of FSHP to use.
-
+
* ``0`` - uses SHA-1 digest (deprecated)
* ``1`` - uses SHA-2/256 digest (default)
* ``2`` - uses SHA-2/384 digest
* ``3`` - uses SHA-2/512 digest
-
+
Aliases ``sha1``, ``sha256`` etc are also allowed.
"""
@@ -88,7 +88,7 @@ class fshp(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
#instance attrs
#=========================================================
variant = None
-
+
#=========================================================
#init
#=========================================================
@@ -124,17 +124,17 @@ class fshp(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
@property
def _info(self):
return self._variant_info[self.variant]
-
+
#=========================================================
#formatting
#=========================================================
-
+
@classmethod
def identify(cls, hash):
- return uh.identify_prefix(hash, u"{FSHP")
+ return uh.identify_prefix(hash, u"{FSHP")
_fshp_re = re.compile(ur"^\{FSHP(\d+)\|(\d+)\|(\d+)\}([a-zA-Z0-9+/]+={0,3})$")
-
+
@classmethod
def from_string(cls, hash):
if not hash:
diff --git a/passlib/handlers/ldap_digests.py b/passlib/handlers/ldap_digests.py
index 2a7d826..148e288 100644
--- a/passlib/handlers/ldap_digests.py
+++ b/passlib/handlers/ldap_digests.py
@@ -90,10 +90,10 @@ class _SaltedBase64DigestHelper(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHand
return cls(checksum=chk, salt=salt, strict=True)
def to_string(self):
- data = (self.checksum or self._stub_checksum) + self.salt
+ data = (self.checksum or self._stub_checksum) + self.salt
hash = self.ident + b64encode(data).decode("ascii")
return to_hash_str(hash)
-
+
def calc_checksum(self, secret):
if secret is None:
raise TypeError("no secret provided")
diff --git a/passlib/handlers/md5_crypt.py b/passlib/handlers/md5_crypt.py
index 74968c2..585d478 100644
--- a/passlib/handlers/md5_crypt.py
+++ b/passlib/handlers/md5_crypt.py
@@ -27,17 +27,17 @@ B_APR_MAGIC = b("$apr1$")
def raw_md5_crypt(secret, salt, apr=False):
"""perform raw md5-crypt calculation
-
+
:arg secret:
password, bytes or unicode (encoded to utf-8)
-
+
:arg salt:
salt portion of hash, bytes or unicode (encoded to ascii),
clipped to max 8 bytes.
-
+
:param apr:
flag to use apache variant
-
+
:returns:
encoded checksum as unicode
"""
@@ -216,13 +216,13 @@ class md5_crypt(uh.HasManyBackends, _Md5Common):
#=========================================================
name = "md5_crypt"
ident = u"$1$"
-
+
#=========================================================
#primary interface
#=========================================================
#FIXME: can't find definitive policy on how md5-crypt handles non-ascii.
# all backends currently coerce -> utf-8
-
+
backends = ("os_crypt", "builtin")
_has_backend_builtin = True
diff --git a/passlib/handlers/misc.py b/passlib/handlers/misc.py
index 7357adf..80910af 100644
--- a/passlib/handlers/misc.py
+++ b/passlib/handlers/misc.py
@@ -61,7 +61,7 @@ class plaintext(uh.StaticHandler):
Unicode passwords will be encoded using utf-8.
"""
name = "plaintext"
-
+
@classmethod
def identify(cls, hash):
return hash is not None
diff --git a/passlib/handlers/nthash.py b/passlib/handlers/nthash.py
index f761305..e8e65a4 100644
--- a/passlib/handlers/nthash.py
+++ b/passlib/handlers/nthash.py
@@ -80,7 +80,7 @@ class nthash(uh.HasManyIdents, uh.GenericHandler):
@staticmethod
def raw_nthash(secret, hex=False):
"""encode password using md4-based NTHASH algorithm
-
+
:returns:
returns string of raw bytes if ``hex=False``,
returns digest as hexidecimal unicode if ``hex=True``.
diff --git a/passlib/handlers/sha2_crypt.py b/passlib/handlers/sha2_crypt.py
index 914d955..80308a4 100644
--- a/passlib/handlers/sha2_crypt.py
+++ b/passlib/handlers/sha2_crypt.py
@@ -335,7 +335,7 @@ class sha256_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandl
def _calc_checksum_builtin(self, secret):
if isinstance(secret, unicode):
- secret = secret.encode("utf-8")
+ secret = secret.encode("utf-8")
checksum, salt, rounds = raw_sha256_crypt(secret,
self.salt.encode("ascii"),
self.rounds)
diff --git a/passlib/tests/test_apache.py b/passlib/tests/test_apache.py
index 6fce388..984d2b7 100644
--- a/passlib/tests/test_apache.py
+++ b/passlib/tests/test_apache.py
@@ -40,7 +40,7 @@ def backdate_file_mtime(path, offset=10):
class HtpasswdFileTest(TestCase):
"test HtpasswdFile class"
case_prefix = "HtpasswdFile"
-
+
sample_01 = b('user2:2CHkkwa2AtqGs\nuser3:{SHA}3ipNV1GrBtxPmHFC21fCbVCSXIo=\nuser4:pass4\nuser1:$apr1$t4tc7jTh$GPIWVUo8sQKJlUdV8V5vu0\n')
sample_02 = b('user3:{SHA}3ipNV1GrBtxPmHFC21fCbVCSXIo=\nuser4:pass4\n')
sample_03 = b('user2:pass2x\nuser3:{SHA}3ipNV1GrBtxPmHFC21fCbVCSXIo=\nuser4:pass4\nuser1:$apr1$t4tc7jTh$GPIWVUo8sQKJlUdV8V5vu0\nuser5:pass5\n')
@@ -152,7 +152,7 @@ class HtpasswdFileTest(TestCase):
set_file(path, self.sample_dup)
hc = apache.HtpasswdFile(path)
self.assert_(hc.verify('user1','pass1'))
-
+
def test_06_save(self):
"test save()"
#load from file
@@ -182,11 +182,11 @@ class HtpasswdFileTest(TestCase):
#check users() returns native string by default
ht = apache.HtpasswdFile(path)
self.assertIsInstance(ht.users()[0], native_str)
-
+
#check returns unicode if encoding explicitly set
ht = apache.HtpasswdFile(path, encoding="utf-8")
self.assertIsInstance(ht.users()[0], unicode)
-
+
#check returns bytes if encoding explicitly disabled
ht = apache.HtpasswdFile(path, encoding=None)
self.assertIsInstance(ht.users()[0], bytes)
@@ -371,12 +371,12 @@ class HtdigestFileTest(TestCase):
ht = apache.HtdigestFile(path)
self.assertIsInstance(ht.realms()[0], native_str)
self.assertIsInstance(ht.users("realm")[0], native_str)
-
+
#check returns unicode if encoding explicitly set
ht = apache.HtdigestFile(path, encoding="utf-8")
self.assertIsInstance(ht.realms()[0], unicode)
self.assertIsInstance(ht.users(u"realm")[0], unicode)
-
+
#check returns bytes if encoding explicitly disabled
ht = apache.HtdigestFile(path, encoding=None)
self.assertIsInstance(ht.realms()[0], bytes)
diff --git a/passlib/tests/test_context.py b/passlib/tests/test_context.py
index 68afcbe..42f42c4 100644
--- a/passlib/tests/test_context.py
+++ b/passlib/tests/test_context.py
@@ -207,7 +207,7 @@ admin.sha512_crypt.max_rounds = 40000
policy = CryptPolicy.from_string(self.sample_config_4s)
self.assertEquals(policy.to_dict(), self.sample_config_4pd)
-
+
#test with custom encoding
uc2 = to_bytes(self.sample_config_1s, "utf-16", source_encoding="utf-8")
policy = CryptPolicy.from_string(uc2, encoding="utf-16")
diff --git a/passlib/tests/test_utils.py b/passlib/tests/test_utils.py
index 8a9d1bb..e432ee1 100644
--- a/passlib/tests/test_utils.py
+++ b/passlib/tests/test_utils.py
@@ -84,7 +84,7 @@ class MiscTest(TestCase):
self.assertNotEqual(x,y)
#NOTE: decoding this due to py3 bytes
self.assertEqual(sorted(set(x.decode("ascii"))), [u'a',u'b',u'c'])
-
+
#generate_password
self.assertEqual(len(utils.generate_password(15)), 15)
@@ -110,7 +110,7 @@ class MiscTest(TestCase):
"test safe_os_crypt() wrapper"
if not safe_os_crypt:
raise self.SkipTest("stdlib crypt module not available")
-
+
#NOTE: this is assuming EVERY crypt will support des_crypt.
# if this fails on some platform, this test will need modifying.
@@ -119,18 +119,18 @@ class MiscTest(TestCase):
self.assertTrue(ok)
self.assertIsInstance(hash, unicode)
self.assertEqual(hash, u'aaqPiZY5xR5l.')
-
+
#test hash-as-bytes
self.assertRaises(TypeError, safe_os_crypt, u'test', b('aa'))
-
+
#test password as ascii
ret = safe_os_crypt(b('test'), u'aa')
self.assertEqual(ret, (True, u'aaqPiZY5xR5l.'))
-
+
#test unicode password w/ high char
ret = safe_os_crypt(u'test\u1234', u'aa')
self.assertEqual(ret, (True, u'aahWwbrUsKZk.'))
-
+
#test utf-8 password w/ high char
ret = safe_os_crypt(b('test\xe1\x88\xb4'), u'aa')
self.assertEqual(ret, (True, u'aahWwbrUsKZk.'))
@@ -148,7 +148,7 @@ class MiscTest(TestCase):
#=========================================================
class CodecTest(TestCase):
"tests bytes/unicode helpers in passlib.utils"
-
+
def test_bytes(self):
"test b() helper, bytes and native_str types"
# Py2k #
@@ -166,14 +166,14 @@ class CodecTest(TestCase):
# Py3k #
#self.assertEqual(b('\x00\xff'), b"\x00\xff")
# end Py3k #
-
+
def test_to_bytes(self):
"test to_bytes()"
-
+
#check unicode inputs
self.assertEqual(to_bytes(u'abc'), b('abc'))
self.assertEqual(to_bytes(u'\x00\xff'), b('\x00\xc3\xbf'))
-
+
#check unicode w/ encodings
self.assertEqual(to_bytes(u'\x00\xff', 'latin-1'), b('\x00\xff'))
self.assertRaises(ValueError, to_bytes, u'\x00\xff', 'ascii')
@@ -186,28 +186,28 @@ class CodecTest(TestCase):
#check byte inputs ignores enocding
self.assertEqual(to_bytes(b('\x00\xc3\xbf'), "latin-1"),
- b('\x00\xc3\xbf'))
+ b('\x00\xc3\xbf'))
self.assertEqual(to_bytes(b('\x00\xc3\xbf'), None, "utf-8"),
b('\x00\xc3\xbf'))
-
+
#check bytes transcoding
self.assertEqual(to_bytes(b('\x00\xc3\xbf'), "latin-1", "utf-8"),
b('\x00\xff'))
-
+
#check other
self.assertRaises(TypeError, to_bytes, None)
def test_to_unicode(self):
"test to_unicode()"
-
+
#check unicode inputs
self.assertEqual(to_unicode(u'abc'), u'abc')
self.assertEqual(to_unicode(u'\x00\xff'), u'\x00\xff')
-
+
#check unicode input ignores encoding
self.assertEqual(to_unicode(u'\x00\xff', None), u'\x00\xff')
self.assertEqual(to_unicode(u'\x00\xff', "ascii"), u'\x00\xff')
-
+
#check bytes input
self.assertEqual(to_unicode(b('abc')), u'abc')
self.assertEqual(to_unicode(b('\x00\xc3\xbf')), u'\x00\xff')
@@ -215,13 +215,13 @@ class CodecTest(TestCase):
u'\x00\xff')
self.assertRaises(ValueError, to_unicode, b('\x00\xff'))
self.assertRaises(TypeError, to_unicode, b('\x00\xff'), None)
-
+
#check other
self.assertRaises(TypeError, to_unicode, None)
-
+
def test_to_native_str(self):
"test to_native_str()"
-
+
self.assertEqual(to_native_str(u'abc'), 'abc')
self.assertEqual(to_native_str(b('abc')), 'abc')
self.assertRaises(TypeError, to_native_str, None)
@@ -233,7 +233,7 @@ class CodecTest(TestCase):
b('\x00\xff'))
self.assertEqual(to_native_str(b('\x00\xff'), 'latin-1'),
b('\x00\xff'))
-
+
# Py3k #
#self.assertEqual(to_native_str(u'\x00\xff'), '\x00\xff')
#self.assertEqual(to_native_str(b('\x00\xc3\xbf')), '\x00\xff')
@@ -243,31 +243,31 @@ class CodecTest(TestCase):
# '\x00\xff')
#
# end Py3k #
-
+
#TODO: test to_hash_str()
-
+
def test_is_ascii_safe(self):
- "test is_ascii_safe()"
+ "test is_ascii_safe()"
self.assertTrue(is_ascii_safe(b("\x00abc\x7f")))
self.assertTrue(is_ascii_safe(u"\x00abc\x7f"))
self.assertFalse(is_ascii_safe(b("\x00abc\x80")))
self.assertFalse(is_ascii_safe(u"\x00abc\x80"))
-
-
+
+
def test_is_same_codec(self):
"test is_same_codec()"
self.assertTrue(is_same_codec(None, None))
self.assertFalse(is_same_codec(None, 'ascii'))
-
+
self.assertTrue(is_same_codec("ascii", "ascii"))
self.assertTrue(is_same_codec("ascii", "ASCII"))
-
+
self.assertTrue(is_same_codec("utf-8", "utf-8"))
self.assertTrue(is_same_codec("utf-8", "utf8"))
self.assertTrue(is_same_codec("utf-8", "UTF_8"))
self.assertFalse(is_same_codec("ascii", "utf-8"))
-
+
#=========================================================
#test des module
#=========================================================
@@ -489,9 +489,9 @@ class MD4_Test(TestCase):
"test md4 update"
h = md4(b(''))
self.assertEqual(h.hexdigest(), "31d6cfe0d16ae931b73c59d7e0c089c0")
-
+
#NOTE: under py2, hashlib methods try to encode to ascii,
- # though shouldn't rely on that.
+ # though shouldn't rely on that.
# Py3k #
#self.assertRaises(TypeError, h.update, u'x')
# end Py3k #
@@ -536,7 +536,7 @@ from passlib.utils import pbkdf2
class KdfTest(TestCase):
"test kdf helpers"
-
+
def test_pbkdf1(self):
"test pbkdf1"
for secret, salt, rounds, klen, hash, correct in [
@@ -546,7 +546,7 @@ class KdfTest(TestCase):
]:
result = pbkdf2.pbkdf1(secret, salt, rounds, klen, hash)
self.assertEqual(result, correct)
-
+
#test rounds < 1
#test klen < 0
#test klen > block size
diff --git a/passlib/tests/test_win32.py b/passlib/tests/test_win32.py
index 8508c2e..524fda6 100644
--- a/passlib/tests/test_win32.py
+++ b/passlib/tests/test_win32.py
@@ -25,8 +25,8 @@ class UtilTest(TestCase):
("NEWPASSWORD", u'09eeab5aa415d6e4d408e6b105741864'),
("welcome", u"c23413a8a1e7665faad3b435b51404ee"),
]:
- result = mod.raw_lmhash(secret, hex=True)
- self.assertEquals(result, hash)
+ result = mod.raw_lmhash(secret, hex=True)
+ self.assertEquals(result, hash)
def test_nthash(self):
for secret, hash in [
diff --git a/passlib/utils/__init__.py b/passlib/utils/__init__.py
index ffbc60f..6fe4fa9 100644
--- a/passlib/utils/__init__.py
+++ b/passlib/utils/__init__.py
@@ -131,7 +131,7 @@ native_str = bytes
# if py25 compat were sacrificed, this func could be removed.
def b(source):
"convert native str to bytes (noop under py2; uses latin-1 under py3)"
- #assert isinstance(source, native_str)
+ #assert isinstance(source, native_str)
# Py2k #
return source
# Py3k #
@@ -147,23 +147,23 @@ try:
from crypt import crypt as os_crypt
except ImportError: #pragma: no cover
safe_os_crypt = os_crypt = None
-else:
+else:
def safe_os_crypt(secret, hash):
"""wrapper around stdlib's crypt.
-
+
Python 3's crypt behaves slightly differently from Python 2's crypt.
for one, it takes in and returns unicode.
- internally, it converts to utf-8 before hashing.
+ internally, it converts to utf-8 before hashing.
Annoyingly, *there is no way to call it using bytes*.
thus, it can't be used to hash non-ascii passwords
using any encoding but utf-8 (eg, using latin-1).
-
+
This wrapper attempts to gloss over all those issues:
Under Python 2, it accept passwords as unicode or bytes,
accepts hashes only as unicode, and always returns unicode.
Under Python 3, it will signal that it cannot hash a password
if provided as non-utf-8 bytes, but otherwise behave the same as crypt.
-
+
:arg secret: password as bytes or unicode
:arg hash: hash/salt as unicode
:returns:
@@ -171,8 +171,8 @@ else:
or ``(True, result: unicode)`` otherwise.
"""
#XXX: source indicates crypt() may return None on some systems
- # if an error occurrs - could make this return False in that case.
-
+ # if an error occurrs - could make this return False in that case.
+
# Py2k #
#NOTE: this guard logic is designed purely to match py3 behavior,
# with the exception that it accepts secret as bytes
@@ -183,7 +183,7 @@ else:
else:
hash = hash.encode("utf-8")
return True, os_crypt(secret, hash).decode("ascii")
-
+
# Py3k #
#if isinstance(secret, bytes):
# #decode to utf-8. if successful, will be reencoded with os_crypt,
@@ -301,15 +301,15 @@ def has_salt_info(handler):
def to_bytes(source, encoding="utf-8", source_encoding=None, errname="value"):
"""helper to encoding unicode -> bytes
-
+
this function takes in a ``source`` string.
- if unicode, encodes it using the specified ``encoding``.
+ if unicode, encodes it using the specified ``encoding``.
if bytes, returns unchanged - unless ``source_encoding``
is specified, in which case the bytes are transcoded
if and only if the source encoding doesn't match
the desired encoding.
all other types result in a :exc:`TypeError`.
-
+
:arg source: source bytes/unicode to process
:arg encoding: target character encoding or ``None``.
:param source_encoding: optional source encoding
@@ -317,11 +317,11 @@ def to_bytes(source, encoding="utf-8", source_encoding=None, errname="value"):
:raises TypeError: if unicode encountered but ``encoding=None`` specified;
or if source is not unicode or bytes.
-
+
:returns: bytes object
-
+
.. note::
-
+
if ``encoding`` is set to ``None``, then unicode strings
will be rejected, and only byte strings will be allowed through.
"""
@@ -332,7 +332,7 @@ def to_bytes(source, encoding="utf-8", source_encoding=None, errname="value"):
else:
return source
elif not encoding:
- raise TypeError("%s must be bytes, not %s" % (errname, type(source)))
+ raise TypeError("%s must be bytes, not %s" % (errname, type(source)))
elif isinstance(source, unicode):
return source.encode(encoding)
elif source_encoding:
@@ -340,15 +340,15 @@ def to_bytes(source, encoding="utf-8", source_encoding=None, errname="value"):
(errname, source_encoding, type(source)))
else:
raise TypeError("%s must be unicode or bytes, not %s" % (errname, type(source)))
-
+
def to_unicode(source, source_encoding="utf-8", errname="value"):
"""take in unicode or bytes, return unicode
-
+
if bytes provided, decodes using specified encoding.
leaves unicode alone.
-
+
:raises TypeError: if source is not unicode or bytes.
-
+
:arg source: source bytes/unicode to process
:arg source_encoding: encoding to use when decoding bytes instances
:param errname: optional name of variable/noun to reference when raising errors
@@ -367,12 +367,12 @@ def to_unicode(source, source_encoding="utf-8", errname="value"):
def to_native_str(source, encoding="utf-8", errname="value"):
"""take in unicode or bytes, return native string
-
+
python 2: encodes unicode using specified encoding, leaves bytes alone.
python 3: decodes bytes using specified encoding, leaves unicode alone.
-
+
:raises TypeError: if source is not unicode or bytes.
-
+
:arg source: source bytes/unicode to process
:arg encoding: encoding to use when encoding unicode / decoding bytes
:param errname: optional name of variable/noun to reference when raising errors
@@ -386,7 +386,7 @@ def to_native_str(source, encoding="utf-8", errname="value"):
# Py3k #
#return source.decode(encoding)
# end Py3k #
-
+
elif isinstance(source, unicode):
# Py2k #
return source.encode(encoding)
@@ -452,13 +452,13 @@ ujoin = u''.join
def belem_join(elems):
"""takes series of bytes elements, returns bytes.
-
+
elem should be result of bytes[x].
this is another bytes instance under py2,
but it int under py3.
-
+
returns bytes.
-
+
this is bytes() constructor under py3,
but b"".join() under py2.
"""
@@ -477,13 +477,13 @@ belem_join = bjoin
def bord(elem):
"""takes bytes element, returns integer.
-
+
elem should be result of bytes[x].
this is another bytes instance under py2,
but it int under py3.
-
+
returns int in range(0,256).
-
+
this is ord() under py2, and noop under py3.
"""
# Py2k #
@@ -516,12 +516,12 @@ def bjoin_ints(values):
def render_bytes(source, *args):
"""helper for using formatting operator with bytes.
-
+
this function is motivated by the fact that
:class:`bytes` instances do not support % or {} formatting under python 3.
this function is an attempt to provide a replacement
that will work uniformly under python 2 & 3.
-
+
it converts everything to unicode (including bytes arguments),
then encodes the result to latin-1.
"""
@@ -740,7 +740,7 @@ def getrandstr(rng, charset, count):
yield charset[value % letters]
value //= letters
i += 1
-
+
if isinstance(charset, unicode):
return ujoin(helper())
else:
diff --git a/passlib/utils/des.py b/passlib/utils/des.py
index 88b591d..805a265 100644
--- a/passlib/utils/des.py
+++ b/passlib/utils/des.py
@@ -7,7 +7,7 @@ found at `<http://www.dynamic.net.au/christos/crypt/UnixCrypt2.txt>`_.
The copyright & license for that source is as follows::
- UnixCrypt.java 0.9 96/11/25
+ UnixCrypt.java 0.9 96/11/25
Copyright (c) 1996 Aki Yoshida. All rights reserved.
Permission to use, copy, modify and distribute this software
for non-commercial or commercial purposes and without fee is
@@ -17,8 +17,8 @@ The copyright & license for that source is as follows::
---
Unix crypt(3C) utility
- @version 0.9, 11/25/96
- @author Aki Yoshida
+ @version 0.9, 11/25/96
+ @author Aki Yoshida
---
@@ -575,7 +575,7 @@ def expand_des_key(key):
"convert 7 byte des key to 8 byte des key (by adding parity bit every 7 bits)"
if not isinstance(key, bytes):
raise TypeError("key must be bytes, not %s" % (type(key),))
-
+
#NOTE: could probably do this much more cleverly and efficiently,
# but no need really given it's use.
@@ -605,10 +605,10 @@ def expand_des_key(key):
def des_encrypt_block(key, input):
"""do traditional encryption of a single DES block
- :arg key: 8 byte des key
+ :arg key: 8 byte des key
:arg input: 8 byte plaintext
:returns: 8 byte ciphertext
-
+
all values must be :class:`bytes`
"""
if not isinstance(key, bytes):
diff --git a/passlib/utils/handlers.py b/passlib/utils/handlers.py
index 8d23200..9c5176b 100644
--- a/passlib/utils/handlers.py
+++ b/passlib/utils/handlers.py
@@ -70,7 +70,7 @@ def identify_prefix(hash, prefix):
hash = hash.decode("ascii")
except UnicodeDecodeError:
return False
- return hash.startswith(prefix)
+ return hash.startswith(prefix)
#=========================================================
#parsing helpers
@@ -224,7 +224,7 @@ class StaticHandler(object):
hash = cls._norm_hash(hash)
result = cls.genhash(secret, hash, *cargs, **context)
return cls._norm_hash(result) == hash
-
+
@classmethod
def _norm_hash(cls, hash):
"""[helper for verify] normalize hash for comparsion purposes"""
@@ -273,7 +273,7 @@ class GenericHandler(object):
If this attribute is filled in, the default :meth:`identify` method will use
it as a identifying prefix that can be used to recognize instances of this handler's
hash. Filling this out is recommended for speed.
-
+
This should be a unicode str.
.. attribute:: checksum_size
@@ -389,9 +389,9 @@ class GenericHandler(object):
@classmethod
def from_string(cls, hash): #pragma: no cover
"""return parsed instance from hash/configuration string
-
+
:raises ValueError: if hash is incorrectly formatted
-
+
:returns:
hash parsed into components,
for formatting / calculating checksum.
@@ -400,12 +400,12 @@ class GenericHandler(object):
def to_string(self): #pragma: no cover
"""render instance to hash or configuration string
-
+
:returns:
if :attr:`checksum` is set, should return full hash string.
if not, should either return abbreviated configuration string,
or fill in a stub checksum.
-
+
should return native string type (ascii-bytes under python 2,
unicode under python 3)
"""
diff --git a/passlib/utils/md4.py b/passlib/utils/md4.py
index 40a48e4..0e70f1e 100644
--- a/passlib/utils/md4.py
+++ b/passlib/utils/md4.py
@@ -224,7 +224,7 @@ class md4(object):
def hexdigest(self):
return to_native_str(hexlify(self.digest()), "latin-1")
-
+
#=========================================================================
#eoc
#=========================================================================
diff --git a/passlib/win32.py b/passlib/win32.py
index 21191b1..4ba694c 100644
--- a/passlib/win32.py
+++ b/passlib/win32.py
@@ -49,7 +49,7 @@ def raw_lmhash(secret, encoding="ascii", hex=False):
# for it's encoding. until a clear reference is found,
# as well as a path for getting the encoding,
# letting this default to "ascii" to prevent incorrect hashes
- # from being made w/o user explicitly choosing an encoding.
+ # from being made w/o user explicitly choosing an encoding.
if isinstance(secret, unicode):
secret = secret.encode(encoding)
ns = secret.upper()[:14] + b("\x00") * (14-len(secret))
diff --git a/setup.cfg b/setup.cfg
index 8945f79..38574b1 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,7 +1,7 @@
[egg_info]
# XXX: should stop relying on this section,
# it's only used by setuptools/distribute,
-# and will be phased out in distutils2
+# and will be phased out in distutils2
tag_build = a1.dev
tag_date = true
diff --git a/setup.py b/setup.py
index b41ded9..c3b7a38 100644
--- a/setup.py
+++ b/setup.py
@@ -29,7 +29,7 @@ if py3k:
#monkeypatch preprocessor into lib2to3
from passlib.setup.cond2to3 import patch2to3
patch2to3()
-
+
#enable 2to3 translation in build_py
if has_distribute:
opts['use_2to3'] = True
@@ -61,7 +61,7 @@ to providing full-strength password hashing for multi-user application.
for details, installation instructions, and examples.
* See the `passlib homepage <http://passlib.googlecode.com>`_
- for the latest news, more information, and additional downloads.
+ for the latest news, more information, and additional downloads.
* See the `changelog <http://packages.python.org/passlib/history.html>`_
for description of what's new in Passlib.