summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES21
-rw-r--r--README2
-rw-r--r--admin/benchmarks.py14
-rw-r--r--choose_rounds.py2
-rw-r--r--docs/index.rst2
-rw-r--r--docs/lib/passlib.apps.rst8
-rw-r--r--docs/lib/passlib.context-tutorial.rst24
-rw-r--r--docs/lib/passlib.context.rst35
-rw-r--r--docs/lib/passlib.hash.bcrypt.rst4
-rw-r--r--docs/lib/passlib.hash.bcrypt_sha256.rst4
-rw-r--r--docs/lib/passlib.hash.bsdi_crypt.rst4
-rw-r--r--docs/lib/passlib.hash.cisco_pix.rst4
-rw-r--r--docs/lib/passlib.hash.cisco_type7.rst2
-rw-r--r--docs/lib/passlib.hash.des_crypt.rst2
-rw-r--r--docs/lib/passlib.hash.django_std.rst4
-rw-r--r--docs/lib/passlib.hash.fshp.rst4
-rw-r--r--docs/lib/passlib.hash.grub_pbkdf2_sha512.rst2
-rw-r--r--docs/lib/passlib.hash.hex_digests.rst2
-rw-r--r--docs/lib/passlib.hash.ldap_crypt.rst2
-rw-r--r--docs/lib/passlib.hash.ldap_std.rst2
-rw-r--r--docs/lib/passlib.hash.lmhash.rst4
-rw-r--r--docs/lib/passlib.hash.md5_crypt.rst4
-rw-r--r--docs/lib/passlib.hash.msdcc.rst2
-rw-r--r--docs/lib/passlib.hash.msdcc2.rst2
-rw-r--r--docs/lib/passlib.hash.mssql2000.rst2
-rw-r--r--docs/lib/passlib.hash.mssql2005.rst2
-rw-r--r--docs/lib/passlib.hash.mysql323.rst2
-rw-r--r--docs/lib/passlib.hash.nthash.rst4
-rw-r--r--docs/lib/passlib.hash.oracle10.rst2
-rw-r--r--docs/lib/passlib.hash.oracle11.rst2
-rw-r--r--docs/lib/passlib.hash.pbkdf2_digest.rst4
-rw-r--r--docs/lib/passlib.hash.plaintext.rst2
-rw-r--r--docs/lib/passlib.hash.postgres_md5.rst2
-rw-r--r--docs/lib/passlib.hash.rst8
-rw-r--r--docs/lib/passlib.hash.scram.rst6
-rw-r--r--docs/lib/passlib.hash.sha256_crypt.rst4
-rw-r--r--docs/lib/passlib.hash.unix_disabled.rst2
-rw-r--r--docs/lib/passlib.hosts.rst4
-rw-r--r--docs/new_app_quickstart.rst6
-rw-r--r--docs/password_hash_api.rst88
-rw-r--r--passlib/apache.py4
-rw-r--r--passlib/context.py133
-rw-r--r--passlib/ext/django/models.py4
-rw-r--r--passlib/ext/django/utils.py22
-rw-r--r--passlib/handlers/bcrypt.py4
-rw-r--r--passlib/handlers/cisco.py16
-rw-r--r--passlib/handlers/des_crypt.py8
-rw-r--r--passlib/handlers/digests.py18
-rw-r--r--passlib/handlers/django.py29
-rw-r--r--passlib/handlers/fshp.py8
-rw-r--r--passlib/handlers/ldap_digests.py22
-rw-r--r--passlib/handlers/md5_crypt.py4
-rw-r--r--passlib/handlers/misc.py48
-rw-r--r--passlib/handlers/mssql.py10
-rw-r--r--passlib/handlers/mysql.py4
-rw-r--r--passlib/handlers/oracle.py8
-rw-r--r--passlib/handlers/pbkdf2.py17
-rw-r--r--passlib/handlers/phpass.py2
-rw-r--r--passlib/handlers/postgres.py2
-rw-r--r--passlib/handlers/scram.py2
-rw-r--r--passlib/handlers/sha1_crypt.py2
-rw-r--r--passlib/handlers/sha2_crypt.py6
-rw-r--r--passlib/handlers/sun_md5_crypt.py2
-rw-r--r--passlib/handlers/windows.py22
-rw-r--r--passlib/ifc.py63
-rw-r--r--passlib/tests/test_apps.py2
-rw-r--r--passlib/tests/test_context.py107
-rw-r--r--passlib/tests/test_context_deprecated.py4
-rw-r--r--passlib/tests/test_ext_django.py6
-rw-r--r--passlib/tests/test_handlers.py28
-rw-r--r--passlib/tests/test_handlers_bcrypt.py6
-rw-r--r--passlib/tests/test_registry.py2
-rw-r--r--passlib/tests/test_utils_handlers.py35
-rw-r--r--passlib/tests/utils.py168
-rw-r--r--passlib/utils/__init__.py14
-rw-r--r--passlib/utils/handlers.py110
76 files changed, 623 insertions, 615 deletions
diff --git a/CHANGES b/CHANGES
index a8e8237..9e07b61 100644
--- a/CHANGES
+++ b/CHANGES
@@ -96,6 +96,22 @@ Minor Internal Changes
Deprecations
------------
+ * The :class:`~passlib.context.CryptContext` object, and the :class:`~passlib.ifc.PasswordHash` api
+ used by all hashes in passlib, have had a number of cleanups made:
+
+ * :meth:`!encrypt` has been renamed to :meth:`~passlib.ifc.PasswordHash.hash`,
+ to clarify that it's performing one-way hashing rather than encryption.
+ A compatibility alias will remain in place until Passlib 2.0.
+ This should fix the longstanding :issue:`21`.
+
+ * The little-used methods :meth:`~passlib.ifc.PasswordHash.genhash` and
+ :meth:`~passlib.ifc.PasswordHash.genconfig` have been deprecated.
+ Compatibility aliases will remain in place until Passlib 2.0.
+
+ * In order for :meth:`~passlib.ifc.PasswordHash.hash` to take over the job
+ previously performed by :meth:`~passlib.ifc.PasswordHash.genhash`, it now accepts
+ a keyword ``"config"``, which specifies an existing hash to extract configuration from.
+
* The :func:`~passlib.utils.generate_secret` function has been deprecated
in favor of the new :mod:`passlib.pwd` module, and the old function will be removed
in Passlib 2.0.
@@ -112,7 +128,6 @@ Deprecations
Backwards Incompatibilities
---------------------------
-
* :func:`passlib.utils.pbkdf2.pbkdf2` no longer supports custom PRF callables.
this was an unused feature, and prevented some useful optimizations.
@@ -125,6 +140,10 @@ Backwards Incompatibilities
algorithm available on the host, rather than one that is guaranteed to be portable.
Applications can explicitly set ``default_scheme="portable"`` to retain the old behavior.
+ * The little-used method :meth:`~passlib.ifc.PasswordHash.genconfig` method
+ will now always return a valid hash, rather than a truncated configuration
+ hash or ``None``. This should affect nearly 0 applications.
+
**1.6.6** (NOT YET RELEASED)
============================
diff --git a/README b/README
index e7e9c15..174bc45 100644
--- a/README
+++ b/README
@@ -34,7 +34,7 @@ A quick example of using passlib to integrate into a new application::
>>> from passlib.apps import custom_app_context as pwd_context
>>> # encrypting a password...
- >>> hash = pwd_context.encrypt("somepass")
+ >>> hash = pwd_context.hash("somepass")
>>> hash
'$6$rounds=36122$kzMjVFTjgSVuPoS.$zx2RoZ2TYRHoKn71Y60MFmyqNPxbNnTZdwYD8y2atgoRIp923WJSbcbQc6Af3osdW96MRfwb5Hk7FymOM6D7J1'
diff --git a/admin/benchmarks.py b/admin/benchmarks.py
index 9c294a2..5ba52d0 100644
--- a/admin/benchmarks.py
+++ b/admin/benchmarks.py
@@ -201,7 +201,7 @@ def test_context_calls():
another__vary_rounds=100,
)
def helper():
- hash = ctx.encrypt(SECRET, rounds=2001)
+ hash = ctx.hash(SECRET, rounds=2001)
ctx.verify(SECRET, hash)
ctx.verify_and_update(SECRET, hash)
ctx.verify_and_update(OTHER, hash)
@@ -219,7 +219,7 @@ def test_bcrypt_builtin():
bcrypt.set_backend("builtin")
bcrypt.default_rounds = 10
def helper():
- hash = bcrypt.encrypt(SECRET)
+ hash = bcrypt.hash(SECRET)
bcrypt.verify(SECRET, hash)
bcrypt.verify(OTHER, hash)
return helper
@@ -231,7 +231,7 @@ def test_bcrypt_ffi():
bcrypt.set_backend("bcrypt")
bcrypt.default_rounds = 8
def helper():
- hash = bcrypt.encrypt(SECRET)
+ hash = bcrypt.hash(SECRET)
bcrypt.verify(SECRET, hash)
bcrypt.verify(OTHER, hash)
return helper
@@ -242,7 +242,7 @@ def test_md5_crypt_builtin():
from passlib.hash import md5_crypt
md5_crypt.set_backend("builtin")
def helper():
- hash = md5_crypt.encrypt(SECRET)
+ hash = md5_crypt.hash(SECRET)
md5_crypt.verify(SECRET, hash)
md5_crypt.verify(OTHER, hash)
return helper
@@ -252,7 +252,7 @@ def test_ldap_salted_md5():
"""test ldap_salted_md5"""
from passlib.hash import ldap_salted_md5 as handler
def helper():
- hash = handler.encrypt(SECRET, salt='....')
+ hash = handler.hash(SECRET, salt='....')
handler.verify(SECRET, hash)
handler.verify(OTHER, hash)
return helper
@@ -263,7 +263,7 @@ def test_phpass():
from passlib.hash import phpass as handler
kwds = dict(salt='.'*8, rounds=16)
def helper():
- hash = handler.encrypt(SECRET, **kwds)
+ hash = handler.hash(SECRET, **kwds)
handler.verify(SECRET, hash)
handler.verify(OTHER, hash)
return helper
@@ -273,7 +273,7 @@ def test_sha1_crypt():
from passlib.hash import sha1_crypt as handler
kwds = dict(salt='.'*8, rounds=10000)
def helper():
- hash = handler.encrypt(SECRET, **kwds)
+ hash = handler.hash(SECRET, **kwds)
handler.verify(SECRET, hash)
handler.verify(OTHER, hash)
return helper
diff --git a/choose_rounds.py b/choose_rounds.py
index 43c4502..e8607e9 100644
--- a/choose_rounds.py
+++ b/choose_rounds.py
@@ -103,7 +103,7 @@ def main(*args):
"""estimate speed using specified # of rounds"""
# time a single verify() call
secret = "S0m3-S3Kr1T"
- hash = hasher.encrypt(secret, rounds=rounds)
+ hash = hasher.hash(secret, rounds=rounds)
def helper():
start = tick()
hasher.verify(secret, hash)
diff --git a/docs/index.rst b/docs/index.rst
index a776bd6..5dd1a2b 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -28,7 +28,7 @@ using the :doc:`SHA256-Crypt </lib/passlib.hash.sha256_crypt>` algorithm::
>>> from passlib.hash import sha256_crypt
>>> # generate new salt, and hash a password
- >>> hash = sha256_crypt.encrypt("toomanysecrets")
+ >>> hash = sha256_crypt.hash("toomanysecrets")
>>> hash
'$5$rounds=80000$zvpXD3gCkrt7tw.1$QqeTSolNHEfgryc5oMgiq1o8qCEAcmye3FoMSuvgToC'
diff --git a/docs/lib/passlib.apps.rst b/docs/lib/passlib.apps.rst
index e82d81b..ca22050 100644
--- a/docs/lib/passlib.apps.rst
+++ b/docs/lib/passlib.apps.rst
@@ -25,7 +25,7 @@ Each of the objects in this module can be imported directly::
Encrypting a password is simple (and salt generation is handled automatically)::
- >>> hash = custom_app_context.encrypt("toomanysecrets")
+ >>> hash = custom_app_context.hash("toomanysecrets")
>>> hash
'$5$rounds=84740$fYChCy.52EzebF51$9bnJrmTf2FESI93hgIBFF4qAfysQcKoB0veiI0ZeYU4'
@@ -116,7 +116,7 @@ Passlib provides two contexts related to ldap hashes:
>>> ldap_context = ldap_context.replace(default="ldap_salted_md5")
>>> # the new context object will now default to {SMD5}:
- >>> ldap_context.encrypt("password")
+ >>> ldap_context.hash("password")
'{SMD5}T9f89F591P3fFh1jz/YtW4aWD5s='
.. data:: ldap_nocrypt_context
@@ -190,7 +190,7 @@ PostgreSQL
>>> from passlib.apps import postgres_context
>>> # encrypting a password...
- >>> postgres_context.encrypt("somepass", user="dbadmin")
+ >>> postgres_context.hash("somepass", user="dbadmin")
'md578ed0f0ab2be0386645c1b74282917e7'
>>> # verifying a password...
@@ -200,7 +200,7 @@ PostgreSQL
False
>>> # forgetting the user will result in an error:
- >>> postgres_context.encrypt("somepass")
+ >>> postgres_context.hash("somepass")
Traceback (most recent call last):
<traceback omitted>
TypeError: user must be unicode or bytes, not None
diff --git a/docs/lib/passlib.context-tutorial.rst b/docs/lib/passlib.context-tutorial.rst
index 422e349..acdef7e 100644
--- a/docs/lib/passlib.context-tutorial.rst
+++ b/docs/lib/passlib.context-tutorial.rst
@@ -66,7 +66,7 @@ interface, and hashing and verifying passwords is equally as straightforward::
>>> # this loads first algorithm in the schemes list (sha256_crypt),
>>> # generates a new salt, and hashes the password:
- >>> hash1 = myctx.encrypt("joshua")
+ >>> hash1 = myctx.hash("joshua")
>>> hash1
'$5$rounds=80000$HFEGd1wnFknpibRl$VZqjyYcTenv7CtOf986hxuE0pRaGXnuLXyfb7m9xL69'
@@ -78,7 +78,7 @@ interface, and hashing and verifying passwords is equally as straightforward::
>>> # alternately, you can explicitly pick one of the configured algorithms,
>>> # through this is rarely needed in practice:
- >>> hash2 = myctx.encrypt("dogsnamehere", scheme="md5_crypt")
+ >>> hash2 = myctx.hash("dogsnamehere", scheme="md5_crypt")
>>> hash2
'$1$e2nig/AC$stejMS1ek6W0/UogYKFao/'
@@ -93,7 +93,7 @@ using the ``default`` keyword::
>>> myctx = CryptContext(schemes=["sha256_crypt", "md5_crypt", "des_crypt"],
default="des_crypt")
- >>> hash = myctx.encrypt("password")
+ >>> hash = myctx.hash("password")
>>> hash
'bIwNofDzt1LCY'
@@ -106,7 +106,7 @@ which probably provide a better argument for *why* you'd want to use it.
.. seealso::
- * the :meth:`CryptContext.encrypt`, :meth:`~CryptContext.verify`, and :meth:`~CryptContext.identify` methods.
+ * the :meth:`CryptContext.hash`, :meth:`~CryptContext.verify`, and :meth:`~CryptContext.identify` methods.
* the :ref:`schemes <context-schemes-option>` and :ref:`default <context-default-option>` constructor options.
.. _context-default-settings-example:
@@ -127,12 +127,12 @@ and the :class:`ldap_salted_md5 <passlib.hash.ldap_salted_md5>` algorithm uses
>>> myctx = CryptContext(["sha256_crypt", "ldap_salted_md5"])
>>> # sha256_crypt using 80000 rounds...
- >>> myctx.encrypt("password", scheme="sha256_crypt")
+ >>> myctx.hash("password", scheme="sha256_crypt")
'$5$rounds=80000$GgU/gwNBs9SaObqs$ohY23/zm.8O0TpkGx5fxk0aeVdFpaeKo9GUkMJ0VrMC'
^^^^^
>>> # ldap_salted_md5 with an 8 byte salt...
- >>> myctx.encrypt("password", scheme="ldap_salted_md5")
+ >>> myctx.hash("password", scheme="ldap_salted_md5")
'{SMD5}cIYrPh5f/TeUKg9oghECB5fSeu8='
^^^^^^^^^^
@@ -148,11 +148,11 @@ These is done by passing the CryptContext constructor a keyword with the format
... ldap_salted_md5__salt_size=16)
>>> # the effect of this can be seen the next time encrypt is called:
- >>> myctx.encrypt("password", scheme="sha256_crypt")
+ >>> myctx.hash("password", scheme="sha256_crypt")
'$5$rounds=91234$GgU/gwNBs9SaObqs$ohY23/zm.8O0TpkGx5fxk0aeVdFpaeKo9GUkMJ0VrMC'
^^^^^
- >>> myctx.encrypt("password", scheme="ldap_salted_md5")
+ >>> myctx.hash("password", scheme="ldap_salted_md5")
'{SMD5}NnQh2S2pjnFxwtMhjbVH59TaG6P0/l/r3RsDwPj/n/M='
^^^^^^^^^^^^^^^^^^^^^
@@ -288,7 +288,7 @@ perform can be combined into the following bit of skeleton code:
hash = get_hash_from_user(user)
if pass_ctx.verify(password, hash):
if pass_ctx.needs_update(hash):
- new_hash = pass_ctx.encrypt(password)
+ new_hash = pass_ctx.hash(password)
replace_user_hash(user, new_hash)
do_successful_things()
else:
@@ -340,7 +340,7 @@ New hashes generated by this context will always honor the minimum
(just as if ``default_rounds`` was set to the same value)::
>>> # plain call to encrypt:
- >>> hash1 = myctx.encrypt("password")
+ >>> hash1 = myctx.hash("password")
'$5$rounds=131072$i6xuFK6j8r66ahGn$r.7H8HUk30qiH7fIWRJFJfhWG925nRZh90aYPMdewr3'
^^^^^^
>>> # hashes with enough rounds won't show up as deprecated...
@@ -351,7 +351,7 @@ Explicitly setting the rounds too low will cause a warning,
and the minimum will be used anyways::
>>> # explicit rounds passed to encrypt...
- >>> myctx.encrypt("password", rounds=1000)
+ >>> myctx.hash("password", rounds=1000)
__main__:1: PasslibConfigWarning: sha256_crypt config requires rounds >= 131072,
increasing value from 80000
'$5$rounds=131072$86YrzUF3fGwY99oy$03e/pyh4l3N/G0509er9JiQmIxc0y9lrAJaLswX/iv8'
@@ -494,7 +494,7 @@ the following code in the correct function::
# 'category' containing a category assigned to the user account
#
- hash = user_pwd_context.encrypt(secret, category=category)
+ hash = user_pwd_context.hash(secret, category=category)
#... perform appropriate actions to store hash...
diff --git a/docs/lib/passlib.context.rst b/docs/lib/passlib.context.rst
index cb61f7b..595a986 100644
--- a/docs/lib/passlib.context.rst
+++ b/docs/lib/passlib.context.rst
@@ -68,7 +68,7 @@ Options which directly affect the behavior of the CryptContext instance:
The most important option in the constructor,
This option controls what hashes can be used
- by the :meth:`~CryptContext.encrypt` method,
+ by the :meth:`~CryptContext.hash` method,
which hashes will be recognized by :meth:`~CryptContext.verify`
and :meth:`~CryptContext.identify`, and other effects
throughout the instance.
@@ -109,23 +109,23 @@ Options which directly affect the behavior of the CryptContext instance:
You can use the :meth:`~CryptContext.default_scheme` method
to retrieve the name of the current default scheme.
As an example, the following demonstrates the effect
- of this parameter on the :meth:`~CryptContext.encrypt`
+ of this parameter on the :meth:`~CryptContext.hash`
method::
>>> from passlib.context import CryptContext
>>> myctx = CryptContext(schemes=["sha256_crypt", "md5_crypt"])
- >>> # encrypt() uses the first scheme
+ >>> # hash() uses the first scheme
>>> myctx.default_scheme()
'sha256_crypt'
- >>> myctx.encrypt("password")
+ >>> myctx.hash("password")
'$5$rounds=80000$R5ZIZRTNPgbdcWq5$fT/Oeqq/apMa/0fbx8YheYWS6Z3XLTxCzEtutsk2cJ1'
>>> # but setting default causes the second scheme to be used.
>>> myctx.update(default="md5_crypt")
>>> myctx.default_scheme()
'md5_crypt'
- >>> myctx.encrypt("password")
+ >>> myctx.hash("password")
'$1$Rr0C.KI8$Kvciy8pqfL9BQ2CJzEzfZ/'
.. seealso:: the :ref:`context-basic-example` example in the tutorial.
@@ -184,7 +184,7 @@ in ``schemes``, and :samp:`{option}` one of the parameters below:
:samp:`{scheme}__default_rounds`
Sets the default number of rounds to use with this scheme
- when generating new hashes (using :meth:`~CryptContext.encrypt`).
+ when generating new hashes (using :meth:`~CryptContext.hash`).
If not set, this will fall back to the an algorithm-specific
:attr:`~passlib.ifc.PasswordHash.default_rounds`.
@@ -193,15 +193,15 @@ in ``schemes``, and :samp:`{option}` one of the parameters below:
>>> from passlib.context import CryptContext
- >>> # no explicit default_rounds set, so encrypt() uses sha256_crypt's default (80000)
+ >>> # no explicit default_rounds set, so hash() uses sha256_crypt's default (80000)
>>> myctx = CryptContext(["sha256_crypt"])
- >>> myctx.encrypt("fooey")
+ >>> myctx.hash("fooey")
'$5$rounds=80000$60Y7mpmAhUv6RDvj$AdseAOq6bKUZRDRTr/2QK1t38qm3P6sYeXhXKnBAmg0'
^^^^^
>>> # but if a default is specified, it will be used instead.
>>> myctx = CryptContext(["sha256_crypt"], sha256_crypt__default_rounds=77123)
- >>> myctx.encrypt("fooey")
+ >>> myctx.hash("fooey")
'$5$rounds=77123$60Y7mpmAhUv6RDvj$AdseAOq6bKUZRDRTr/2QK1t38qm3P6sYeXhXKnBAmg0'
^^^^^
@@ -211,7 +211,7 @@ in ``schemes``, and :samp:`{option}` one of the parameters below:
Instead of using a fixed rounds value (such as specified by
``default_rounds``, above); this option will cause each call
- to :meth:`~CryptContext.encrypt` to vary the default rounds value
+ to :meth:`~CryptContext.hash` to vary the default rounds value
by some amount.
This can be an integer value, in which case each call will use a rounds
@@ -223,13 +223,13 @@ in ``schemes``, and :samp:`{option}` one of the parameters below:
As an example of how this parameter operates::
- >>> # without vary_rounds set, encrypt() uses the same amount each time:
+ >>> # without vary_rounds set, hash() uses the same amount each time:
>>> from passlib.context import CryptContext
>>> myctx = CryptContext(schemes=["sha256_crypt"],
... sha256_crypt__default_rounds=80000)
- >>> myctx.encrypt("fooey")
+ >>> myctx.hash("fooey")
'$5$rounds=80000$60Y7mpmAhUv6RDvj$AdseAOq6bKUZRDRTr/2QK1t38qm3P6sYeXhXKnBAmg0'
- >>> myctx.encrypt("fooey")
+ >>> myctx.hash("fooey")
'$5$rounds=80000$60Y7mpmAhUv6RDvj$AdseAOq6bKUZRDRTr/2QK1t38qm3P6sYeXhXKnBAmg0'
^^^^^
@@ -238,9 +238,9 @@ in ``schemes``, and :samp:`{option}` one of the parameters below:
>>> myctx = CryptContext(schemes=["sha256_crypt"],
... sha256_crypt__default_rounds=80000,
... sha256_crypt__vary_rounds=0.1)
- >>> myctx.encrypt("fooey")
+ >>> myctx.hash("fooey")
'$5$rounds=83966$bMpgQxN2hXo2kVr4$jL4Q3ov41UPgSbO7jYL0PdtsOg5koo4mCa.UEF3zan.'
- >>> myctx.encrypt("fooey")
+ >>> myctx.hash("fooey")
'$5$rounds=72109$43BBHC/hYPHzL69c$VYvVIdKn3Zdnvu0oJHVlo6rr0WjiMTGmlrZrrH.GxnA'
^^^^^
@@ -333,13 +333,13 @@ For example, a CryptContext could be set up as follows::
>>> # In this case, calling encrypt with ``category=None`` would result
>>> # in a hash that used 77000 sha256-crypt rounds:
- >>> myctx.encrypt("password", category=None)
+ >>> myctx.hash("password", category=None)
'$5$rounds=77000$sj3XI0AbKlEydAKt$BhFvyh4.IoxaUeNlW6rvQ.O0w8BtgLQMYorkCOMzf84'
^^^^^
>>> # But if the application passed in ``category="staff"`` when an administrative
>>> # account set their password, 88000 rounds would be used:
- >>> myctx.encrypt("password", category="staff")
+ >>> myctx.hash("password", category="staff")
'$5$rounds=88000$w7XIdKfTI9.YLwmA$MIzGvs6NU1QOQuuDHhICLmDsdW/t94Bbdfxdh/6NJl7'
^^^^^
@@ -353,6 +353,7 @@ purpose is to act as a container for multiple password hashes.
Most applications will only need to make use two methods in a CryptContext
instance:
+.. automethod:: CryptContext.hash
.. automethod:: CryptContext.encrypt
.. automethod:: CryptContext.verify
.. automethod:: CryptContext.identify
diff --git a/docs/lib/passlib.hash.bcrypt.rst b/docs/lib/passlib.hash.bcrypt.rst
index 53a841a..039b178 100644
--- a/docs/lib/passlib.hash.bcrypt.rst
+++ b/docs/lib/passlib.hash.bcrypt.rst
@@ -14,12 +14,12 @@ for new applications. This class can be used directly as follows::
>>> from passlib.hash import bcrypt
>>> # generate new salt, encrypt password
- >>> h = bcrypt.encrypt("password")
+ >>> h = bcrypt.hash("password")
>>> h
'$2a$12$NT0I31Sa7ihGEWpka9ASYrEFkhuTNeBQ2xfZskIiiJeyFXhRgS.Sy'
>>> # the same, but with an explicit number of rounds
- >>> bcrypt.encrypt("password", rounds=8)
+ >>> bcrypt.hash("password", rounds=8)
'$2a$08$8wmNsdCH.M21f.LSBSnYjQrZ9l1EmtBc9uNPGL.9l75YE8D8FlnZC'
>>> # verify password
diff --git a/docs/lib/passlib.hash.bcrypt_sha256.rst b/docs/lib/passlib.hash.bcrypt_sha256.rst
index 255455f..8b8face 100644
--- a/docs/lib/passlib.hash.bcrypt_sha256.rst
+++ b/docs/lib/passlib.hash.bcrypt_sha256.rst
@@ -16,12 +16,12 @@ This class can be used directly as follows::
>>> from passlib.hash import bcrypt_sha256
>>> # generate new salt, encrypt password
- >>> h = bcrypt_sha256.encrypt("password")
+ >>> h = bcrypt_sha256.hash("password")
>>> h
'$bcrypt-sha256$2a,12$LrmaIX5x4TRtAwEfwJZa1.$2ehnw6LvuIUTM0iz4iz9hTxv21B6KFO'
>>> # the same, but with an explicit number of rounds
- >>> bcrypt.encrypt("password", rounds=8)
+ >>> bcrypt.hash("password", rounds=8)
'$bcrypt-sha256$2a,8$UE3dIZ.0I6XZtA/LdMrrle$Ag04/5zYu./12.OSqInXZnJ.WZoh1ua'
>>> # verify password
diff --git a/docs/lib/passlib.hash.bsdi_crypt.rst b/docs/lib/passlib.hash.bsdi_crypt.rst
index c41bc16..082fd53 100644
--- a/docs/lib/passlib.hash.bsdi_crypt.rst
+++ b/docs/lib/passlib.hash.bsdi_crypt.rst
@@ -17,12 +17,12 @@ It class can be used directly as follows::
>>> from passlib.hash import bsdi_crypt
>>> # generate new salt, encrypt password
- >>> hash = bsdi_crypt.encrypt("password")
+ >>> hash = bsdi_crypt.hash("password")
>>> hash
'_7C/.Bf/4gZk10RYRs4Y'
>>> # same, but with explict number of rounds
- >>> bsdi_crypt.encrypt("password", rounds=10001)
+ >>> bsdi_crypt.hash("password", rounds=10001)
'_FQ0.amG/zwCMip7DnBk'
>>> # verify password
diff --git a/docs/lib/passlib.hash.cisco_pix.rst b/docs/lib/passlib.hash.cisco_pix.rst
index e62ecb7..ab11a5f 100644
--- a/docs/lib/passlib.hash.cisco_pix.rst
+++ b/docs/lib/passlib.hash.cisco_pix.rst
@@ -34,7 +34,7 @@ newer systems. They can be used directly as follows::
>>> from passlib.hash import cisco_pix as pix
>>> # encrypt password using specified username
- >>> hash = pix.encrypt("password", user="user")
+ >>> hash = pix.hash("password", user="user")
>>> hash
'A5XOy94YKDPXCo7U'
@@ -49,7 +49,7 @@ newer systems. They can be used directly as follows::
False
>>> # encrypt password without associate user account
- >>> hash2 = pix.encrypt("password")
+ >>> hash2 = pix.hash("password")
>>> hash2
'NuLKvvWGg.x9HEKO'
diff --git a/docs/lib/passlib.hash.cisco_type7.rst b/docs/lib/passlib.hash.cisco_type7.rst
index e419201..a6efb16 100644
--- a/docs/lib/passlib.hash.cisco_type7.rst
+++ b/docs/lib/passlib.hash.cisco_type7.rst
@@ -23,7 +23,7 @@ This class can be used directly as follows::
>>> from passlib.hash import cisco_type7
>>> # encode password
- >>> h = cisco_type7.encrypt("password")
+ >>> h = cisco_type7.hash("password")
>>> h
'044B0A151C36435C0D'
diff --git a/docs/lib/passlib.hash.des_crypt.rst b/docs/lib/passlib.hash.des_crypt.rst
index 5ae26df..373259f 100644
--- a/docs/lib/passlib.hash.des_crypt.rst
+++ b/docs/lib/passlib.hash.des_crypt.rst
@@ -17,7 +17,7 @@ It can used directly as follows::
>>> from passlib.hash import des_crypt
>>> # generate new salt, encrypt password
- >>> hash = des_crypt.encrypt("password")
+ >>> hash = des_crypt.hash("password")
'JQMuyS6H.AGMo'
>>> # verify the password
diff --git a/docs/lib/passlib.hash.django_std.rst b/docs/lib/passlib.hash.django_std.rst
index 8daf54b..47209b5 100644
--- a/docs/lib/passlib.hash.django_std.rst
+++ b/docs/lib/passlib.hash.django_std.rst
@@ -42,7 +42,7 @@ These classes can be used directly as follows::
>>> from passlib.hash import django_pbkdf2_sha256 as handler
>>> # encrypt password
- >>> h = handler.encrypt("password")
+ >>> h = handler.hash("password")
>>> h
'pbkdf2_sha256$10000$s1w0UXDd00XB$+4ORmyvVWAQvoAEWlDgN34vlaJx1ZTZpa1pCSRey2Yk='
@@ -122,7 +122,7 @@ These classes can be used directly as follows::
>>> from passlib.hash import django_salted_sha1 as handler
>>> # encrypt password
- >>> h = handler.encrypt("password")
+ >>> h = handler.hash("password")
>>> h
'sha1$c6218$161d1ac8ab38979c5a31cbaba4a67378e7e60845'
diff --git a/docs/lib/passlib.hash.fshp.rst b/docs/lib/passlib.hash.fshp.rst
index 1ebcc6f..5fd88db 100644
--- a/docs/lib/passlib.hash.fshp.rst
+++ b/docs/lib/passlib.hash.fshp.rst
@@ -24,12 +24,12 @@ It can be used directly as follows::
>>> from passlib.hash import fshp
>>> # generate new salt, encrypt password
- >>> hash = fshp.encrypt("password")
+ >>> hash = fshp.hash("password")
>>> hash
'{FSHP1|16|16384}PtoqcGUetmVEy/uR8715TNqKa8+teMF9qZO1lA9lJNUm1EQBLPZ+qPRLeEPHqy6C'
>>> # the same, but with an explicit number of rounds, larger salt, and specific variant
- >>> fshp.encrypt("password", rounds=40000, salt_size=32, variant="sha512")
+ >>> fshp.hash("password", rounds=40000, salt_size=32, variant="sha512")
'{FSHP3|32|40000}cB8yE/CuADSgUTQZjWy+YTf/cvbU11D/rHNKiUiB6z4dIaO77U/rmNW
pgZcZllZbCra5GJ8ZfFRNwCHirPqvYTAnbaQQeFQbWym/frRrRev3buoygFQRYexl4091Pc5m'
diff --git a/docs/lib/passlib.hash.grub_pbkdf2_sha512.rst b/docs/lib/passlib.hash.grub_pbkdf2_sha512.rst
index 686f98d..fa8af77 100644
--- a/docs/lib/passlib.hash.grub_pbkdf2_sha512.rst
+++ b/docs/lib/passlib.hash.grub_pbkdf2_sha512.rst
@@ -57,7 +57,7 @@ The result is then encoded into hexadecimal.
>>> from passlib.hash import pbkdf2_sha512, grub_pbkdf2_sha512
>>> # given a pbkdf2_sha512 hash...
- >>> h = pbkdf2_sha512.encrypt("password")
+ >>> h = pbkdf2_sha512.hash("password")
>>> h
'$pbkdf2-sha512$6400$y6vYff3SihJiqumIrNXwGw$NobVwyUlVI52/Cvrguwli5fX6XgKHNUf7fWWS2VgoWEevaTCiZx4OCYhwGFwzUAuz/g1zQVSIf.9JEb0BEVEEA'
diff --git a/docs/lib/passlib.hash.hex_digests.rst b/docs/lib/passlib.hash.hex_digests.rst
index a9b8391..0f8efc0 100644
--- a/docs/lib/passlib.hash.hex_digests.rst
+++ b/docs/lib/passlib.hash.hex_digests.rst
@@ -23,7 +23,7 @@ and can be used directly as follows::
>>> from passlib.hash import hex_sha1 as hex_sha1
>>> # encrypt password
- >>> h = hex_sha1.encrypt("password")
+ >>> h = hex_sha1.hash("password")
>>> h
'5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8'
diff --git a/docs/lib/passlib.hash.ldap_crypt.rst b/docs/lib/passlib.hash.ldap_crypt.rst
index 745716f..ede7c5f 100644
--- a/docs/lib/passlib.hash.ldap_crypt.rst
+++ b/docs/lib/passlib.hash.ldap_crypt.rst
@@ -21,7 +21,7 @@ elsewhere in Passlib, and can be used directly as follows::
>>> from passlib.hash import ldap_md5_crypt
>>> # encrypt password
- >>> hash = ldap_md5_crypt.encrypt("password")
+ >>> hash = ldap_md5_crypt.hash("password")
>>> hash
'{CRYPT}$1$gwvn5BO0$3dyk8j.UTcsNUPrLMsU6/0'
diff --git a/docs/lib/passlib.hash.ldap_std.rst b/docs/lib/passlib.hash.ldap_std.rst
index 6440b49..12c2abd 100644
--- a/docs/lib/passlib.hash.ldap_std.rst
+++ b/docs/lib/passlib.hash.ldap_std.rst
@@ -15,7 +15,7 @@ and are can be used directly as follows::
>>> from passlib.hash import ldap_salted_md5 as lsm
>>> # encrypt password
- >>> hash = lsm.encrypt("password")
+ >>> hash = lsm.hash("password")
>>> hash
'{SMD5}OqsUXNHIhHbznxrqHoIM+ZT8DmE='
diff --git a/docs/lib/passlib.hash.lmhash.rst b/docs/lib/passlib.hash.lmhash.rst
index e0335de..0a9e627 100644
--- a/docs/lib/passlib.hash.lmhash.rst
+++ b/docs/lib/passlib.hash.lmhash.rst
@@ -25,7 +25,7 @@ This class can be used directly as follows::
>>> from passlib.hash import lmhash
>>> # encrypt password
- >>> h = lmhash.encrypt("password")
+ >>> h = lmhash.hash("password")
>>> h
'e52cac67419a9a224a3b108f3fa6cb6d'
@@ -124,7 +124,7 @@ the handling of non-ASCII characters.
Thus if an application wishes to provide support for non-ASCII passwords,
it must decide which encoding to use. Passlib uses ``cp437`` as a
default, but this may need to be overridden via
- ``lmhash.encrypt(secret, encoding="some-other-codec")``.
+ ``lmhash.hash(secret, encoding="some-other-codec")``.
All known encodings are ``us-ascii``-compatible, so for ASCII passwords,
the default should be sufficient.
diff --git a/docs/lib/passlib.hash.md5_crypt.rst b/docs/lib/passlib.hash.md5_crypt.rst
index 0cc705e..3105c26 100644
--- a/docs/lib/passlib.hash.md5_crypt.rst
+++ b/docs/lib/passlib.hash.md5_crypt.rst
@@ -32,7 +32,7 @@ The :class:`!md5_crypt` class can be can be used directly as follows::
>>> from passlib.hash import md5_crypt
>>> # generate new salt, encrypt password
- >>> h = md5_crypt.encrypt("password")
+ >>> h = md5_crypt.hash("password")
>>> h
'$1$3azHgidD$SrJPt7B.9rekpmwJwtON31'
@@ -43,7 +43,7 @@ The :class:`!md5_crypt` class can be can be used directly as follows::
False
>>> # encrypt password using cisco-compatible 4-char salt
- >>> md5_crypt.encrypt("password", salt_size=4)
+ >>> md5_crypt.hash("password", salt_size=4)
'$1$wu98$9UuD3hvrwehnqyF1D548N0'
.. seealso::
diff --git a/docs/lib/passlib.hash.msdcc.rst b/docs/lib/passlib.hash.msdcc.rst
index 610a358..6c72aff 100644
--- a/docs/lib/passlib.hash.msdcc.rst
+++ b/docs/lib/passlib.hash.msdcc.rst
@@ -28,7 +28,7 @@ This class can be used directly as follows::
>>> from passlib.hash import msdcc
>>> # encrypt password using specified username
- >>> hash = msdcc.encrypt("password", user="Administrator")
+ >>> hash = msdcc.hash("password", user="Administrator")
>>> hash
'25fd08fa89795ed54207e6e8442a6ca0'
diff --git a/docs/lib/passlib.hash.msdcc2.rst b/docs/lib/passlib.hash.msdcc2.rst
index 8a100e8..203d957 100644
--- a/docs/lib/passlib.hash.msdcc2.rst
+++ b/docs/lib/passlib.hash.msdcc2.rst
@@ -22,7 +22,7 @@ This class can be used directly as follows::
>>> from passlib.hash import msdcc2
>>> # encrypt password using specified username
- >>> hash = msdcc2.encrypt("password", user="Administrator")
+ >>> hash = msdcc2.hash("password", user="Administrator")
>>> hash
'4c253e4b65c007a8cd683ea57bc43c76'
diff --git a/docs/lib/passlib.hash.mssql2000.rst b/docs/lib/passlib.hash.mssql2000.rst
index 8594047..416bce6 100644
--- a/docs/lib/passlib.hash.mssql2000.rst
+++ b/docs/lib/passlib.hash.mssql2000.rst
@@ -20,7 +20,7 @@ This class can be used directly as follows::
>>> from passlib.hash import mssql2000 as m20
>>> # encrypt password
- >>> h = m20.encrypt("password")
+ >>> h = m20.hash("password")
>>> h
'0x0100200420C4988140FD3920894C3EDC188E94F428D57DAD5905F6CC1CBAF950CAD4C63F272B2C91E4DEEB5E6444'
diff --git a/docs/lib/passlib.hash.mssql2005.rst b/docs/lib/passlib.hash.mssql2005.rst
index cca8026..3b997da 100644
--- a/docs/lib/passlib.hash.mssql2005.rst
+++ b/docs/lib/passlib.hash.mssql2005.rst
@@ -19,7 +19,7 @@ This class can be used directly as follows::
>>> from passlib.hash import mssql2005 as m25
>>> # encrypt password
- >>> h = m25.encrypt("password")
+ >>> h = m25.hash("password")
>>> h
'0x01006ACDF9FF5D2E211B392EEF1175EFFE13B3A368CE2F94038B'
diff --git a/docs/lib/passlib.hash.mysql323.rst b/docs/lib/passlib.hash.mysql323.rst
index 1d09001..207602b 100644
--- a/docs/lib/passlib.hash.mysql323.rst
+++ b/docs/lib/passlib.hash.mysql323.rst
@@ -24,7 +24,7 @@ That aside, this class can be used as follows::
>>> from passlib.hash import mysql323
>>> # encrypt password
- >>> mysql323.encrypt("password")
+ >>> mysql323.hash("password")
'5d2e19393cc5ef67'
>>> # verify correct password
diff --git a/docs/lib/passlib.hash.nthash.rst b/docs/lib/passlib.hash.nthash.rst
index 92cea05..c93dff4 100644
--- a/docs/lib/passlib.hash.nthash.rst
+++ b/docs/lib/passlib.hash.nthash.rst
@@ -23,7 +23,7 @@ This class can be used directly as follows::
>>> from passlib.hash import nthash
>>> # encrypt password
- >>> h = nthash.encrypt("password")
+ >>> h = nthash.hash("password")
>>> h
'8846f7eaee8fb117ad06bdd830b7586c'
@@ -66,7 +66,7 @@ NTHASH digest. An example digest (of ``password``) is
It has no salt and a single fixed round.
- The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept no optional keywords.
+ The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept no optional keywords.
.. versionchanged:: 1.6
This hash was named ``nthash`` under previous releases of Passlib.
diff --git a/docs/lib/passlib.hash.oracle10.rst b/docs/lib/passlib.hash.oracle10.rst
index a274c0f..d2c6524 100644
--- a/docs/lib/passlib.hash.oracle10.rst
+++ b/docs/lib/passlib.hash.oracle10.rst
@@ -17,7 +17,7 @@ a username for all encrypt/verify operations)::
>>> from passlib.hash import oracle10 as oracle10
>>> # encrypt password using specified username
- >>> hash = oracle10.encrypt("password", user="username")
+ >>> hash = oracle10.hash("password", user="username")
>>> hash
'872805F3F4C83365'
diff --git a/docs/lib/passlib.hash.oracle11.rst b/docs/lib/passlib.hash.oracle11.rst
index b8a0d92..0953ead 100644
--- a/docs/lib/passlib.hash.oracle11.rst
+++ b/docs/lib/passlib.hash.oracle11.rst
@@ -11,7 +11,7 @@ This class can be can be used directly as follows::
>>> from passlib.hash import oracle11 as oracle11
>>> # generate new salt, encrypt password
- >>> hash = oracle11.encrypt("password")
+ >>> hash = oracle11.hash("password")
>>> hash
'S:4143053633E59B4992A8EA17D2FF542C9EDEB335C886EED9C80450C1B4E6'
diff --git a/docs/lib/passlib.hash.pbkdf2_digest.rst b/docs/lib/passlib.hash.pbkdf2_digest.rst
index 9a2ec95..8c5bca6 100644
--- a/docs/lib/passlib.hash.pbkdf2_digest.rst
+++ b/docs/lib/passlib.hash.pbkdf2_digest.rst
@@ -27,12 +27,12 @@ All of these classes can be used directly as follows::
>>> from passlib.hash import pbkdf2_sha256
>>> # generate new salt, encrypt password
- >>> hash = pbkdf2_sha256.encrypt("password")
+ >>> hash = pbkdf2_sha256.hash("password")
>>> hash
'$pbkdf2-sha256$6400$0ZrzXitFSGltTQnBWOsdAw$Y11AchqV4b0sUisdZd0Xr97KWoymNE0LNNrnEgY4H9M'
>>> # same, but with an explicit number of rounds and salt length
- >>> pbkdf2_sha256.encrypt("password", rounds=8000, salt_size=10)
+ >>> pbkdf2_sha256.hash("password", rounds=8000, salt_size=10)
'$pbkdf2-sha256$8000$XAuBMIYQQogxRg$tRRlz8hYn63B9LYiCd6PRo6FMiunY9ozmMMI3srxeRE'
>>> # verify the password
diff --git a/docs/lib/passlib.hash.plaintext.rst b/docs/lib/passlib.hash.plaintext.rst
index 2b1fb91..0d16a11 100644
--- a/docs/lib/passlib.hash.plaintext.rst
+++ b/docs/lib/passlib.hash.plaintext.rst
@@ -13,7 +13,7 @@ It can be used directly as follows::
>>> from passlib.hash import plaintext as plaintext
>>> # "encrypt" password
- >>> plaintext.encrypt("password")
+ >>> plaintext.hash("password")
'password'
>>> # verify password
diff --git a/docs/lib/passlib.hash.postgres_md5.rst b/docs/lib/passlib.hash.postgres_md5.rst
index bea5de8..e14dc18 100644
--- a/docs/lib/passlib.hash.postgres_md5.rst
+++ b/docs/lib/passlib.hash.postgres_md5.rst
@@ -21,7 +21,7 @@ That aside, this class can be used directly as follows::
>>> from passlib.hash import postgres_md5
>>> # encrypt password using specified username
- >>> hash = postgres_md5.encrypt("password", user="username")
+ >>> hash = postgres_md5.hash("password", user="username")
>>> hash
'md55a231fcdb710d73268c4f44283487ba2'
diff --git a/docs/lib/passlib.hash.rst b/docs/lib/passlib.hash.rst
index 3dcda1c..a86416c 100644
--- a/docs/lib/passlib.hash.rst
+++ b/docs/lib/passlib.hash.rst
@@ -26,8 +26,8 @@ All of the hashes in this module can used in two ways:
>>> # import the desired hash
>>> from passlib.hash import md5_crypt
- >>> # hash the password - encrypt() takes care of salt generation, unicode encoding, etc.
- >>> hash = md5_crypt.encrypt("password")
+ >>> # hash the password - hash() takes care of salt generation, unicode encoding, etc.
+ >>> hash = md5_crypt.hash("password")
>>> hash
'$1$IU54yC7Y$nI1wF8ltcRvaRHwMIjiJq1'
@@ -46,10 +46,10 @@ All of the hashes in this module can used in two ways:
>>> pwd_context = CryptContext(schemes=["md5_crypt", "des_crypt"])
>>> # hash two different passwords (context objects used the first scheme as the default)
- >>> hash1 = pwd_context.encrypt("password")
+ >>> hash1 = pwd_context.hash("password")
>>> hash1
'$1$2y72Yi12$o6Yu2OyjN.9FiK.9HJ7i5.'
- >>> hash2 = pwd_context.encrypt("letmein", scheme="des_crypt")
+ >>> hash2 = pwd_context.hash("letmein", scheme="des_crypt")
>>> hash2
'0WMdk/ven8bok'
diff --git a/docs/lib/passlib.hash.scram.rst b/docs/lib/passlib.hash.scram.rst
index bcd98d4..b1b6c3c 100644
--- a/docs/lib/passlib.hash.scram.rst
+++ b/docs/lib/passlib.hash.scram.rst
@@ -37,14 +37,14 @@ This class can be used like any other Passlib hash, as follows::
>>> from passlib.hash import scram
>>> # generate new salt, encrypt password against default list of algorithms
- >>> hash = scram.encrypt("password")
+ >>> hash = scram.hash("password")
>>> hash
'$scram$6400$.Z/znnNOKWUsBaCU$sha-1=cRseQyJpnuPGn3e6d6u6JdJWk.0,sha-256=5G
cjEbRaUIIci1r6NAMdI9OPZbxl9S5CFR6la9CHXYc,sha-512=.DHbIm82ajXbFR196Y.9Ttbs
gzvGjbMeuWCtKve8TPjRMNoZK9EGyHQ6y0lW9OtWdHZrDZbBUhB9ou./VI2mlw'
>>> # same, but with an explicit number of rounds
- >>> scram.encrypt("password", rounds=8000)
+ >>> scram.hash("password", rounds=8000)
'$scram$8000$Y0zp/R/DeO89h/De$sha-1=eE8dq1f1P1hZm21lfzsr3CMbiEA,sha-256=Nf
kaDFMzn/yHr/HTv7KEFZqaONo6psRu5LBBFLEbZ.o,sha-512=XnGG11X.J2VGSG1qTbkR3FVr
9j5JwsnV5Fd094uuC.GtVDE087m8e7rGoiVEgXnduL48B2fPsUD9grBjURjkiA'
@@ -64,7 +64,7 @@ Additionally, this class provides a number of useful methods for SCRAM-specific
* You can override the default list of digests, and/or the number of iterations::
- >>> hash = scram.encrypt("password", rounds=1000, algs="sha-1,sha-256,md5")
+ >>> hash = scram.hash("password", rounds=1000, algs="sha-1,sha-256,md5")
>>> hash
'$scram$1000$RsgZo7T2/l8rBUBI$md5=iKsH555d3ctn795Za4S7bQ,sha-1=dRcE2AUjALLF
tX5DstdLCXZ9Afw,sha-256=WYE/LF7OntriUUdFXIrYE19OY2yL0N5qsQmdPNFn7JE'
diff --git a/docs/lib/passlib.hash.sha256_crypt.rst b/docs/lib/passlib.hash.sha256_crypt.rst
index 800c7a4..069fc19 100644
--- a/docs/lib/passlib.hash.sha256_crypt.rst
+++ b/docs/lib/passlib.hash.sha256_crypt.rst
@@ -18,12 +18,12 @@ This class can be used directly as follows::
>>> from passlib.hash import sha256_crypt
>>> # generate new salt, encrypt password
- >>> hash = sha256_crypt.encrypt("password")
+ >>> hash = sha256_crypt.hash("password")
>>> hash
'$5$rounds=80000$wnsT7Yr92oJoP28r$cKhJImk5mfuSKV9b3mumNzlbstFUplKtQXXMo4G6Ep5'
>>> # same, but with explict number of rounds
- >>> sha256_crypt.encrypt("password", rounds=12345)
+ >>> sha256_crypt.hash("password", rounds=12345)
'$5$rounds=12345$q3hvJE5mn5jKRsW.$BbbYTFiaImz9rTy03GGi.Jf9YY5bmxN0LU3p3uI1iUB'
>>> # verify password
diff --git a/docs/lib/passlib.hash.unix_disabled.rst b/docs/lib/passlib.hash.unix_disabled.rst
index a112147..1159787 100644
--- a/docs/lib/passlib.hash.unix_disabled.rst
+++ b/docs/lib/passlib.hash.unix_disabled.rst
@@ -14,7 +14,7 @@ It can be used directly as follows::
>>> from passlib.hash import unix_disabled
>>> # 'encrypting' a password always results in "!" or "*"
- >>> unix_disabled.encrypt("password")
+ >>> unix_disabled.hash("password")
'!'
>>> # verifying will fail for all passwords and hashes
diff --git a/docs/lib/passlib.hosts.rst b/docs/lib/passlib.hosts.rst
index cef0335..3a0a02e 100644
--- a/docs/lib/passlib.hosts.rst
+++ b/docs/lib/passlib.hosts.rst
@@ -29,7 +29,7 @@ Each of the objects in this module can be imported directly::
Encrypting a password is simple (and salt generation is handled automatically)::
- >>> hash = linux_context.encrypt("toomanysecrets")
+ >>> hash = linux_context.hash("toomanysecrets")
>>> hash
'$5$rounds=84740$fYChCy.52EzebF51$9bnJrmTf2FESI93hgIBFF4qAfysQcKoB0veiI0ZeYU4'
@@ -47,7 +47,7 @@ You can also identify hashes::
Or encrypt using a specific algorithm::
>>> linux_context.schemes()
('sha512_crypt', 'sha256_crypt', 'md5_crypt', 'des_crypt', 'unix_disabled')
- >>> linux_context.encrypt("password", scheme="des_crypt")
+ >>> linux_context.hash("password", scheme="des_crypt")
'2fmLLcoHXuQdI'
>>> linux_context.identify('2fmLLcoHXuQdI')
'des_crypt'
diff --git a/docs/new_app_quickstart.rst b/docs/new_app_quickstart.rst
index 375231b..f2cad98 100644
--- a/docs/new_app_quickstart.rst
+++ b/docs/new_app_quickstart.rst
@@ -20,7 +20,7 @@ all they need to do is the following::
>>> from passlib.apps import custom_app_context as pwd_context
>>> # encrypting a password...
- >>> hash = pwd_context.encrypt("somepass")
+ >>> hash = pwd_context.hash("somepass")
>>> # verifying a password...
>>> ok = pwd_context.verify("somepass", hash)
@@ -29,7 +29,7 @@ all they need to do is the following::
>>> # the custom_app_context is preconfigured so that
>>> # if the category is set to "admin" instead of None,
>>> # it uses a stronger setting of 80000 rounds:
- >>> hash = pwd_context.encrypt("somepass", category="admin")
+ >>> hash = pwd_context.hash("somepass", category="admin")
For applications which started using this preset, but whose needs
have grown beyond it, it is recommended to create your own :mod:`CryptContext <passlib.context>`
@@ -217,7 +217,7 @@ To start using your CryptContext, import the context you created wherever it's n
>>> from myapp.model.security import pwd_context
>>> # encrypting a password...
- >>> hash = pwd_context.encrypt("somepass")
+ >>> hash = pwd_context.hash("somepass")
>>> hash
'$pbkdf2-sha256$7252$qKFNyMYTmgQDCFDS.jRJDQ$sms3/EWbs4/3k3aOoid5azwq3HPZKVpUUrAsCfjrN6M'
diff --git a/docs/password_hash_api.rst b/docs/password_hash_api.rst
index 67e2a0b..279ff82 100644
--- a/docs/password_hash_api.rst
+++ b/docs/password_hash_api.rst
@@ -24,7 +24,7 @@ defined by the following abstract base class:
While it offers a number of methods and attributes,
but most applications will only need the two primary methods:
- * :meth:`~PasswordHash.encrypt` - generate new salt, return hash of password.
+ * :meth:`~PasswordHash.hash` - generate new salt, return hash of password.
* :meth:`~PasswordHash.verify` - verify password against existing hash.
While not needed by most applications, the following methods
@@ -47,25 +47,25 @@ Usage Examples
==============
The following code shows how to use the primary
methods of the :class:`~passlib.ifc.PasswordHash` interface --
-:meth:`~PasswordHash.encrypt` and :meth:`~PasswordHash.verify` --
+:meth:`~PasswordHash.hash` and :meth:`~PasswordHash.verify` --
using the :class:`~passlib.hash.sha256_crypt` hash as an example::
>>> # import the handler class
>>> from passlib.hash import sha256_crypt
>>> # hash a password using the default settings:
- >>> hash = sha256_crypt.encrypt("password")
+ >>> hash = sha256_crypt.hash("password")
>>> hash
'$5$rounds=40000$HIo6SCnVL9zqF8TK$y2sUnu13gp4cv0YgLQMW56PfQjWaTyiHjVbXTgleYG9'
- >>> # note that each call to encrypt() generates a new salt,
+ >>> # note that each call to hash() generates a new salt,
>>> # and thus the contents of the hash will differ, despite using the same password:
- >>> sha256_crypt.encrypt("password")
+ >>> sha256_crypt.hash("password")
'$5$rounds=40000$1JfxoiYM5Pxokyh8$ez8uV8jjXW7SjpaTg2vHJmx3Qn36uyZpjhyC9AfBi7B'
>>> # if the hash supports a variable number of iterations (which sha256_crypt does),
>>> # you can override the default value via the 'rounds' keyword:
- >>> sha256_crypt.encrypt("password", rounds=12345)
+ >>> sha256_crypt.hash("password", rounds=12345)
'$5$rounds=12345$UeVpHaN2YFDwBoeJ$NJN8DwVZ4UfQw6.ijJZNWoZtk1Ivi5YfKCDsI2HzSq2'
^^^^^
@@ -87,7 +87,7 @@ common use-cases, such as how to use the :meth:`~PasswordHash.identify` method::
>>> # attempting to call verify() with another algorithm's hash will result in a ValueError:
>>> from passlib.hash import sha256_crypt, md5_crypt
- >>> other_hash = md5_crypt.encrypt("password")
+ >>> other_hash = md5_crypt.hash("password")
>>> sha256_crypt.verify("password", other_hash)
Traceback (most recent call last):
<traceback omitted>
@@ -95,20 +95,20 @@ common use-cases, such as how to use the :meth:`~PasswordHash.identify` method::
>>> # this can be prevented by using the identify method,
>>> # determines whether a hash belongs to a given algorithm:
- >>> hash = sha256_crypt.encrypt("password")
+ >>> hash = sha256_crypt.hash("password")
>>> sha256_crypt.identify(hash)
True
>>> sha256_crypt.identify(other_hash)
False
-While the initial :meth:`~PasswordHash.encrypt` example works for most hashes,
+While the initial :meth:`~PasswordHash.hash` example works for most hashes,
a small number of algorithms require you provide external data
(such as a username) every time a hash is calculated.
An example of this is the :class:`~passlib.hash.oracle10` algorithm::
>>> # for oracle10, encrypt requires a username:
>>> from passlib.hash import oracle10
- >>> hash = oracle10.encrypt("secret", user="admin")
+ >>> hash = oracle10.hash("secret", user="admin")
'B858CE295C95193F'
>>> # the difference between this and something like the rounds setting (above)
@@ -123,7 +123,7 @@ An example of this is the :class:`~passlib.hash.oracle10` algorithm::
False
>>> # forgetting to include the username when it's required will cause a TypeError:
- >>> hash = oracle10.encrypt("password")
+ >>> hash = oracle10.hash("password")
Traceback (most recent call last):
<traceback omitted>
TypeError: user must be unicode or bytes, not None
@@ -139,13 +139,13 @@ An example of this is the :class:`~passlib.hash.oracle10` algorithm::
Primary Methods
===============
Most applications will only need to use two methods:
-:meth:`~PasswordHash.encrypt` to generate new hashes, and :meth:`~PasswordHash.verify`
+:meth:`~PasswordHash.hash` to generate new hashes, and :meth:`~PasswordHash.verify`
to check passwords against existing hashes.
These methods provide an easy interface for working with a password hash,
and abstract away details such as salt generation, hash normalization,
and hash comparison.
-.. classmethod:: PasswordHash.encrypt(secret, \*\*kwds)
+.. classmethod:: PasswordHash.hash(secret, \*\*kwds)
Digest password using format-specific algorithm,
returning resulting hash string.
@@ -165,6 +165,15 @@ and hash comparison.
and :attr:`~PasswordHash.context_kwds`.
Examples of common keywords include ``rounds`` and ``salt_size``.
+ :type config: str
+ :param config:
+
+ This optional parameter can be used provide an existing password hash,
+ and it's salt & configuration options will be used to hash the new password.
+ This option is mutually exclusive with all :attr:`~PasswordHash.setting_kwds`.
+
+ .. versionadded:: 1.7
+
:returns:
Resulting password hash, encoded in an algorithm-specific format.
This will always be an instance of :class:`!str`
@@ -199,6 +208,21 @@ and hash comparison.
clipped (though applications may set :ref:`relaxed=True <relaxed-keyword>`
to restore the old behavior).
+ .. versionchanged:: 1.7
+
+ This method was renamed from :meth:`encrypt`,
+ and the ``config`` parameter was added.
+
+.. classmethod:: PasswordHash.encrypt(secret, \*\*kwds)
+
+ Legacy alias for :meth:`hash`.
+
+ .. deprecated:: 1.7
+
+ This method was renamed to :meth:`!hash` in version 1.7.
+ This alias will be removed in version 2.0, and should only
+ be used for compatibility with Passlib 1.3 - 1.6.
+
.. classmethod:: PasswordHash.verify(secret, hash, \*\*context_kwds)
Verify a secret using an existing hash.
@@ -224,7 +248,7 @@ and hash comparison.
The ones that do typically require external contextual information
in order to calculate the digest. For these hashes,
the values must match the ones passed to the original
- :meth:`~PasswordHash.encrypt` call when the hash was generated,
+ :meth:`~PasswordHash.hash` call when the hash was generated,
or the password will not verify.
These additional keywords are algorithm-specific, and will be listed
@@ -243,7 +267,7 @@ and hash comparison.
:raises ValueError:
* if ``hash`` does not match this algorithm's format.
* if the ``secret`` contains forbidden characters (see
- :meth:`~PasswordHash.encrypt`).
+ :meth:`~PasswordHash.hash`).
* if a configuration/salt string generated by :meth:`~PasswordHash.genconfig`
is passed in as the value for ``hash`` (these strings look
similar to a full hash, but typically lack the digest portion
@@ -289,15 +313,24 @@ using the provided configuration string.
.. seealso::
- Most applications will find :meth:`~PasswordHash.encrypt` much more useful,
+ Most applications will find :meth:`~PasswordHash.hash` much more useful,
as it combines the functionality of these two methods into one.
.. classmethod:: PasswordHash.genconfig(\*\*setting_kwds)
+ .. deprecated:: 1.7
+
+ As of 1.7, this method is deprecated, and slated for complete removal in Passlib 2.0.
+
+ For all known real-world uses, ``.hash("", **settings)``
+ should provide equivalent functionality.
+
+ This deprecation may be reversed if a use-case presents itself in the mean time.
+
Returns a configuration string encoding settings for hash generation.
This function takes in all the same :attr:`~PasswordHash.setting_kwds`
- as :meth:`~PasswordHash.encrypt`, fills in suitable defaults,
+ as :meth:`~PasswordHash.hash`, fills in suitable defaults,
and encodes the settings into a single "configuration" string,
suitable passing to :meth:`~PasswordHash.genhash`.
@@ -313,7 +346,7 @@ using the provided configuration string.
:raises ValueError, TypeError:
This function raises exceptions for the same
- reasons as :meth:`~PasswordHash.encrypt`.
+ reasons as :meth:`~PasswordHash.hash`.
.. note::
@@ -326,6 +359,13 @@ using the provided configuration string.
Encrypt secret using specified configuration string.
+ .. deprecated:: 1.7
+
+ The new :meth:`hash` method added in 1.7 supports a **config** keyword;
+ all calls to :meth:`!genhash` can be replaced with ``.hash(secret, config=config, **context)``.
+ The :meth:`!genhash` method will be removed in version 2.0, and should only
+ be used for compatibility with Passlib 1.3 - 1.6.
+
This takes in a password and a configuration string,
and returns a hash for that password.
@@ -349,7 +389,7 @@ using the provided configuration string.
The ones that do typically require external contextual information
in order to calculate the digest. For these hashes,
the values must match the ones passed to the original
- :meth:`~PasswordHash.encrypt` call when the hash was generated,
+ :meth:`~PasswordHash.hash` call when the hash was generated,
or the password will not verify.
These additional keywords are algorithm-specific, and will be listed
@@ -363,7 +403,7 @@ using the provided configuration string.
:raises ValueError, TypeError:
This function raises exceptions for the same
- reasons as :meth:`~PasswordHash.encrypt`.
+ reasons as :meth:`~PasswordHash.hash`.
.. warning::
@@ -440,7 +480,7 @@ the hashes in passlib:
.. attribute:: PasswordHash.setting_kwds
- Tuple listing the keywords supported by :meth:`~PasswordHash.encrypt`
+ Tuple listing the keywords supported by :meth:`~PasswordHash.hash`
and :meth:`~PasswordHash.genconfig` that control hash generation, and which will
be encoded into the resulting hash.
@@ -519,7 +559,7 @@ the hashes in passlib:
.. _relaxed-keyword:
``relaxed``
- By default, passing an invalid value to :meth:`~PasswordHash.encrypt`
+ By default, passing an invalid value to :meth:`~PasswordHash.hash`
will result in a :exc:`ValueError`. However, if ``relaxed=True``
then Passlib will attempt to correct the error and (if successful)
issue a :exc:`~passlib.exc.PasslibHashWarning` instead.
@@ -536,7 +576,7 @@ the hashes in passlib:
.. attribute:: PasswordHash.context_kwds
- Tuple listing the keywords supported by :meth:`~PasswordHash.encrypt`,
+ Tuple listing the keywords supported by :meth:`~PasswordHash.hash`,
:meth:`~PasswordHash.verify`, and :meth:`~PasswordHash.genhash` affect the hash, but are
not encoded within it, and thus must be provided each time
the hash is calculated.
@@ -644,7 +684,7 @@ and the following attributes should be defined:
.. attribute:: PasswordHash.default_rounds
The default number of rounds that will be used if none is explicitly
- provided to :meth:`~PasswordHash.encrypt`.
+ provided to :meth:`~PasswordHash.hash`.
This will always be an integer between :attr:`~PasswordHash.min_rounds`
and :attr:`~PasswordHash.max_rounds`.
diff --git a/passlib/apache.py b/passlib/apache.py
index cb166b9..1aea738 100644
--- a/passlib/apache.py
+++ b/passlib/apache.py
@@ -589,7 +589,7 @@ class HtpasswdFile(_CommonFile):
The old alias is deprecated, and will be removed in Passlib 1.8.
"""
user = self._encode_user(user)
- hash = self.context.encrypt(password)
+ hash = self.context.hash(password)
if PY3:
hash = hash.encode(self.encoding)
existing = (user in self._records)
@@ -916,7 +916,7 @@ class HtdigestFile(_CommonFile):
realm = self._encode_realm(realm)
key = (user, realm)
existing = (key in self._records)
- hash = htdigest.encrypt(password, user, realm, encoding=self.encoding)
+ hash = htdigest.hash(password, user, realm, encoding=self.encoding)
if PY3:
hash = hash.encode(self.encoding)
self._records[key] = hash
diff --git a/passlib/context.py b/passlib/context.py
index 86a69b4..5105c37 100644
--- a/passlib/context.py
+++ b/passlib/context.py
@@ -590,7 +590,7 @@ class _CryptRecord(object):
# cloned from custom_handler.
genconfig = None
- encrypt = None
+ hash = None
verify = None
identify = None
genhash = None
@@ -640,10 +640,9 @@ class _CryptRecord(object):
# these aren't wrapped by _CryptRecord, copy them directly from handler.
self.genconfig = custom_handler.genconfig
- self.encrypt = custom_handler.encrypt
+ self.hash = custom_handler.hash
self.verify = custom_handler.verify
self.identify = custom_handler.identify
- self.genhash = custom_handler.genhash
#===================================================================
# virtual attrs
@@ -1207,7 +1206,7 @@ class _CryptConfig(object):
# main CryptContext class
#=============================================================================
class CryptContext(object):
- """Helper for encrypting passwords using different algorithms.
+ """Helper for hashing & verifying passwords using multiple algorithms.
Instances of this class allow applications to choose a specific
set of hash algorithms which they wish to support, set limits and defaults
@@ -1731,7 +1730,7 @@ class CryptContext(object):
return self._get_record(scheme, category).deprecated
def default_scheme(self, category=None, resolve=False):
- """return name of scheme that :meth:`encrypt` will use by default.
+ """return name of scheme that :meth:`hash` will use by default.
:type resolve: bool
:arg resolve:
@@ -2008,7 +2007,7 @@ class CryptContext(object):
or is otherwise outside of the bounds specified by the policy
(e.g. the number of rounds is lower than :ref:`min_rounds <context-min-rounds-option>`
configuration for that algorithm).
- If so, the password should be re-encrypted using :meth:`encrypt`
+ If so, the password should be re-hashed using :meth:`hash`
Otherwise, it will return ``False``.
:type hash: unicode or bytes
@@ -2066,95 +2065,31 @@ class CryptContext(object):
"""
return self.needs_update(hash, scheme, category)
+ @deprecated_method(deprecated="1.7", removed="2.0", replacement="CryptContext.hash('', **kwds)")
def genconfig(self, scheme=None, category=None, **settings):
"""Generate a config string for specified scheme.
- This wraps the :meth:`~passlib.ifc.PasswordHash.genconfig`
- method of the appropriate algorithm, using the default if
- one is not specified.
- The main difference between this and calling a hash's
- :meth:`!genconfig` method directly is that this way, the CryptContext
- will add in any hash-specific options, such as the default rounds.
+ .. deprecated:: 1.7
- :type scheme: str or None
- :param scheme:
-
- Optional scheme to use. Scheme must be one of the ones
- configured for this context (see the
- :ref:`schemes <context-schemes-option>` option).
- If no scheme is specified, the configured default
- will be used.
-
- :type category: str or None
- :param category:
- Optional :ref:`user category <user-categories>`.
- If specified, this will cause any category-specific defaults to
- be used when hashing the password (e.g. different default scheme,
- different default rounds values, etc).
-
- :param \*\*settings:
- All additional keywords are passed to the appropriate handler,
- and should match its :attr:`~passlib.ifc.PasswordHash.setting_kwds`.
-
- :returns:
- A configuration string suitable for passing to :meth:`~CryptContext.genhash`,
- encoding all the provided settings and defaults; or ``None``
- if the selected algorithm doesn't support configuration strings.
- The return value will always be a :class:`!str`.
+ As of 1.7, all calls to :meth:`!genconfig` should be replaced with
+ ``.hash("", **settings)``.
+ This method will be removed in version 2.0, and should only
+ be used for compatibility with Passlib 1.3 - 1.6.
"""
- return self._get_record(scheme, category).genconfig(**settings)
+ return self.hash(unicode(""), scheme, category, config=True, **settings)
+ @deprecated_method(deprecated="1.7", removed="2.0", replacement="CryptContext.hash(secret, config=config)")
def genhash(self, secret, config, scheme=None, category=None, **kwds):
"""Generate hash for the specified secret using another hash.
- This wraps the :meth:`~passlib.ifc.PasswordHash.genhash`
- method of the appropriate algorithm, identifying it based
- on the provided hash / configuration if a scheme is not specified
- explicitly.
+ .. deprecated:: 1.7
- :type secret: unicode or bytes
- :arg secret:
- the password to hash.
-
- :type config: unicode or bytes
- :arg hash:
- The hash or configuration string to extract the settings and salt
- from when hashing the password.
-
- :type scheme: str or None
- :param scheme:
-
- Optional scheme to use. Scheme must be one of the ones
- configured for this context (see the
- :ref:`schemes <context-schemes-option>` option).
- If no scheme is specified, it will be identified
- based on the value of *config*.
-
- :type category: str or None
- :param category:
- Optional :ref:`user category <user-categories>`.
- Ignored by this function, this parameter
- is provided for symmetry with the other methods.
-
- :param \*\*kwds:
- All additional keywords are passed to the appropriate handler,
- and should match its :attr:`~passlib.ifc.PasswordHash.context_kwds`.
-
- :returns:
- The secret as encoded by the specified algorithm and options.
- The return value will always be a :class:`!str`.
-
- :raises TypeError, ValueError:
- * if any of the arguments have an invalid type or value.
- * if the selected algorithm's underlying :meth:`~passlib.ifc.PasswordHash.genhash`
- method throws an error based on *secret* or the provided *kwds*.
+ As of 1.7, the :meth:`hash` function now supports a **hash** keyword;
+ all calls to :meth:`!genhash` can be replaced with ``.hash(secret, config=config, **context)``.
+ This method will be removed in version 2.0, and should only
+ be used for compatibility with Passlib 1.3 - 1.6.
"""
- # XXX: could insert normalization to preferred unicode encoding here
- record = self._get_record(scheme, category)
- strip_unused = self._strip_unused_context_kwds
- if strip_unused:
- strip_unused(kwds, record)
- return record.genhash(secret, config, **kwds)
+ return self.hash(secret, scheme, category, config=config, **kwds)
def identify(self, hash, category=None, resolve=False, required=False):
"""Attempt to identify which algorithm the hash belongs to.
@@ -2198,7 +2133,7 @@ class CryptContext(object):
else:
return record.scheme
- def encrypt(self, secret, scheme=None, category=None, **kwds):
+ def hash(self, secret, scheme=None, category=None, **kwds):
"""run secret through selected algorithm, returning resulting hash.
:type secret: unicode or bytes
@@ -2223,7 +2158,7 @@ class CryptContext(object):
:param \*\*kwds:
All other keyword options are passed to the selected algorithm's
- :meth:`PasswordHash.encrypt() <passlib.ifc.PasswordHash.encrypt>` method.
+ :meth:`PasswordHash.hash() <passlib.ifc.PasswordHash.hash>` method.
:returns:
The secret as encoded by the specified algorithm and options.
@@ -2232,16 +2167,32 @@ class CryptContext(object):
:raises TypeError, ValueError:
* If any of the arguments have an invalid type or value.
This includes any keywords passed to the underlying hash's
- :meth:`PasswordHash.encrypt() <passlib.ifc.PasswordHash.encrypt>` method.
+ :meth:`PasswordHash.hash() <passlib.ifc.PasswordHash.hash>` method.
.. seealso:: the :ref:`context-basic-example` example in the tutorial
"""
# XXX: could insert normalization to preferred unicode encoding here
+ if scheme is None:
+ config = kwds.get("config")
+ if not (config is None or config is True):
+ scheme = self.identify(config)
record = self._get_record(scheme, category)
strip_unused = self._strip_unused_context_kwds
if strip_unused:
strip_unused(kwds, record)
- return record.encrypt(secret, **kwds)
+ return record.hash(secret, **kwds)
+
+ @deprecated_method(deprecated="1.7", removed="2.0", replacement="CryptContext.hash()")
+ def encrypt(self, *args, **kwds):
+ """
+ Legacy alias for :meth:`hash`.
+
+ .. deprecated:: 1.7
+ This method was renamed to :meth:`!hash` in version 1.7.
+ This alias will be removed in version 2.0, and should only
+ be used for compatibility with Passlib 1.3 - 1.6.
+ """
+ return self.hash(*args, **kwds)
def verify(self, secret, hash, scheme=None, category=None, **kwds):
"""verify secret against an existing hash.
@@ -2308,7 +2259,7 @@ class CryptContext(object):
This is a convenience method which takes care of all the following:
first it verifies the password (:meth:`~CryptContext.verify`), if this is successfull
it checks if the hash needs updating (:meth:`~CryptContext.needs_update`), and if so,
- re-hashes the password (:meth:`~CryptContext.encrypt`), returning the replacement hash.
+ re-hashes the password (:meth:`~CryptContext.hash`), returning the replacement hash.
This series of steps is a very common task for applications
which wish to update deprecated hashes, and this call takes
care of all 3 steps efficiently.
@@ -2370,8 +2321,8 @@ class CryptContext(object):
if not record.verify(secret, hash, **clean_kwds):
return False, None
elif record.needs_update(hash, secret=secret):
- # NOTE: we re-encrypt with default scheme, not current one.
- return True, self.encrypt(secret, None, category, **kwds)
+ # NOTE: we re-hash with default scheme, not current one.
+ return True, self.hash(secret, None, category, **kwds)
else:
return True, None
diff --git a/passlib/ext/django/models.py b/passlib/ext/django/models.py
index 0349012..1337ce2 100644
--- a/passlib/ext/django/models.py
+++ b/passlib/ext/django/models.py
@@ -84,7 +84,7 @@ def _apply_patch():
else:
# NOTE: pulls _get_category from module globals
cat = _get_category(user)
- user.password = password_context.encrypt(password, category=cat)
+ user.password = password_context.hash(password, category=cat)
@_manager.monkeypatch(USER_PATH)
def check_password(user, password):
@@ -154,7 +154,7 @@ def _apply_patch():
# Django make_password() autogenerates a salt if salt is bool False (None / ''),
# so we only pass the keyword on if there's actually a fixed salt.
kwds['salt'] = salt
- return password_context.encrypt(password, **kwds)
+ return password_context.hash(password, **kwds)
@_manager.monkeypatch(HASHERS_PATH)
@lru_cache.lru_cache()
diff --git a/passlib/ext/django/utils.py b/passlib/ext/django/utils.py
index 07a11db..b20da79 100644
--- a/passlib/ext/django/utils.py
+++ b/passlib/ext/django/utils.py
@@ -247,7 +247,7 @@ class _PasslibHasherWrapper(object):
# hasher api
#=====================================================================
def salt(self):
- # NOTE: passlib's handler.encrypt() should generate new salt each time,
+ # NOTE: passlib's handler.hash() should generate new salt each time,
# so this just returns a special constant which tells
# encode() (below) not to pass a salt keyword along.
return _GEN_SALT_SIGNAL
@@ -267,8 +267,8 @@ class _PasslibHasherWrapper(object):
else:
kwds['rounds'] = self.rounds
elif rounds is not None or iterations is not None:
- warn("%s.encrypt(): 'rounds' and 'iterations' are ignored" % self.__name__)
- return self.passlib_handler.encrypt(password, **kwds)
+ warn("%s.hash(): 'rounds' and 'iterations' are ignored" % self.__name__)
+ return self.passlib_handler.hash(password, **kwds)
def safe_summary(self, encoded):
from django.contrib.auth.hashers import mask_hash
@@ -422,19 +422,9 @@ def _get_hasher(algorithm):
## return to_unicode(hash, "latin-1", "hash").startswith(self.ident)
##
## @property
-## def genconfig(self):
-## # XXX: not sure how to support this.
-## return None
-##
-## @property
-## def genhash(self, secret, config):
-## if config is not None:
-## # XXX: not sure how to support this.
-## raise NotImplementedError("genhash() for hashers not implemented")
-## return self.encrypt(secret)
-##
-## @property
-## def encrypt(self, secret, salt=None, **kwds):
+## def hash(self, secret, config=None, salt=None, **kwds):
+## if config:
+## raise NotImplementedError('hash(config) support missing')
## # NOTE: from how make_password() is coded, all hashers
## # should have salt param. but only some will have
## # 'iterations' parameter.
diff --git a/passlib/handlers/bcrypt.py b/passlib/handlers/bcrypt.py
index 23dca0a..88b755a 100644
--- a/passlib/handlers/bcrypt.py
+++ b/passlib/handlers/bcrypt.py
@@ -87,7 +87,7 @@ class bcrypt(uh.HasManyIdents, uh.HasRounds, uh.HasSalt, uh.HasManyBackends, uh.
It supports a fixed-length salt, and a variable number of rounds.
- The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+ The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
:type salt: str
:param salt:
@@ -593,7 +593,7 @@ class bcrypt_sha256(bcrypt):
It supports a fixed-length salt, and a variable number of rounds.
- The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept
+ The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept
all the same optional keywords as the base :class:`bcrypt` hash.
.. versionadded:: 1.6.2
diff --git a/passlib/handlers/cisco.py b/passlib/handlers/cisco.py
index adf66a1..b382158 100644
--- a/passlib/handlers/cisco.py
+++ b/passlib/handlers/cisco.py
@@ -28,7 +28,7 @@ class cisco_pix(uh.HasUserContext, uh.StaticHandler):
It does a single round of hashing, and relies on the username
as the salt.
- The :meth:`~passlib.ifc.PasswordHash.encrypt`, :meth:`~passlib.ifc.PasswordHash.genhash`, and :meth:`~passlib.ifc.PasswordHash.verify` methods
+ The :meth:`~passlib.ifc.PasswordHash.hash`, :meth:`~passlib.ifc.PasswordHash.genhash`, and :meth:`~passlib.ifc.PasswordHash.verify` methods
have the following extra keyword:
:type user: str
@@ -128,7 +128,7 @@ class cisco_type7(uh.GenericHandler):
It has a simple 4-5 bit salt, but is nonetheless a reversible encoding
instead of a real hash.
- The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genhash` methods
+ The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genhash` methods
have the following optional keywords:
:type salt: int
@@ -167,18 +167,6 @@ class cisco_type7(uh.GenericHandler):
# methods
#===================================================================
@classmethod
- def genconfig(cls):
- return None
-
- @classmethod
- def genhash(cls, secret, config):
- # special case to handle ``config=None`` in same style as StaticHandler
- if config is None:
- return cls.encrypt(secret)
- else:
- return super(cisco_type7, cls).genhash(secret, config)
-
- @classmethod
def from_string(cls, hash):
hash = to_unicode(hash, "ascii", "hash")
if len(hash) < 2:
diff --git a/passlib/handlers/des_crypt.py b/passlib/handlers/des_crypt.py
index 1194e54..280c8b0 100644
--- a/passlib/handlers/des_crypt.py
+++ b/passlib/handlers/des_crypt.py
@@ -113,7 +113,7 @@ class des_crypt(uh.HasManyBackends, uh.HasSalt, uh.GenericHandler):
It supports a fixed-length salt.
- The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+ The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
:type salt: str
:param salt:
@@ -210,7 +210,7 @@ class bsdi_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler
It supports a fixed-length salt, and a variable number of rounds.
- The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+ The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
:type salt: str
:param salt:
@@ -369,7 +369,7 @@ class bigcrypt(uh.HasSalt, uh.GenericHandler):
It supports a fixed-length salt.
- The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+ The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
:type salt: str
:param salt:
@@ -452,7 +452,7 @@ class crypt16(uh.HasSalt, uh.GenericHandler):
It supports a fixed-length salt.
- The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+ The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
:type salt: str
:param salt:
diff --git a/passlib/handlers/digests.py b/passlib/handlers/digests.py
index f33aabf..55c3f16 100644
--- a/passlib/handlers/digests.py
+++ b/passlib/handlers/digests.py
@@ -90,9 +90,13 @@ class htdigest(uh.PasswordHash):
default_encoding = "utf-8"
@classmethod
- def encrypt(cls, secret, user, realm, encoding=None):
+ def hash(cls, secret, user, realm, encoding=None, config=None):
# NOTE: this was deliberately written so that raw bytes are passed through
# unchanged, the encoding kwd is only used to handle unicode values.
+ if not (config is None or config is True):
+ # NOTE: 'config' is ignored, as this hash has no salting / other configuration.
+ # just have to make sure it's valid.
+ cls._norm_hash(config)
if not encoding:
encoding = cls.default_encoding
uh.validate_secret(secret)
@@ -117,7 +121,7 @@ class htdigest(uh.PasswordHash):
@classmethod
def verify(cls, secret, hash, user, realm, encoding="utf-8"):
hash = cls._norm_hash(hash)
- other = cls.encrypt(secret, user, realm, encoding)
+ other = cls.hash(secret, user, realm, encoding)
return consteq(hash, other)
@classmethod
@@ -128,16 +132,6 @@ class htdigest(uh.PasswordHash):
return False
return True
- @classmethod
- def genconfig(cls):
- return None
-
- @classmethod
- def genhash(cls, secret, config, user, realm, encoding="utf-8"):
- if config is not None:
- cls._norm_hash(config)
- return cls.encrypt(secret, user, realm, encoding)
-
#=============================================================================
# eof
#=============================================================================
diff --git a/passlib/handlers/django.py b/passlib/handlers/django.py
index 8ed7925..4f2dcfc 100644
--- a/passlib/handlers/django.py
+++ b/passlib/handlers/django.py
@@ -61,18 +61,13 @@ class DjangoSaltedHash(uh.HasSalt, uh.GenericHandler):
checksum_chars = uh.LOWER_HEX_CHARS
- @classproperty
- def _stub_checksum(cls):
- return cls.checksum_chars[0] * cls.checksum_size
-
@classmethod
def from_string(cls, hash):
salt, chk = uh.parse_mc2(hash, cls.ident, handler=cls)
return cls(salt=salt, checksum=chk)
def to_string(self):
- return uh.render_mc2(self.ident, self.salt,
- self.checksum or self._stub_checksum)
+ return uh.render_mc2(self.ident, self.salt, self.checksum)
class DjangoVariableHash(uh.HasRounds, DjangoSaltedHash):
"""base class providing common code for django hashes w/ variable rounds"""
@@ -86,15 +81,14 @@ class DjangoVariableHash(uh.HasRounds, DjangoSaltedHash):
return cls(rounds=rounds, salt=salt, checksum=chk)
def to_string(self):
- return uh.render_mc3(self.ident, self.rounds, self.salt,
- self.checksum or self._stub_checksum)
+ return uh.render_mc3(self.ident, self.rounds, self.salt, self.checksum)
class django_salted_sha1(DjangoSaltedHash):
"""This class implements Django's Salted SHA1 hash, and follows the :ref:`password-hash-api`.
It supports a variable-length salt, and uses a single round of SHA1.
- The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+ The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
:type salt: str
:param salt:
@@ -131,7 +125,7 @@ class django_salted_md5(DjangoSaltedHash):
It supports a variable-length salt, and uses a single round of MD5.
- The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+ The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
:type salt: str
:param salt:
@@ -243,7 +237,7 @@ class django_pbkdf2_sha256(DjangoVariableHash):
It supports a variable-length salt, and a variable number of rounds.
- The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+ The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
:type salt: str
:param salt:
@@ -294,7 +288,7 @@ class django_pbkdf2_sha1(django_pbkdf2_sha256):
It supports a variable-length salt, and a variable number of rounds.
- The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+ The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
:type salt: str
:param salt:
@@ -340,7 +334,7 @@ class django_des_crypt(uh.HasSalt, uh.GenericHandler):
It supports a fixed-length salt.
- The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+ The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
:type salt: str
:param salt:
@@ -365,7 +359,6 @@ class django_des_crypt(uh.HasSalt, uh.GenericHandler):
checksum_chars = salt_chars = uh.HASH64_CHARS
checksum_size = 11
min_salt_size = default_salt_size = 2
- _stub_checksum = u('.')*11
# NOTE: regarding duplicate salt field:
#
@@ -403,7 +396,7 @@ class django_des_crypt(uh.HasSalt, uh.GenericHandler):
def to_string(self):
salt = self.salt
- chk = salt[:2] + (self.checksum or self._stub_checksum)
+ chk = salt[:2] + self.checksum
if self.use_duplicate_salt:
# filling in salt field, so that we're compatible with django 1.0
return uh.render_mc2(self.ident, salt, chk)
@@ -438,14 +431,16 @@ class django_disabled(uh.StaticHandler):
.. versionchanged:: 1.6.2 added Django 1.6 support
"""
name = "django_disabled"
+ _hash_prefix = u("!")
+ # XXX: move this to StaticHandler, or wherever _hash_prefix is being used?
@classmethod
def identify(cls, hash):
hash = uh.to_unicode_for_identify(hash)
- return hash.startswith(u("!"))
+ return hash.startswith(cls._hash_prefix)
def _calc_checksum(self, secret):
- return u("!")
+ return u("") # prefix will get prepended
@classmethod
def verify(cls, secret, hash):
diff --git a/passlib/handlers/fshp.py b/passlib/handlers/fshp.py
index 0c082f1..553ec57 100644
--- a/passlib/handlers/fshp.py
+++ b/passlib/handlers/fshp.py
@@ -27,7 +27,7 @@ class fshp(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
It supports a variable-length salt, and a variable number of rounds.
- The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+ The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
:param salt:
Optional raw salt string.
@@ -168,12 +168,8 @@ class fshp(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
chk = data[salt_size:]
return cls(salt=salt, checksum=chk, rounds=rounds, variant=variant)
- @property
- def _stub_checksum(self):
- return b'\x00' * self.checksum_size
-
def to_string(self):
- chk = self.checksum or self._stub_checksum
+ chk = self.checksum
salt = self.salt
data = bascii_to_str(b64encode(salt+chk))
return "{FSHP%d|%d|%d}%s" % (self.variant, len(salt), self.rounds, data)
diff --git a/passlib/handlers/ldap_digests.py b/passlib/handlers/ldap_digests.py
index e92c658..9182647 100644
--- a/passlib/handlers/ldap_digests.py
+++ b/passlib/handlers/ldap_digests.py
@@ -61,10 +61,8 @@ class _SaltedBase64DigestHelper(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHand
checksum_chars = uh.PADDED_BASE64_CHARS
ident = None # required - prefix identifier
- checksum_size = None # required
_hash_func = None # required - hash function
_hash_regex = None # required - regexp to recognize hash
- _stub_checksum = None # required - default checksum to plug in
min_salt_size = max_salt_size = 4
# NOTE: openldap implementation uses 4 byte salt,
@@ -89,7 +87,7 @@ class _SaltedBase64DigestHelper(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHand
return cls(checksum=data[:cs], salt=data[cs:])
def to_string(self):
- data = (self.checksum or self._stub_checksum) + self.salt
+ data = self.checksum + self.salt
hash = self.ident + b64encode(data).decode("ascii")
return uascii_to_str(hash)
@@ -104,7 +102,7 @@ class _SaltedBase64DigestHelper(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHand
class ldap_md5(_Base64DigestHelper):
"""This class stores passwords using LDAP's plain MD5 format, and follows the :ref:`password-hash-api`.
- The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods have no optional keywords.
+ The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods have no optional keywords.
"""
name = "ldap_md5"
ident = u("{MD5}")
@@ -114,7 +112,7 @@ class ldap_md5(_Base64DigestHelper):
class ldap_sha1(_Base64DigestHelper):
"""This class stores passwords using LDAP's plain SHA1 format, and follows the :ref:`password-hash-api`.
- The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods have no optional keywords.
+ The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods have no optional keywords.
"""
name = "ldap_sha1"
ident = u("{SHA}")
@@ -126,7 +124,7 @@ class ldap_salted_md5(_SaltedBase64DigestHelper):
It supports a 4-16 byte salt.
- The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keyword:
+ The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keyword:
:type salt: bytes
:param salt:
@@ -159,14 +157,13 @@ class ldap_salted_md5(_SaltedBase64DigestHelper):
checksum_size = 16
_hash_func = md5
_hash_regex = re.compile(u(r"^\{SMD5\}(?P<tmp>[+/a-zA-Z0-9]{27,}={0,2})$"))
- _stub_checksum = b'\x00' * 16
class ldap_salted_sha1(_SaltedBase64DigestHelper):
"""This class stores passwords using LDAP's salted SHA1 format, and follows the :ref:`password-hash-api`.
It supports a 4-16 byte salt.
- The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keyword:
+ The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keyword:
:type salt: bytes
:param salt:
@@ -199,7 +196,6 @@ class ldap_salted_sha1(_SaltedBase64DigestHelper):
checksum_size = 20
_hash_func = sha1
_hash_regex = re.compile(u(r"^\{SSHA\}(?P<tmp>[+/a-zA-Z0-9]{32,}={0,2})$"))
- _stub_checksum = b'\x00' * 20
class ldap_plaintext(plaintext):
"""This class stores passwords in plaintext, and follows the :ref:`password-hash-api`.
@@ -208,7 +204,7 @@ class ldap_plaintext(plaintext):
except that it will identify a hash only if it does NOT begin with the ``{XXX}`` identifier prefix
used by RFC2307 passwords.
- The :meth:`~passlib.ifc.PasswordHash.encrypt`, :meth:`~passlib.ifc.PasswordHash.genhash`, and :meth:`~passlib.ifc.PasswordHash.verify` methods all require the
+ The :meth:`~passlib.ifc.PasswordHash.hash`, :meth:`~passlib.ifc.PasswordHash.genhash`, and :meth:`~passlib.ifc.PasswordHash.verify` methods all require the
following additional contextual keyword:
:type encoding: str
@@ -228,6 +224,12 @@ class ldap_plaintext(plaintext):
_2307_pat = re.compile(u(r"^\{\w+\}.*$"))
@classmethod
+ def genconfig(cls):
+ # Overridding plaintext.genconfig() since it returns "",
+ # but have to return non-empty value due to identify() below
+ return "!"
+
+ @classmethod
def identify(cls, hash):
# NOTE: identifies all strings EXCEPT those with {XXX} prefix
hash = uh.to_unicode_for_identify(hash)
diff --git a/passlib/handlers/md5_crypt.py b/passlib/handlers/md5_crypt.py
index 3d3cb8a..fd7d9fa 100644
--- a/passlib/handlers/md5_crypt.py
+++ b/passlib/handlers/md5_crypt.py
@@ -226,7 +226,7 @@ class md5_crypt(uh.HasManyBackends, _MD5_Common):
It supports a variable-length salt.
- The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+ The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
:type salt: str
:param salt:
@@ -304,7 +304,7 @@ class apr_md5_crypt(_MD5_Common):
It supports a variable-length salt.
- The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+ The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
:type salt: str
:param salt:
diff --git a/passlib/handlers/misc.py b/passlib/handlers/misc.py
index 71723bf..cafdc26 100644
--- a/passlib/handlers/misc.py
+++ b/passlib/handlers/misc.py
@@ -58,17 +58,6 @@ class unix_fallback(uh.StaticHandler):
super(unix_fallback, self).__init__(**kwds)
self.enable_wildcard = enable_wildcard
- @classmethod
- def genhash(cls, secret, config):
- # override default to preserve checksum
- if config is None:
- return cls.encrypt(secret)
- else:
- uh.validate_secret(secret)
- self = cls.from_string(config)
- self.checksum = self._calc_checksum(secret)
- return self.to_string()
-
def _calc_checksum(self, secret):
if self.checksum:
# NOTE: hash will generally be "!", but we want to preserve
@@ -97,7 +86,7 @@ class unix_disabled(uh.PasswordHash):
This class does not implement a hash, but instead matches the "disabled account"
strings found in ``/etc/shadow`` on most Unix variants. "encrypting" a password
will simply return the disabled account marker. It will reject all passwords,
- no matter the hash string. The :meth:`~passlib.ifc.PasswordHash.encrypt`
+ no matter the hash string. The :meth:`~passlib.ifc.PasswordHash.hash`
method supports one optional keyword:
:type marker: str
@@ -149,10 +138,6 @@ class unix_disabled(uh.PasswordHash):
return not hash or hash[0] in start
@classmethod
- def encrypt(cls, secret, marker=None):
- return cls.genhash(secret, None, marker)
-
- @classmethod
def verify(cls, secret, hash):
uh.validate_secret(secret)
if not cls.identify(hash): # handles typecheck
@@ -160,17 +145,13 @@ class unix_disabled(uh.PasswordHash):
return False
@classmethod
- def genconfig(cls):
- return None
-
- @classmethod
- def genhash(cls, secret, config, marker=None):
+ def hash(cls, secret, config=None, marker=None):
uh.validate_secret(secret)
- if config is not None and not cls.identify(config): # handles typecheck
- raise uh.exc.InvalidHashError(cls)
- if config:
+ if not (config is None or config is True):
# we want to preserve the existing str,
# since it might contain a disabled password hash ("!" + hash)
+ if not cls.identify(config):
+ raise uh.exc.InvalidHashError(cls)
return to_native_str(config, param="config")
# if None or empty string, replace with marker
if marker:
@@ -184,7 +165,7 @@ class unix_disabled(uh.PasswordHash):
class plaintext(uh.PasswordHash):
"""This class stores passwords in plaintext, and follows the :ref:`password-hash-api`.
- The :meth:`~passlib.ifc.PasswordHash.encrypt`, :meth:`~passlib.ifc.PasswordHash.genhash`, and :meth:`~passlib.ifc.PasswordHash.verify` methods all require the
+ The :meth:`~passlib.ifc.PasswordHash.hash`, :meth:`~passlib.ifc.PasswordHash.genhash`, and :meth:`~passlib.ifc.PasswordHash.verify` methods all require the
following additional contextual keyword:
:type encoding: str
@@ -212,7 +193,10 @@ class plaintext(uh.PasswordHash):
raise uh.exc.ExpectedStringError(hash, "hash")
@classmethod
- def encrypt(cls, secret, encoding=None):
+ def hash(cls, secret, encoding=None, config=None):
+ # NOTE: 'config' is ignored, as this hash has no salting / etc
+ if not (config is None or config is True or cls.identify(config)):
+ raise uh.exc.InvalidHashError(cls)
uh.validate_secret(secret)
if not encoding:
encoding = cls.default_encoding
@@ -225,17 +209,7 @@ class plaintext(uh.PasswordHash):
hash = to_native_str(hash, encoding, "hash")
if not cls.identify(hash):
raise uh.exc.InvalidHashError(cls)
- return consteq(cls.encrypt(secret, encoding), hash)
-
- @classmethod
- def genconfig(cls):
- return None
-
- @classmethod
- def genhash(cls, secret, hash, encoding=None):
- if hash is not None and not cls.identify(hash):
- raise uh.exc.InvalidHashError(cls)
- return cls.encrypt(secret, encoding)
+ return consteq(cls.hash(secret, encoding), hash)
#=============================================================================
# eof
diff --git a/passlib/handlers/mssql.py b/passlib/handlers/mssql.py
index 21d7133..5366618 100644
--- a/passlib/handlers/mssql.py
+++ b/passlib/handlers/mssql.py
@@ -104,7 +104,7 @@ class mssql2000(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
It supports a fixed-length salt.
- The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+ The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
:type salt: bytes
:param salt:
@@ -127,7 +127,6 @@ class mssql2000(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
setting_kwds = ("salt",)
checksum_size = 40
min_salt_size = max_salt_size = 4
- _stub_checksum = b"\x00" * 40
#===================================================================
# formatting
@@ -150,7 +149,7 @@ class mssql2000(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
return cls(salt=data[:4], checksum=data[4:])
def to_string(self):
- raw = self.salt + (self.checksum or self._stub_checksum)
+ raw = self.salt + self.checksum
# raw bytes format - BIDENT2 + raw
return "0x0100" + bascii_to_str(hexlify(raw).upper())
@@ -182,7 +181,7 @@ class mssql2005(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
It supports a fixed-length salt.
- The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+ The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
:type salt: bytes
:param salt:
@@ -206,7 +205,6 @@ class mssql2005(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
checksum_size = 20
min_salt_size = max_salt_size = 4
- _stub_checksum = b"\x00" * 20
#===================================================================
# formatting
@@ -228,7 +226,7 @@ class mssql2005(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
return cls(salt=data[:4], checksum=data[4:])
def to_string(self):
- raw = self.salt + (self.checksum or self._stub_checksum)
+ raw = self.salt + self.checksum
# raw bytes format - BIDENT2 + raw
return "0x0100" + bascii_to_str(hexlify(raw)).upper()
diff --git a/passlib/handlers/mysql.py b/passlib/handlers/mysql.py
index 7e31313..4a71253 100644
--- a/passlib/handlers/mysql.py
+++ b/passlib/handlers/mysql.py
@@ -47,7 +47,7 @@ class mysql323(uh.StaticHandler):
It has no salt and a single fixed round.
- The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept no optional keywords.
+ The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept no optional keywords.
"""
#===================================================================
# class attrs
@@ -96,7 +96,7 @@ class mysql41(uh.StaticHandler):
It has no salt and a single fixed round.
- The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept no optional keywords.
+ The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept no optional keywords.
"""
#===================================================================
# class attrs
diff --git a/passlib/handlers/oracle.py b/passlib/handlers/oracle.py
index 250731b..3e7f4f9 100644
--- a/passlib/handlers/oracle.py
+++ b/passlib/handlers/oracle.py
@@ -54,7 +54,7 @@ class oracle10(uh.HasUserContext, uh.StaticHandler):
It does a single round of hashing, and relies on the username as the salt.
- The :meth:`~passlib.ifc.PasswordHash.encrypt`, :meth:`~passlib.ifc.PasswordHash.genhash`, and :meth:`~passlib.ifc.PasswordHash.verify` methods all require the
+ The :meth:`~passlib.ifc.PasswordHash.hash`, :meth:`~passlib.ifc.PasswordHash.genhash`, and :meth:`~passlib.ifc.PasswordHash.verify` methods all require the
following additional contextual keywords:
:type user: str
@@ -106,7 +106,7 @@ class oracle11(uh.HasSalt, uh.GenericHandler):
It supports a fixed-length salt.
- The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+ The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
:type salt: str
:param salt:
@@ -133,8 +133,6 @@ class oracle11(uh.HasSalt, uh.GenericHandler):
checksum_size = 40
checksum_chars = uh.UPPER_HEX_CHARS
- _stub_checksum = u('0') * 40
-
#--HasSalt--
min_salt_size = max_salt_size = 20
salt_chars = uh.UPPER_HEX_CHARS
@@ -155,7 +153,7 @@ class oracle11(uh.HasSalt, uh.GenericHandler):
return cls(salt=salt, checksum=chk.upper())
def to_string(self):
- chk = (self.checksum or self._stub_checksum)
+ chk = self.checksum
hash = u("S:%s%s") % (chk.upper(), self.salt.upper())
return uascii_to_str(hash)
diff --git a/passlib/handlers/pbkdf2.py b/passlib/handlers/pbkdf2.py
index 3832562..a52aea4 100644
--- a/passlib/handlers/pbkdf2.py
+++ b/passlib/handlers/pbkdf2.py
@@ -98,7 +98,7 @@ def create_pbkdf2_hash(hash_name, digest_size, rounds=12000, ident=None, module=
It supports a variable-length salt, and a variable number of rounds.
- The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+ The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
:type salt: bytes
:param salt:
@@ -151,7 +151,7 @@ class cta_pbkdf2_sha1(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.Generic
It supports a variable-length salt, and a variable number of rounds.
- The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+ The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
:type salt: bytes
:param salt:
@@ -187,6 +187,7 @@ class cta_pbkdf2_sha1(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.Generic
name = "cta_pbkdf2_sha1"
setting_kwds = ("salt", "salt_size", "rounds")
ident = u("$p5k2$")
+ checksum_size = 20
# NOTE: max_salt_size and max_rounds are arbitrarily chosen to provide a
# sanity check. underlying algorithm (and reference implementation)
@@ -250,7 +251,7 @@ class dlitz_pbkdf2_sha1(uh.HasRounds, uh.HasSalt, uh.GenericHandler):
It supports a variable-length salt, and a variable number of rounds.
- The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+ The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
:type salt: str
:param salt:
@@ -286,6 +287,7 @@ class dlitz_pbkdf2_sha1(uh.HasRounds, uh.HasSalt, uh.GenericHandler):
name = "dlitz_pbkdf2_sha1"
setting_kwds = ("salt", "salt_size", "rounds")
ident = u("$p5k2$")
+ _stub_checksum = u("0" * 48 + "=")
# NOTE: max_salt_size and max_rounds are arbitrarily chosen to provide a
# sanity check. underlying algorithm (and reference implementation)
@@ -351,7 +353,7 @@ class atlassian_pbkdf2_sha1(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler)
It supports a fixed-length salt, and a fixed number of rounds.
- The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keyword:
+ The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keyword:
:type salt: bytes
:param salt:
@@ -375,8 +377,6 @@ class atlassian_pbkdf2_sha1(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler)
ident = u("{PKCS5S2}")
checksum_size = 32
- _stub_checksum = b"\x00" * 32
-
#--HasRawSalt--
min_salt_size = max_salt_size = 16
@@ -391,7 +391,7 @@ class atlassian_pbkdf2_sha1(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler)
return cls(salt=salt, checksum=chk)
def to_string(self):
- data = self.salt + (self.checksum or self._stub_checksum)
+ data = self.salt + self.checksum
hash = self.ident + b64encode(data).decode("ascii")
return uascii_to_str(hash)
@@ -409,7 +409,7 @@ class grub_pbkdf2_sha512(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.Gene
It supports a variable-length salt, and a variable number of rounds.
- The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+ The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
:type salt: bytes
:param salt:
@@ -441,6 +441,7 @@ class grub_pbkdf2_sha512(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.Gene
setting_kwds = ("salt", "salt_size", "rounds")
ident = u("grub.pbkdf2.sha512.")
+ checksum_size = 64
# NOTE: max_salt_size and max_rounds are arbitrarily chosen to provide a
# sanity check. the underlying pbkdf2 specifies no bounds for either,
diff --git a/passlib/handlers/phpass.py b/passlib/handlers/phpass.py
index 3dc955d..eb3e46e 100644
--- a/passlib/handlers/phpass.py
+++ b/passlib/handlers/phpass.py
@@ -29,7 +29,7 @@ class phpass(uh.HasManyIdents, uh.HasRounds, uh.HasSalt, uh.GenericHandler):
It supports a fixed-length salt, and a variable number of rounds.
- The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+ The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
:type salt: str
:param salt:
diff --git a/passlib/handlers/postgres.py b/passlib/handlers/postgres.py
index 634b77e..17156fa 100644
--- a/passlib/handlers/postgres.py
+++ b/passlib/handlers/postgres.py
@@ -23,7 +23,7 @@ class postgres_md5(uh.HasUserContext, uh.StaticHandler):
It does a single round of hashing, and relies on the username as the salt.
- The :meth:`~passlib.ifc.PasswordHash.encrypt`, :meth:`~passlib.ifc.PasswordHash.genhash`, and :meth:`~passlib.ifc.PasswordHash.verify` methods all require the
+ The :meth:`~passlib.ifc.PasswordHash.hash`, :meth:`~passlib.ifc.PasswordHash.genhash`, and :meth:`~passlib.ifc.PasswordHash.verify` methods all require the
following additional contextual keywords:
:type user: str
diff --git a/passlib/handlers/scram.py b/passlib/handlers/scram.py
index 49672bd..d024d6d 100644
--- a/passlib/handlers/scram.py
+++ b/passlib/handlers/scram.py
@@ -25,7 +25,7 @@ class scram(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
It supports a variable-length salt, and a variable number of rounds.
- The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+ The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
:type salt: bytes
:param salt:
diff --git a/passlib/handlers/sha1_crypt.py b/passlib/handlers/sha1_crypt.py
index 243bd99..3db70ad 100644
--- a/passlib/handlers/sha1_crypt.py
+++ b/passlib/handlers/sha1_crypt.py
@@ -26,7 +26,7 @@ class sha1_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler
It supports a variable-length salt, and a variable number of rounds.
- The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+ The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
:type salt: str
:param salt:
diff --git a/passlib/handlers/sha2_crypt.py b/passlib/handlers/sha2_crypt.py
index 88bc2a7..db321b6 100644
--- a/passlib/handlers/sha2_crypt.py
+++ b/passlib/handlers/sha2_crypt.py
@@ -281,7 +281,7 @@ class _SHA2_Common(uh.HasManyBackends, uh.HasRounds, uh.HasSalt,
def __init__(self, implicit_rounds=None, **kwds):
super(_SHA2_Common, self).__init__(**kwds)
- # if user calls encrypt() w/ 5000 rounds, default to compact form.
+ # if user calls hash() w/ 5000 rounds, default to compact form.
if implicit_rounds is None:
implicit_rounds = (self.use_defaults and self.rounds == 5000)
self.implicit_rounds = implicit_rounds
@@ -394,7 +394,7 @@ class sha256_crypt(_SHA2_Common):
It supports a variable-length salt, and a variable number of rounds.
- The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+ The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
:type salt: str
:param salt:
@@ -453,7 +453,7 @@ class sha512_crypt(_SHA2_Common):
It supports a variable-length salt, and a variable number of rounds.
- The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+ The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
:type salt: str
:param salt:
diff --git a/passlib/handlers/sun_md5_crypt.py b/passlib/handlers/sun_md5_crypt.py
index 499f1f8..8028194 100644
--- a/passlib/handlers/sun_md5_crypt.py
+++ b/passlib/handlers/sun_md5_crypt.py
@@ -176,7 +176,7 @@ class sun_md5_crypt(uh.HasRounds, uh.HasSalt, uh.GenericHandler):
It supports a variable-length salt, and a variable number of rounds.
- The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
+ The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
:type salt: str
:param salt:
diff --git a/passlib/handlers/windows.py b/passlib/handlers/windows.py
index e7e0c6a..1929d17 100644
--- a/passlib/handlers/windows.py
+++ b/passlib/handlers/windows.py
@@ -30,7 +30,7 @@ class lmhash(uh.HasEncodingContext, uh.StaticHandler):
It has no salt and a single fixed round.
- The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.verify` methods accept a single
+ The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.verify` methods accept a single
optional keyword:
:type encoding: str
@@ -114,7 +114,7 @@ class nthash(uh.StaticHandler):
It has no salt and a single fixed round.
- The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept no optional keywords.
+ The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept no optional keywords.
Note that while this class outputs lower-case hexadecimal digests,
it will accept upper-case digests as well.
@@ -167,7 +167,7 @@ bsd_nthash = uh.PrefixWrapper("bsd_nthash", nthash, prefix="$3$$", ident="$3$$",
It has no salt and a single fixed round.
- The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept no optional keywords.
+ The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept no optional keywords.
""")
##class ntlm_pair(object):
@@ -183,18 +183,10 @@ bsd_nthash = uh.PrefixWrapper("bsd_nthash", nthash, prefix="$3$$", ident="$3$$",
## return len(hash) == 65 and cls._hash_regex.match(hash) is not None
##
## @classmethod
-## def genconfig(cls):
-## return None
-##
-## @classmethod
-## def genhash(cls, secret, config):
+## def hash(cls, secret, config=None):
## if config is not None and not cls.identify(config):
## raise uh.exc.InvalidHashError(cls)
-## return cls.encrypt(secret)
-##
-## @classmethod
-## def encrypt(cls, secret):
-## return lmhash.encrypt(secret) + ":" + nthash.encrypt(secret)
+## return lmhash.hash(secret) + ":" + nthash.hash(secret)
##
## @classmethod
## def verify(cls, secret, hash):
@@ -217,7 +209,7 @@ class msdcc(uh.HasUserContext, uh.StaticHandler):
It has a fixed number of rounds, and uses the associated
username as the salt.
- The :meth:`~passlib.ifc.PasswordHash.encrypt`, :meth:`~passlib.ifc.PasswordHash.genhash`, and :meth:`~passlib.ifc.PasswordHash.verify` methods
+ The :meth:`~passlib.ifc.PasswordHash.hash`, :meth:`~passlib.ifc.PasswordHash.genhash`, and :meth:`~passlib.ifc.PasswordHash.verify` methods
have the following optional keywords:
:type user: str
@@ -265,7 +257,7 @@ class msdcc2(uh.HasUserContext, uh.StaticHandler):
It has a fixed number of rounds, and uses the associated
username as the salt.
- The :meth:`~passlib.ifc.PasswordHash.encrypt`, :meth:`~passlib.ifc.PasswordHash.genhash`, and :meth:`~passlib.ifc.PasswordHash.verify` methods
+ The :meth:`~passlib.ifc.PasswordHash.hash`, :meth:`~passlib.ifc.PasswordHash.genhash`, and :meth:`~passlib.ifc.PasswordHash.verify` methods
have the following extra keyword:
:type user: str
diff --git a/passlib/ifc.py b/passlib/ifc.py
index 387a1e3..5fad04b 100644
--- a/passlib/ifc.py
+++ b/passlib/ifc.py
@@ -7,6 +7,8 @@ import logging; log = logging.getLogger(__name__)
import sys
# site
# pkg
+from passlib.utils.compat import unicode
+from passlib.utils import deprecated_method
# local
__all__ = [
"PasswordHash",
@@ -77,10 +79,31 @@ class PasswordHash(object):
#===================================================================
@classmethod
@abstractmethod
- def encrypt(cls, secret, **setting_and_context_kwds): # pragma: no cover -- abstract method
+ def hash(cls, secret, # *
+ config=None, **setting_and_context_kwds): # pragma: no cover -- abstract method
"""encrypt secret, returning resulting hash"""
+ # FIXME: need stub for classes that define .encrypt() instead ...
+ # this should call .encrypt(), and check for recursion back to here.
+ # NOTE: as stopgap until genconfig() is removed, implementations
+ # should support 'config=True' to generate config string & bypass digest generation
raise NotImplementedError("must be implemented by subclass")
+ @deprecated_method(deprecated="1.7", removed="2.0", replacement=".hash()")
+ @classmethod
+ def encrypt(cls, *args, **kwds):
+ """
+ Legacy alias for :meth:`hash`.
+
+ .. deprecated:: 1.7
+ This method was renamed to :meth:`!hash` in version 1.7.
+ This alias will be removed in version 2.0, and should only
+ be used for compatibility with Passlib 1.3 - 1.6.
+ """
+ return cls.hash(*args, **kwds)
+
+ # XXX: could provide default implementation which hands value to
+ # hash(), and then does constant-time comparision on the result
+ # (after making both are same string type)
@classmethod
@abstractmethod
def verify(cls, secret, hash, **context_kwds): # pragma: no cover -- abstract method
@@ -94,7 +117,7 @@ class PasswordHash(object):
@abstractmethod
def using(cls, **kwds):
"""
- Return another hasher object (typically a subclass),
+ Return another hasher object (typically a subclass of the current one),
which integrates the configuration options specified by ``kwds``.
.. todo::
@@ -144,17 +167,39 @@ class PasswordHash(object):
"""check if hash belongs to this scheme, returns True/False"""
raise NotImplementedError("must be implemented by subclass")
+ @deprecated_method(deprecated="1.7", removed="2.0")
@classmethod
- @abstractmethod
def genconfig(cls, **setting_kwds): # pragma: no cover -- abstract method
- """compile settings into a configuration string for genhash()"""
- raise NotImplementedError("must be implemented by subclass")
+ """
+ compile settings into a configuration string for genhash()
+
+ .. deprecated:: 1.7
+
+ As of 1.7, this method is deprecated, and slated for complete removal in Passlib 2.0.
+ For all known real-world uses, hashing a constant string
+ should provide equivalent functionality.
+
+ This deprecation may be reversed if a use-case presents itself in the mean time.
+ """
+ # NOTE: as stopgap, builting .hash() implementations support "config=True"
+ # to replicate genconfig()'s behavior. this will be removed along with .genconfig()
+ return cls.hash(unicode(""), config=True, **setting_kwds)
+
+ @deprecated_method(deprecated="1.7", removed="2.0", replacement=".hash(config=config)")
@classmethod
- @abstractmethod
- def genhash(cls, secret, config, **context_kwds): # pragma: no cover -- abstract method
- """generated hash for secret, using settings from config/hash string"""
- raise NotImplementedError("must be implemented by subclass")
+ def genhash(cls, secret, config, **context):
+ """
+ generated hash for secret, using settings from config/hash string
+
+ .. deprecated:: 1.7
+
+ As of 1.7, the :meth:`digest` function now supports a **config** keyword;
+ all calls to :meth:`!genhash` can be replaced with ``.hash(secret, config=config, **context_kwds)``.
+ This method will be removed in version 2.0, and should only
+ be used for compatibility with Passlib 1.3 - 1.6.
+ """
+ return cls.hash(secret, config=config, **context)
#===================================================================
# undocumented methods / attributes
diff --git a/passlib/tests/test_apps.py b/passlib/tests/test_apps.py
index c3d6820..70718dc 100644
--- a/passlib/tests/test_apps.py
+++ b/passlib/tests/test_apps.py
@@ -111,7 +111,7 @@ class AppsTest(TestCase):
'$H$8b95CoYQnQ9Y6fSTsACyphNh5yoM02.',
]:
self.assertTrue(ctx.verify("test", hash))
- self.assertTrue(ctx.encrypt("test").startswith("$H$"))
+ self.assertTrue(ctx.hash("test").startswith("$H$"))
def test_roundup_context(self):
ctx = apps.roundup_context
diff --git a/passlib/tests/test_context.py b/passlib/tests/test_context.py
index a061384..381f3e5 100644
--- a/passlib/tests/test_context.py
+++ b/passlib/tests/test_context.py
@@ -857,14 +857,14 @@ sha512_crypt__min_rounds = 45000
# run through handlers
for crypt in handlers:
- h = cc.encrypt("test", scheme=crypt.name)
+ h = cc.hash("test", scheme=crypt.name)
self.assertEqual(cc.identify(h), crypt.name)
self.assertEqual(cc.identify(h, resolve=True), crypt)
self.assertTrue(cc.verify('test', h))
self.assertFalse(cc.verify('notest', h))
# test default
- h = cc.encrypt("test")
+ h = cc.hash("test")
self.assertEqual(cc.identify(h), "md5_crypt")
# test genhash
@@ -897,7 +897,7 @@ sha512_crypt__min_rounds = 45000
# override scheme & custom settings
self.assertEqual(
cc.genconfig(scheme="phpass", salt='.'*8, rounds=8, ident='P'),
- '$P$6........',
+ '$P$6........howoEQCILTCSAH3X4azem0',
)
#--------------------------------------------------------------
@@ -934,13 +934,17 @@ sha512_crypt__min_rounds = 45000
# rejects non-string secrets
cc = CryptContext(["des_crypt"])
- hash = cc.encrypt('stub')
+ hash = cc.hash('stub')
for secret, kwds in self.nonstring_vectors:
self.assertRaises(TypeError, cc.genhash, secret, hash, **kwds)
# rejects non-string hashes
cc = CryptContext(["des_crypt"])
for hash, kwds in self.nonstring_vectors:
+ if hash is None:
+ # NOTE: as of 1.7, genhash is just wrapper for hash(),
+ # and handles genhash(secret, None) fine.
+ continue
self.assertRaises(TypeError, cc.genhash, 'secret', hash, **kwds)
# .. but should accept None if default scheme lacks config string
@@ -959,16 +963,16 @@ sha512_crypt__min_rounds = 45000
def test_43_encrypt(self):
- """test encrypt() method"""
+ """test hash() method"""
cc = CryptContext(**self.sample_4_dict)
# hash specific settings
self.assertEqual(
- cc.encrypt("password", scheme="phpass", salt='.'*8),
+ cc.hash("password", scheme="phpass", salt='.'*8),
'$H$5........De04R5Egz0aq8Tf.1eVhY/',
)
self.assertEqual(
- cc.encrypt("password", scheme="phpass", salt='.'*8, ident="P"),
+ cc.hash("password", scheme="phpass", salt='.'*8, ident="P"),
'$P$5........De04R5Egz0aq8Tf.1eVhY/',
)
@@ -977,13 +981,13 @@ sha512_crypt__min_rounds = 45000
# min rounds
with self.assertWarningList(PasslibConfigWarning):
self.assertEqual(
- cc.encrypt("password", rounds=1999, salt="nacl"),
+ cc.hash("password", rounds=1999, salt="nacl"),
'$5$rounds=1999$nacl$nmfwJIxqj0csloAAvSER0B8LU0ERCAbhmMug4Twl609',
)
with self.assertWarningList([]):
self.assertEqual(
- cc.encrypt("password", rounds=2001, salt="nacl"),
+ cc.hash("password", rounds=2001, salt="nacl"),
'$5$rounds=2001$nacl$8PdeoPL4aXQnJ0woHhqgIw/efyfCKC2WHneOpnvF.31'
)
@@ -1000,17 +1004,17 @@ sha512_crypt__min_rounds = 45000
# rejects non-string secrets
cc = CryptContext(["des_crypt"])
for secret, kwds in self.nonstring_vectors:
- self.assertRaises(TypeError, cc.encrypt, secret, **kwds)
+ self.assertRaises(TypeError, cc.hash, secret, **kwds)
# throws error without schemes
- self.assertRaises(KeyError, CryptContext().encrypt, 'secret')
+ self.assertRaises(KeyError, CryptContext().hash, 'secret')
# bad scheme values
- self.assertRaises(KeyError, cc.encrypt, 'secret', scheme="fake") # XXX: should this be ValueError?
- self.assertRaises(TypeError, cc.encrypt, 'secret', scheme=1)
+ self.assertRaises(KeyError, cc.hash, 'secret', scheme="fake") # XXX: should this be ValueError?
+ self.assertRaises(TypeError, cc.hash, 'secret', scheme=1)
# bad category values
- self.assertRaises(TypeError, cc.encrypt, 'secret', category=1)
+ self.assertRaises(TypeError, cc.hash, 'secret', category=1)
def test_44_identify(self):
@@ -1044,7 +1048,7 @@ sha512_crypt__min_rounds = 45000
handlers = ["md5_crypt", "des_crypt", "bsdi_crypt"]
cc = CryptContext(handlers, bsdi_crypt__default_rounds=5)
- h = hash.md5_crypt.encrypt("test")
+ h = hash.md5_crypt.hash("test")
# check base verify
self.assertTrue(cc.verify("test", h))
@@ -1066,7 +1070,7 @@ sha512_crypt__min_rounds = 45000
# rejects non-string secrets
cc = CryptContext(["des_crypt"])
- h = refhash = cc.encrypt('stub')
+ h = refhash = cc.hash('stub')
for secret, kwds in self.nonstring_vectors:
self.assertRaises(TypeError, cc.verify, secret, h, **kwds)
@@ -1122,7 +1126,7 @@ sha512_crypt__min_rounds = 45000
# calling needs_update should query callback
ctx = CryptContext([dummy])
- hash = refhash = dummy.encrypt("test")
+ hash = refhash = dummy.hash("test")
self.assertFalse(ctx.needs_update(hash))
self.assertEqual(check_state, [(hash,None)])
del check_state[:]
@@ -1161,8 +1165,8 @@ sha512_crypt__min_rounds = 45000
cc = CryptContext(**self.sample_4_dict)
# create some hashes
- h1 = cc.encrypt("password", scheme="des_crypt")
- h2 = cc.encrypt("password", scheme="sha256_crypt")
+ h1 = cc.hash("password", scheme="des_crypt")
+ h2 = cc.hash("password", scheme="sha256_crypt")
# check bad password, deprecated hash
ok, new_hash = cc.verify_and_update("wrongpass", h1)
@@ -1190,7 +1194,7 @@ sha512_crypt__min_rounds = 45000
# rejects non-string secrets
cc = CryptContext(["des_crypt"])
- hash = refhash = cc.encrypt('stub')
+ hash = refhash = cc.hash('stub')
for secret, kwds in self.nonstring_vectors:
self.assertRaises(TypeError, cc.verify_and_update, secret, hash, **kwds)
@@ -1210,14 +1214,14 @@ sha512_crypt__min_rounds = 45000
self.assertRaises(TypeError, cc.verify_and_update, 'secret', refhash, category=1)
def test_48_context_kwds(self):
- """encrypt(), verify(), and verify_and_update() -- discard unused context keywords"""
+ """hash(), verify(), and verify_and_update() -- discard unused context keywords"""
# setup test case
# NOTE: postgres_md5 hash supports 'user' context kwd, which is used for this test.
from passlib.hash import des_crypt, md5_crypt, postgres_md5
- des_hash = des_crypt.encrypt("stub")
- pg_root_hash = postgres_md5.encrypt("stub", user="root")
- pg_admin_hash = postgres_md5.encrypt("stub", user="admin")
+ des_hash = des_crypt.hash("stub")
+ pg_root_hash = postgres_md5.hash("stub", user="root")
+ pg_admin_hash = postgres_md5.hash("stub", user="admin")
#------------------------------------------------------------
# case 1: contextual kwds not supported by any hash in CryptContext
@@ -1226,12 +1230,12 @@ sha512_crypt__min_rounds = 45000
self.assertEqual(cc1.context_kwds, set())
# des_scrypt should work w/o any contextual kwds
- self.assertTrue(des_crypt.identify(cc1.encrypt("stub")), "des_crypt")
+ self.assertTrue(des_crypt.identify(cc1.hash("stub")), "des_crypt")
self.assertTrue(cc1.verify("stub", des_hash))
self.assertEqual(cc1.verify_and_update("stub", des_hash), (True, None))
# des_crypt should throw error due to unknown context keyword
- self.assertRaises(TypeError, cc1.encrypt, "stub", user="root")
+ self.assertRaises(TypeError, cc1.hash, "stub", user="root")
self.assertRaises(TypeError, cc1.verify, "stub", des_hash, user="root")
self.assertRaises(TypeError, cc1.verify_and_update, "stub", des_hash, user="root")
@@ -1242,17 +1246,17 @@ sha512_crypt__min_rounds = 45000
self.assertEqual(cc2.context_kwds, set(["user"]))
# verify des_crypt works w/o "user" kwd
- self.assertTrue(des_crypt.identify(cc2.encrypt("stub")), "des_crypt")
+ self.assertTrue(des_crypt.identify(cc2.hash("stub")), "des_crypt")
self.assertTrue(cc2.verify("stub", des_hash))
self.assertEqual(cc2.verify_and_update("stub", des_hash), (True, None))
# verify des_crypt ignores "user" kwd
- self.assertTrue(des_crypt.identify(cc2.encrypt("stub", user="root")), "des_crypt")
+ self.assertTrue(des_crypt.identify(cc2.hash("stub", user="root")), "des_crypt")
self.assertTrue(cc2.verify("stub", des_hash, user="root"))
self.assertEqual(cc2.verify_and_update("stub", des_hash, user="root"), (True, None))
# verify error with unknown kwd
- self.assertRaises(TypeError, cc2.encrypt, "stub", badkwd="root")
+ self.assertRaises(TypeError, cc2.hash, "stub", badkwd="root")
self.assertRaises(TypeError, cc2.verify, "stub", des_hash, badkwd="root")
self.assertRaises(TypeError, cc2.verify_and_update, "stub", des_hash, badkwd="root")
@@ -1263,12 +1267,12 @@ sha512_crypt__min_rounds = 45000
self.assertEqual(cc3.context_kwds, set(["user"]))
# postgres_md5 should have error w/o context kwd
- self.assertRaises(TypeError, cc3.encrypt, "stub")
+ self.assertRaises(TypeError, cc3.hash, "stub")
self.assertRaises(TypeError, cc3.verify, "stub", pg_root_hash)
self.assertRaises(TypeError, cc3.verify_and_update, "stub", pg_root_hash)
# postgres_md5 should work w/ context kwd
- self.assertEqual(cc3.encrypt("stub", user="root"), pg_root_hash)
+ self.assertEqual(cc3.hash("stub", user="root"), pg_root_hash)
self.assertTrue(cc3.verify("stub", pg_root_hash, user="root"))
self.assertEqual(cc3.verify_and_update("stub", pg_root_hash, user="root"), (True, None))
@@ -1289,7 +1293,7 @@ sha512_crypt__min_rounds = 45000
# NOTE: the follow tests check how _CryptRecord handles
# the min/max/default/vary_rounds options, via the output of
- # genconfig(). it's assumed encrypt() takes the same codepath.
+ # genconfig(). it's assumed hash() takes the same codepath.
def test_50_rounds_limits(self):
"""test rounds limits"""
@@ -1299,6 +1303,9 @@ sha512_crypt__min_rounds = 45000
all__default_rounds=2500,
)
+ # stub digest returned by sha256_crypt's genconfig calls..
+ STUB = '...........................................'
+
#--------------------------------------------------
# min_rounds
#--------------------------------------------------
@@ -1307,26 +1314,20 @@ sha512_crypt__min_rounds = 45000
with self.assertWarningList([PasslibHashWarning]*2):
c2 = cc.copy(all__min_rounds=500, all__max_rounds=None,
all__default_rounds=500)
- self.assertEqual(c2.genconfig(salt="nacl"), "$5$rounds=1000$nacl$")
+ self.assertEqual(c2.genconfig(salt="nacl"), "$5$rounds=1000$nacl$" + STUB)
# below policy minimum
with self.assertWarningList(PasslibConfigWarning):
self.assertEqual(
- cc.genconfig(rounds=1999, salt="nacl"),
- '$5$rounds=1999$nacl$',
- )
+ cc.genconfig(rounds=1999, salt="nacl"), '$5$rounds=1999$nacl$' + STUB)
# equal to policy minimum
self.assertEqual(
- cc.genconfig(rounds=2000, salt="nacl"),
- '$5$rounds=2000$nacl$',
- )
+ cc.genconfig(rounds=2000, salt="nacl"), '$5$rounds=2000$nacl$' + STUB)
# above policy minimum
self.assertEqual(
- cc.genconfig(rounds=2001, salt="nacl"),
- '$5$rounds=2001$nacl$'
- )
+ cc.genconfig(rounds=2001, salt="nacl"), '$5$rounds=2001$nacl$' + STUB)
#--------------------------------------------------
# max rounds
@@ -1337,44 +1338,36 @@ sha512_crypt__min_rounds = 45000
c2 = cc.copy(all__max_rounds=int(1e9)+500, all__min_rounds=None,
all__default_rounds=int(1e9)+500)
- self.assertEqual(c2.genconfig(salt="nacl"),
- "$5$rounds=999999999$nacl$")
+ self.assertEqual(c2.genconfig(salt="nacl"), "$5$rounds=999999999$nacl$" + STUB)
# above policy max
with self.assertWarningList(PasslibConfigWarning):
self.assertEqual(
- cc.genconfig(rounds=3001, salt="nacl"),
- '$5$rounds=3001$nacl$'
- )
+ cc.genconfig(rounds=3001, salt="nacl"), '$5$rounds=3001$nacl$' + STUB)
# equal policy max
self.assertEqual(
- cc.genconfig(rounds=3000, salt="nacl"),
- '$5$rounds=3000$nacl$'
- )
+ cc.genconfig(rounds=3000, salt="nacl"), '$5$rounds=3000$nacl$' + STUB)
# below policy max
self.assertEqual(
- cc.genconfig(rounds=2999, salt="nacl"),
- '$5$rounds=2999$nacl$',
- )
+ cc.genconfig(rounds=2999, salt="nacl"), '$5$rounds=2999$nacl$' + STUB)
#--------------------------------------------------
# default_rounds
#--------------------------------------------------
# explicit default rounds
- self.assertEqual(cc.genconfig(salt="nacl"), '$5$rounds=2500$nacl$')
+ self.assertEqual(cc.genconfig(salt="nacl"), '$5$rounds=2500$nacl$' + STUB)
# fallback default rounds - use handler's
df = hash.sha256_crypt.default_rounds
c2 = cc.copy(all__default_rounds=None, all__max_rounds=df<<1)
- self.assertEqual(c2.genconfig(salt="nacl"),
- '$5$rounds=%d$nacl$' % df)
+ self.assertEqual(c2.genconfig(salt="nacl"), '$5$rounds=%d$nacl$%s' % (df, STUB))
# fallback default rounds - use handler's, but clipped to max rounds
c2 = cc.copy(all__default_rounds=None, all__max_rounds=3000)
- self.assertEqual(c2.genconfig(salt="nacl"), '$5$rounds=3000$nacl$')
+ self.assertEqual(c2.genconfig(salt="nacl"), '$5$rounds=3000$nacl$' + STUB)
# TODO: test default falls back to mx / mn if handler has no default.
diff --git a/passlib/tests/test_context_deprecated.py b/passlib/tests/test_context_deprecated.py
index b59d0d8..128b538 100644
--- a/passlib/tests/test_context_deprecated.py
+++ b/passlib/tests/test_context_deprecated.py
@@ -608,7 +608,7 @@ class CryptContextTest(TestCase):
# check constructor...
cc = CryptContext()
self.assertRaises(KeyError, cc.identify, 'hash', required=True)
- self.assertRaises(KeyError, cc.encrypt, 'secret')
+ self.assertRaises(KeyError, cc.hash, 'secret')
self.assertRaises(KeyError, cc.verify, 'secret', 'hash')
# check updating policy after the fact...
@@ -617,7 +617,7 @@ class CryptContextTest(TestCase):
cc.policy = p
self.assertRaises(KeyError, cc.identify, 'hash', required=True)
- self.assertRaises(KeyError, cc.encrypt, 'secret')
+ self.assertRaises(KeyError, cc.hash, 'secret')
self.assertRaises(KeyError, cc.verify, 'secret', 'hash')
#===================================================================
diff --git a/passlib/tests/test_ext_django.py b/passlib/tests/test_ext_django.py
index a75d5e7..15abfc7 100644
--- a/passlib/tests/test_ext_django.py
+++ b/passlib/tests/test_ext_django.py
@@ -130,7 +130,7 @@ stock_config.update(
# override sample hashes used in test cases
from passlib.hash import django_pbkdf2_sha256
sample_hashes = dict(
- django_pbkdf2_sha256=("not a password", django_pbkdf2_sha256.encrypt("not a password",
+ django_pbkdf2_sha256=("not a password", django_pbkdf2_sha256.hash("not a password",
rounds=stock_config.get("django_pbkdf2_sha256__default_rounds")))
)
@@ -797,9 +797,9 @@ class ContextWithHook(CryptContext):
def update_hook(self):
pass
- def encrypt(self, *args, **kwds):
+ def hash(self, *args, **kwds):
self.update_hook(self)
- return super(ContextWithHook, self).encrypt(*args, **kwds)
+ return super(ContextWithHook, self).hash(*args, **kwds)
def verify(self, *args, **kwds):
self.update_hook(self)
diff --git a/passlib/tests/test_handlers.py b/passlib/tests/test_handlers.py
index 348411c..13ccfb0 100644
--- a/passlib/tests/test_handlers.py
+++ b/passlib/tests/test_handlers.py
@@ -1069,11 +1069,6 @@ class mssql2000_test(HandlerCase):
]
- known_correct_configs = [
- ('0x010034767D5C00000000000000000000000000000000000000000000000000000000000000000000000000000000',
- 'Test', '0x010034767D5C0CFA5FDCA28C4A56085E65E882E71CB0ED2503412FD54D6119FFF04129A1D72E7C3194F7284A7F3A'),
- ]
-
known_alternate_hashes = [
# lower case hex
('0x01005b20054332752e1bc2e7c5df0f9ebfe486e9bee063e8d3b332752e1bc2e7c5df0f9ebfe486e9bee063e8d3b3',
@@ -1161,11 +1156,6 @@ class mssql2005_test(HandlerCase):
(UPASS_TABLE, '0x010083104228FAD559BE52477F2131E538BE9734E5C4B0ADEFD7'),
]
- known_correct_configs = [
- ('0x010034767D5C0000000000000000000000000000000000000000',
- 'Test', '0x010034767D5C0CFA5FDCA28C4A56085E65E882E71CB0ED250341'),
- ]
-
known_alternate_hashes = [
# lower case hex
('0x01005b20054332752e1bc2e7c5df0f9ebfe486e9bee063e8d3b3',
@@ -1797,7 +1787,7 @@ class scram_test(HandlerCase):
self.assertEqual(subcls.default_algs, ["md5", "sha-1"])
# test encrypt output
- h1 = subcls.encrypt("dummy")
+ h1 = subcls.hash("dummy")
self.assertEqual(handler.extract_digest_algs(h1), ["md5", "sha-1"])
def test_94_using_algs(self):
@@ -1809,7 +1799,7 @@ class scram_test(HandlerCase):
handler1 = self.handler.using(algs="sha1,md5")
# shouldn't need update, has same algs
- h1 = handler1.encrypt("dummy")
+ h1 = handler1.hash("dummy")
self.assertFalse(handler1.needs_update(h1))
# *currently* shouldn't need update, has superset of algs required by handler2
@@ -1827,7 +1817,7 @@ class scram_test(HandlerCase):
from passlib.context import CryptContext
c1 = CryptContext(["scram"], scram__algs="sha1,md5")
- h = c1.encrypt("dummy")
+ h = c1.hash("dummy")
self.assertEqual(handler.extract_digest_algs(h), ["md5", "sha-1"])
self.assertFalse(c1.needs_update(h))
@@ -2204,7 +2194,7 @@ class sun_md5_crypt_test(HandlerCase):
# should all be treated the same, with one "$" added to salt digest.
("$md5$3UqYqndY$",
"this", "$md5$3UqYqndY$$6P.aaWOoucxxq.l00SS9k0"),
- ("$md5$3UqYqndY$$......................",
+ ("$md5$3UqYqndY$$.................DUMMY",
"this", "$md5$3UqYqndY$$6P.aaWOoucxxq.l00SS9k0"),
# config with no suffix, hash strings with "$" suffix,
@@ -2214,7 +2204,7 @@ class sun_md5_crypt_test(HandlerCase):
# within config string.
("$md5$3UqYqndY",
"this", "$md5$3UqYqndY$HIZVnfJNGCPbDZ9nIRSgP1"),
- ("$md5$3UqYqndY$......................",
+ ("$md5$3UqYqndY$.................DUMMY",
"this", "$md5$3UqYqndY$HIZVnfJNGCPbDZ9nIRSgP1"),
]
@@ -2251,10 +2241,10 @@ class sun_md5_crypt_test(HandlerCase):
("freebsd|openbsd|netbsd|linux|darwin", False),
]
def do_verify(self, secret, hash):
- # override to fake error for "$..." hash strings listed in known_config.
- # these have to be hash strings, in order to test bare salt issue.
- if isinstance(hash, str) and hash.endswith("$......................"):
- raise ValueError("pretending '$.' hash is config string")
+ # Override to fake error for "$..." hash string listed in known_correct_configs (above)
+ # These have to be hash strings, in order to test bare salt issue.
+ if isinstance(hash, str) and hash.endswith("$.................DUMMY"):
+ raise ValueError("pretending '$...' stub hash is config string")
return self.handler.verify(secret, hash)
#=============================================================================
diff --git a/passlib/tests/test_handlers_bcrypt.py b/passlib/tests/test_handlers_bcrypt.py
index abd181b..9ed979e 100644
--- a/passlib/tests/test_handlers_bcrypt.py
+++ b/passlib/tests/test_handlers_bcrypt.py
@@ -333,7 +333,7 @@ class _bcrypt_test(HandlerCase):
corr_desc = ".*incorrectly set padding bits"
#
- # test encrypt() / genconfig() don't generate invalid salts anymore
+ # test hash() / genconfig() don't generate invalid salts anymore
#
def check_padding(hash):
assert hash.startswith("$2a$") and len(hash) >= 28
@@ -342,14 +342,14 @@ class _bcrypt_test(HandlerCase):
for i in irange(6):
check_padding(bcrypt.genconfig())
for i in irange(3):
- check_padding(bcrypt.encrypt("bob", rounds=bcrypt.min_rounds))
+ check_padding(bcrypt.hash("bob", rounds=bcrypt.min_rounds))
#
# test genconfig() corrects invalid salts & issues warning.
#
with self.assertWarningList(["salt too large", corr_desc]):
hash = bcrypt.genconfig(salt="."*21 + "A.", rounds=5, relaxed=True)
- self.assertEqual(hash, "$2a$05$" + "." * 22)
+ self.assertEqual(hash, "$2a$05$" + "." * (22 + 31))
#
# test public methods against good & bad hashes
diff --git a/passlib/tests/test_registry.py b/passlib/tests/test_registry.py
index 93573ae..325a2ef 100644
--- a/passlib/tests/test_registry.py
+++ b/passlib/tests/test_registry.py
@@ -1,4 +1,4 @@
-"""tests for passlib.pwhash -- (c) Assurance Technologies 2003-2009"""
+"""tests for passlib.hash -- (c) Assurance Technologies 2003-2009"""
#=============================================================================
# imports
#=============================================================================
diff --git a/passlib/tests/test_utils_handlers.py b/passlib/tests/test_utils_handlers.py
index 8546eaf..f43e8d8 100644
--- a/passlib/tests/test_utils_handlers.py
+++ b/passlib/tests/test_utils_handlers.py
@@ -1,4 +1,4 @@
-"""tests for passlib.pwhash -- (c) Assurance Technologies 2003-2009"""
+"""tests for passlib.hash -- (c) Assurance Technologies 2003-2009"""
#=============================================================================
# imports
#=============================================================================
@@ -75,7 +75,7 @@ class SkeletonTest(TestCase):
self.assertRaises(TypeError, d1.identify, 1)
# check default genconfig method
- self.assertIs(d1.genconfig(), None)
+ self.assertEqual(d1.genconfig(), d1.hash(""))
# check default verify method
self.assertTrue(d1.verify('s', b'_a'))
@@ -87,8 +87,8 @@ class SkeletonTest(TestCase):
self.assertRaises(ValueError, d1.verify, 's', u('_c'))
# check default encrypt method
- self.assertEqual(d1.encrypt('s'), '_a')
- self.assertEqual(d1.encrypt('s', flag=True), '_b')
+ self.assertEqual(d1.hash('s'), '_a')
+ self.assertEqual(d1.hash('s', flag=True), '_b')
def test_01_calc_checksum_hack(self):
"""test StaticHandler legacy attr"""
@@ -125,7 +125,7 @@ class SkeletonTest(TestCase):
# encrypt should issue api warnings, but everything else should be fine.
with self.assertWarningList("d1.*should be updated.*_calc_checksum"):
- hash = d1.encrypt("test")
+ hash = d1.hash("test")
self.assertEqual(hash, '7c622762588a0e5cc786ad0a143156f9fd38eea3')
self.assertTrue(d1.verify("test", hash))
@@ -133,7 +133,7 @@ class SkeletonTest(TestCase):
# not defining genhash either, however, should cause NotImplementedError
del d1.genhash
- self.assertRaises(NotImplementedError, d1.encrypt, 'test')
+ self.assertRaises(NotImplementedError, d1.hash, 'test')
#===================================================================
# GenericHandler & mixins
@@ -180,7 +180,6 @@ class SkeletonTest(TestCase):
name = 'd1'
checksum_size = 4
checksum_chars = u('xz')
- _stub_checksum = u('z')*4
def norm_checksum(*a, **k):
return d1(*a, **k).checksum
@@ -207,14 +206,13 @@ class SkeletonTest(TestCase):
self.assertRaises(TypeError, norm_checksum, 1, relaxed=True)
# test _stub_checksum behavior
- self.assertIs(norm_checksum(u('zzzz')), None)
+ self.assertEqual(d1()._stub_checksum, u('xxxx'))
def test_12_norm_checksum_raw(self):
"""test GenericHandler + HasRawChecksum mixin"""
class d1(uh.HasRawChecksum, uh.GenericHandler):
name = 'd1'
checksum_size = 4
- _stub_checksum = b'0'*4
def norm_checksum(*a, **k):
return d1(*a, **k).checksum
@@ -227,7 +225,7 @@ class SkeletonTest(TestCase):
self.assertRaises(TypeError, norm_checksum, u('xxyx'), relaxed=True)
# test _stub_checksum behavior
- self.assertIs(norm_checksum(b'0'*4), None)
+ self.assertEqual(d1()._stub_checksum, b'\x00'*4)
def test_20_norm_salt(self):
"""test GenericHandler + HasSalt mixin"""
@@ -693,7 +691,7 @@ class PrefixWrapperTest(TestCase):
lph = "{MD5}X03MO1qnZdYdgyfeuILPmQ=="
# genconfig
- self.assertIs(d1.genconfig(), None)
+ self.assertEqual(d1.genconfig(), '{XXX}1B2M2Y8AsgTpgAmY7PhCfg==')
# genhash
self.assertEqual(d1.genhash("password", None), dph)
@@ -701,7 +699,7 @@ class PrefixWrapperTest(TestCase):
self.assertRaises(ValueError, d1.genhash, "password", lph)
# encrypt
- self.assertEqual(d1.encrypt("password"), dph)
+ self.assertEqual(d1.hash("password"), dph)
# identify
self.assertTrue(d1.identify(dph))
@@ -774,7 +772,7 @@ class PrefixWrapperTest(TestCase):
# shoudl throw InvalidHashError if wrapped hash doesn't begin
# with orig_prefix.
h = uh.PrefixWrapper("h2", "md5_crypt", orig_prefix="$6$")
- self.assertRaises(ValueError, h.encrypt, 'test')
+ self.assertRaises(ValueError, h.hash, 'test')
#=============================================================================
# sample algorithms - these serve as known quantities
@@ -813,10 +811,8 @@ class SaltedHash(uh.HasSalt, uh.GenericHandler):
hash = hash.decode("ascii")
return cls(salt=hash[5:-40], checksum=hash[-40:])
- _stub_checksum = u('0') * 40
-
def to_string(self):
- hash = u("@salt%s%s") % (self.salt, self.checksum or self._stub_checksum)
+ hash = u("@salt%s%s") % (self.salt, self.checksum)
return uascii_to_str(hash)
def _calc_checksum(self, secret):
@@ -855,10 +851,9 @@ class SaltedHashTest(HandlerCase):
]
def test_bad_kwds(self):
- self.assertRaises(TypeError, SaltedHash,
- checksum=SaltedHash._stub_checksum, salt=None)
- self.assertRaises(ValueError, SaltedHash,
- checksum=SaltedHash._stub_checksum, salt='xxx')
+ stub = SaltedHash(use_defaults=True)._stub_checksum
+ self.assertRaises(TypeError, SaltedHash, checksum=stub, salt=None)
+ self.assertRaises(ValueError, SaltedHash, checksum=stub, salt='xxx')
#=============================================================================
# eof
diff --git a/passlib/tests/utils.py b/passlib/tests/utils.py
index b4d894e..18fb645 100644
--- a/passlib/tests/utils.py
+++ b/passlib/tests/utils.py
@@ -4,6 +4,7 @@
#=============================================================================
from __future__ import with_statement
# core
+import contextlib
import logging; log = logging.getLogger(__name__)
import random
import re
@@ -161,6 +162,35 @@ def unwrap_handler(handler):
handler = handler.wrapped
return handler
+@contextlib.contextmanager
+def patch_calc_min_rounds(handler):
+ """
+ internal helper for test_21_max_rounds() --
+ context manager which temporarily replaces handler's _calc_checksum()
+ with one that uses min_rounds; useful when trying to generate config
+ with high rounds value, but don't care if output is correct.
+ """
+ if isinstance(handler, type) and issubclass(handler, uh.HasRounds):
+ wrapped = handler._calc_checksum
+ def wrapper(self, *args, **kwds):
+ rounds = self.rounds
+ try:
+ self.rounds = self.min_rounds
+ return wrapped(self, *args, **kwds)
+ finally:
+ self.rounds = rounds
+ handler._calc_checksum = wrapper
+ try:
+ yield
+ finally:
+ handler._calc_checksum = wrapped
+ elif isinstance(handler, uh.PrefixWrapper):
+ with patch_calc_min_rounds(handler.wrapped):
+ yield
+ else:
+ yield
+ return
+
#=============================================================================
# misc helpers
#=============================================================================
@@ -274,6 +304,7 @@ class TestCase(_TestCase):
ctx = reset_warnings()
ctx.__enter__()
self.addCleanup(ctx.__exit__)
+ warnings.filterwarnings("ignore", "the method .*\.(encrypt|genconfig|genhash)\(\) is deprecated")
#---------------------------------------------------------------
# tweak message formatting so longMessage mode is only enabled
@@ -618,10 +649,6 @@ class HandlerCase(TestCase):
#---------------------------------------------------------------
# configuration helpers
#---------------------------------------------------------------
- @property
- def supports_config_string(self):
- return self.do_genconfig() is not None
-
@classmethod
def iter_known_hashes(cls):
"""iterate through known (secret, hash) pairs"""
@@ -690,11 +717,15 @@ class HandlerCase(TestCase):
"""subclassable method allowing 'secret' to be encode context kwds"""
return secret
- def do_encrypt(self, secret, **kwds):
+ # TODO: rename to do_hash() to match new API
+ def do_encrypt(self, secret, use_encrypt=False, **kwds):
"""call handler's encrypt method with specified options"""
secret = self.populate_context(secret, kwds)
self.populate_settings(kwds)
- return self.handler.encrypt(secret, **kwds)
+ if use_encrypt:
+ return self.handler.encrypt(secret, **kwds)
+ else:
+ return self.handler.hash(secret, **kwds)
def do_verify(self, secret, hash, **kwds):
"""call handler's verify method"""
@@ -707,13 +738,19 @@ class HandlerCase(TestCase):
def do_genconfig(self, **kwds):
"""call handler's genconfig method with specified options"""
+ # NOTE: as of 1.7, all genconfig() calls are just wrappers for .hash(),
+ # so we have to include context kwds
+ self.populate_context("", kwds)
self.populate_settings(kwds)
return self.handler.genconfig(**kwds)
- def do_genhash(self, secret, config, **kwds):
+ def do_genhash(self, secret, config, use_genhash=False, **kwds):
"""call handler's genhash method with specified options"""
secret = self.populate_context(secret, kwds)
- return self.handler.genhash(secret, config, **kwds)
+ if use_genhash:
+ return self.handler.genhash(secret, config, **kwds)
+ else:
+ return self.handler.hash(secret, config=config, **kwds)
#---------------------------------------------------------------
# automatically generate subclasses for testing specific backends,
@@ -815,15 +852,11 @@ class HandlerCase(TestCase):
and that identify() and genhash() handle the result correctly.
"""
#
- # genconfig() should return native string,
- # or ``None`` if handler does not use a configuration string
- # (mostly used by static hashes)
+ # genconfig() should return native string.
+ # NOTE: prior to 1.7 could return None, but that's no longer allowed.
#
config = self.do_genconfig()
- if self.supports_config_string:
- self.check_returned_native_str(config, "genconfig")
- else:
- self.assertIs(config, None)
+ self.check_returned_native_str(config, "genconfig")
#
# genhash() should always accept genconfig()'s output,
@@ -835,27 +868,28 @@ class HandlerCase(TestCase):
#
# verify() should never accept config strings
#
- if self.supports_config_string:
- self.assertRaises(ValueError, self.do_verify, 'stub', config,
- __msg__="verify() failed to reject genconfig() output: %r" %
- (config,))
- else:
- self.assertRaises(TypeError, self.do_verify, 'stub', config)
+
+ # NOTE: changed as of 1.7 -- previously, .verify() should have
+ # rejected partial config strings returned by genconfig().
+ # as of 1.7, that feature is deprecated, and genconfig()
+ # always returns a hash (usually of the empty string)
+ # so verify should always accept it's output
+ self.do_verify('', config) # usually true, but not required by protocol
#
# identify() should positively identify config strings if not None.
#
- if self.supports_config_string:
- self.assertTrue(self.do_identify(config),
- "identify() failed to identify genconfig() output: %r" %
- (config,))
- else:
- self.assertRaises(TypeError, self.do_identify, config)
- def test_03_hash_workflow(self):
+ # NOTE: changed as of 1.7 -- genconfig() previously might return None,
+ # now must always return valid hash
+ self.assertTrue(self.do_identify(config),
+ "identify() failed to identify genconfig() output: %r" %
+ (config,))
+
+ def test_03_hash_workflow(self, use_16_legacy=False):
"""test basic hash-string workflow.
- this tests that encrypt()'s hashes are accepted
+ this tests that hash()'s hashes are accepted
by verify() and identify(), and regenerated correctly by genhash().
the test is run against a couple of different stock passwords.
"""
@@ -863,10 +897,10 @@ class HandlerCase(TestCase):
for secret in self.stock_passwords:
#
- # encrypt() should generate native str hash
+ # hash() should generate native str hash
#
- result = self.do_encrypt(secret)
- self.check_returned_native_str(result, "encrypt")
+ result = self.do_encrypt(secret, use_encrypt=use_16_legacy)
+ self.check_returned_native_str(result, "hash")
#
# verify() should work only against secret
@@ -877,7 +911,7 @@ class HandlerCase(TestCase):
#
# genhash() should reproduce original hash
#
- other = self.do_genhash(secret, result)
+ other = self.do_genhash(secret, result, use_genhash=use_16_legacy)
self.check_returned_native_str(other, "genhash")
self.assertEqual(other, result, "genhash() failed to reproduce "
"hash: secret=%r hash=%r: result=%r" %
@@ -886,7 +920,7 @@ class HandlerCase(TestCase):
#
# genhash() should NOT reproduce original hash for wrong password
#
- other = self.do_genhash(wrong_secret, result)
+ other = self.do_genhash(wrong_secret, result, use_genhash=use_16_legacy)
self.check_returned_native_str(other, "genhash")
if self.is_disabled_handler:
self.assertEqual(other, result, "genhash() failed to reproduce "
@@ -902,15 +936,19 @@ class HandlerCase(TestCase):
#
self.assertTrue(self.do_identify(result))
+ def test_03_legacy_hash_workflow(self):
+ """test hash-string workflow with legacy .encrypt() & .genhash() methods"""
+ self.test_03_hash_workflow(use_16_legacy=True)
+
def test_04_hash_types(self):
"""test hashes can be unicode or bytes"""
# this runs through workflow similar to 03, but wraps
# everything using tonn() so we test unicode under py2,
# and bytes under py3.
- # encrypt using non-native secret
+ # hash using non-native secret
result = self.do_encrypt(tonn('stub'))
- self.check_returned_native_str(result, "encrypt")
+ self.check_returned_native_str(result, "hash")
# verify using non-native hash
self.check_verify('stub', tonn(result))
@@ -1038,7 +1076,7 @@ class HandlerCase(TestCase):
log(len(handler.default_salt_chars), 2))
def test_11_unique_salt(self):
- """test encrypt() / genconfig() creates new salt each time"""
+ """test hash() / genconfig() creates new salt each time"""
self.require_salt()
# odds of picking 'n' identical salts at random is '(.5**salt_bits)**n'.
# we want to pick the smallest N needed s.t. odds are <1/1000, just
@@ -1059,7 +1097,7 @@ class HandlerCase(TestCase):
sampler(lambda : self.do_encrypt("stub"))
def test_12_min_salt_size(self):
- """test encrypt() / genconfig() honors min_salt_size"""
+ """test hash() / genconfig() honors min_salt_size"""
self.require_salt_info()
handler = self.handler
@@ -1085,7 +1123,7 @@ class HandlerCase(TestCase):
salt_size=min_size-1)
def test_13_max_salt_size(self):
- """test encrypt() / genconfig() honors max_salt_size"""
+ """test hash() / genconfig() honors max_salt_size"""
self.require_salt_info()
handler = self.handler
@@ -1241,8 +1279,8 @@ class HandlerCase(TestCase):
if cls.rounds_cost not in rounds_cost_values:
raise AssertionError("unknown rounds cost constant: %r" % (cls.rounds_cost,))
- def test_21_rounds_limits(self):
- """test encrypt() / genconfig() honors rounds limits"""
+ def test_21_min_rounds(self):
+ """test hash() / genconfig() honors min_rounds"""
self.require_rounds_info()
handler = self.handler
min_rounds = handler.min_rounds
@@ -1253,27 +1291,29 @@ class HandlerCase(TestCase):
# check min-1 is rejected
self.assertRaises(ValueError, self.do_genconfig, rounds=min_rounds-1)
- self.assertRaises(ValueError, self.do_encrypt, 'stub',
- rounds=min_rounds-1)
+ self.assertRaises(ValueError, self.do_encrypt, 'stub', rounds=min_rounds-1)
# TODO: check relaxed mode clips min-1
- # handle max rounds
+ def test_21b_max_rounds(self):
+ """test hash() / genconfig() honors max_rounds"""
+ self.require_rounds_info()
+ handler = self.handler
max_rounds = handler.max_rounds
- if max_rounds is None:
- # check large value is accepted
- self.do_genconfig(rounds=(1<<31)-1)
- else:
- # check max is accepted
- self.do_genconfig(rounds=max_rounds)
+ if max_rounds is not None:
# check max+1 is rejected
- self.assertRaises(ValueError, self.do_genconfig,
- rounds=max_rounds+1)
- self.assertRaises(ValueError, self.do_encrypt, 'stub',
- rounds=max_rounds+1)
+ self.assertRaises(ValueError, self.do_genconfig, rounds=max_rounds+1)
+ self.assertRaises(ValueError, self.do_encrypt, 'stub', rounds=max_rounds+1)
+
+ # handle max rounds
+ with patch_calc_min_rounds(handler):
+ if max_rounds is None:
+ self.do_genconfig(rounds=(1<<31)-1)
+ else:
+ self.do_genconfig(rounds=max_rounds)
- # TODO: check relaxed mode clips max+1
+ # TODO: check relaxed mode clips max+1
def _get_rand_rounds(self):
handler = self.handler
@@ -1907,15 +1947,15 @@ class HandlerCase(TestCase):
def test_76_hash_border(self):
"""test non-string hashes are rejected"""
#
- # test hash=None is rejected (except if config=None)
+ # test hash=None is handled correctly
#
self.assertRaises(TypeError, self.do_identify, None)
self.assertRaises(TypeError, self.do_verify, 'stub', None)
- if self.supports_config_string:
- self.assertRaises(TypeError, self.do_genhash, 'stub', None)
- else:
- result = self.do_genhash('stub', None)
- self.check_returned_native_str(result, "genhash")
+
+ # NOTE: changed in 1.7 -- previously if config string not supported,
+ # genhash() should throw TypeError; now just proxies .hash(secret, config=config)
+ result = self.do_genhash('stub', None)
+ self.check_returned_native_str(result, "genhash")
#
# test hash=int is rejected (picked as example of entirely wrong type)
@@ -2520,15 +2560,15 @@ class UserHandlerMixin(HandlerCase):
"""test user context keyword"""
handler = self.handler
password = 'stub'
- hash = handler.encrypt(password, user=self.default_user)
+ hash = handler.hash(password, user=self.default_user)
if self.requires_user:
- self.assertRaises(TypeError, handler.encrypt, password)
+ self.assertRaises(TypeError, handler.hash, password)
self.assertRaises(TypeError, handler.genhash, password, hash)
self.assertRaises(TypeError, handler.verify, password, hash)
else:
# e.g. cisco_pix works with or without one.
- handler.encrypt(password)
+ handler.hash(password)
handler.genhash(password, hash)
handler.verify(password, hash)
diff --git a/passlib/utils/__init__.py b/passlib/utils/__init__.py
index ab881bf..70d7ea9 100644
--- a/passlib/utils/__init__.py
+++ b/passlib/utils/__init__.py
@@ -134,7 +134,8 @@ class classproperty(object):
return self.im_func
def deprecated_function(msg=None, deprecated=None, removed=None, updoc=True,
- replacement=None, _is_method=False, func_module=None):
+ replacement=None, _is_method=False,
+ func_module=None):
"""decorator to deprecate a function.
:arg msg: optional msg, default chosen if omitted
@@ -156,6 +157,10 @@ def deprecated_function(msg=None, deprecated=None, removed=None, updoc=True,
msg += ", use %s instead" % replacement
msg += "."
def build(func):
+ is_classmethod = _is_method and isinstance(func, classmethod)
+ if is_classmethod:
+ # NOTE: PY26 doesn't support "classmethod().__func__" directly...
+ func = func.__get__(None, type).__func__
opts = dict(
mod=func_module or func.__module__,
name=func.__name__,
@@ -165,7 +170,7 @@ def deprecated_function(msg=None, deprecated=None, removed=None, updoc=True,
if _is_method:
def wrapper(*args, **kwds):
tmp = opts.copy()
- klass = args[0].__class__
+ klass = args[0] if is_classmethod else args[0].__class__
tmp.update(klass=klass.__name__, mod=klass.__module__)
warn(msg % tmp, DeprecationWarning, stacklevel=2)
return func(*args, **kwds)
@@ -190,6 +195,8 @@ def deprecated_function(msg=None, deprecated=None, removed=None, updoc=True,
if not wrapper.__doc__.strip(" ").endswith("\n"):
wrapper.__doc__ += "\n"
wrapper.__doc__ += "\n.. deprecated:: %s\n" % (txt,)
+ if is_classmethod:
+ wrapper = classmethod(wrapper)
return wrapper
return build
@@ -1598,8 +1605,7 @@ def generate_password(size=10, charset=_52charset):
_handler_attrs = (
"name",
"setting_kwds", "context_kwds",
- "genconfig", "genhash",
- "verify", "encrypt", "identify",
+ "verify", "hash", "identify",
)
def is_crypt_handler(obj):
diff --git a/passlib/utils/handlers.py b/passlib/utils/handlers.py
index c413c70..e61e72f 100644
--- a/passlib/utils/handlers.py
+++ b/passlib/utils/handlers.py
@@ -16,7 +16,7 @@ from passlib.ifc import PasswordHash
from passlib.registry import get_crypt_handler
from passlib.utils import classproperty, consteq, getrandstr, getrandbytes,\
BASE64_CHARS, HASH64_CHARS, rng, to_native_str, \
- is_crypt_handler, to_unicode, \
+ is_crypt_handler, to_unicode, deprecated_method, \
MAX_PASSWORD_SIZE
from passlib.utils.compat import join_byte_values, irange, u, native_string_types, \
uascii_to_str, join_unicode, unicode, str_to_uascii, \
@@ -318,15 +318,9 @@ class GenericHandler(PasswordHash):
.. attribute:: _stub_checksum
- [optional]
- If specified, hashes with this checksum will have their checksum
- normalized to ``None``, treating it like a config string.
- This is mainly used by hash formats which don't have a concept
- of a config string, so a unlikely-to-occur checksum (e.g. all zeros)
- is used by some implementations.
-
- This should be a string of the same datatype as :attr:`checksum`,
- or ``None``.
+ Placeholder checksum that will be used by genconfig()
+ in lieu of actually generating a hash for the empty string.
+ This should be a string of the same datatype as :attr:`checksum`.
Instance Attributes
===================
@@ -379,10 +373,6 @@ class GenericHandler(PasswordHash):
# if specified, _norm_checksum() will validate this
checksum_chars = None
- # if specified, hashes with this checksum will be treated
- # as if no checksum was specified.
- _stub_checksum = None
-
# private flag used by HasRawChecksum
_checksum_is_bytes = False
@@ -447,10 +437,6 @@ class GenericHandler(PasswordHash):
else:
raise exc.ExpectedTypeError(checksum, "unicode", "checksum")
- # handle stub
- if checksum == self._stub_checksum:
- return None
-
# check size
cc = self.checksum_size
if cc and len(checksum) != cc:
@@ -541,16 +527,20 @@ class GenericHandler(PasswordHash):
#===================================================================
#'crypt-style' interface (default implementation)
#===================================================================
- @classmethod
- def genconfig(cls, **settings):
- return cls(use_defaults=True, **settings).to_string()
- @classmethod
- def genhash(cls, secret, config, **context):
- validate_secret(secret)
- self = cls.from_string(config, **context)
- self.checksum = self._calc_checksum(secret)
- return self.to_string()
+ @property
+ def _stub_checksum(self):
+ """optional placeholder used by hash(config=True) so it can avoid calculating digest"""
+ if self.checksum_size:
+ if self._checksum_is_bytes:
+ return b'\x00' * self.checksum_size
+ if self.checksum_chars:
+ return self.checksum_chars[0] * self.checksum_size
+ if isinstance(self, HasRounds):
+ # otherwise genconfig() may run for unbounded amount of time
+ raise RuntimeError("%s: must provide stub checksum due to variable time cost" %
+ self.name)
+ return None
def _calc_checksum(self, secret): # pragma: no cover
"""given secret; calcuate and return encoded checksum portion of hash
@@ -565,10 +555,28 @@ class GenericHandler(PasswordHash):
#===================================================================
#'application' interface (default implementation)
#===================================================================
+
@classmethod
- def encrypt(cls, secret, **kwds):
+ def hash(cls, secret, config=None, **kwds):
validate_secret(secret)
- self = cls(use_defaults=True, **kwds)
+ if config is None:
+ # generate new config settings (salt, etc)
+ self = cls(use_defaults=True, **kwds)
+ elif config is True:
+ # NOTE: this is temporary until genconfig() is removed,
+ # at which point support for 'config=True' will probably be removed entirely.
+ # replacement for genconfig() --
+ # this uses optional stub checksum to bypass potentially expensive digest generation,
+ # when caller just wants the config string.
+ self = cls(use_defaults=True, **kwds)
+ self.checksum = self._stub_checksum
+ if self.checksum:
+ return self.to_string()
+ else:
+ # replacement for genhash() --
+ # reuse config settings from existing hash / config string.
+ # NOTE: 'kwds' should be subset of .context_kwds in this case
+ self = cls.from_string(config, **kwds)
self.checksum = self._calc_checksum(secret)
return self.to_string()
@@ -635,7 +643,7 @@ class GenericHandler(PasswordHash):
"""[experimental method] parse hash into dictionary of settings.
this essentially acts as the inverse of :meth:`encrypt`: for most
- cases, if ``hash = cls.encrypt(secret, **opts)``, then
+ cases, if ``hash = cls.hash(secret, **opts)``, then
``cls.parsehash(hash)`` will return a dict matching the original options
(with the extra keyword *checksum*).
@@ -733,18 +741,6 @@ class StaticHandler(GenericHandler):
assert self.checksum is not None
return uascii_to_str(self._hash_prefix + self.checksum)
- @classmethod
- def genconfig(cls):
- # since it has no settings, there's no need for a config string.
- return None
-
- @classmethod
- def genhash(cls, secret, config, **context):
- # since it has no settings, just verify config, and call encrypt()
- if config is not None and not cls.identify(config):
- raise exc.InvalidHashError(cls)
- return cls.encrypt(secret, **context)
-
# per-subclass: stores dynamically created subclass used by _calc_checksum() stub
__cc_compat_hack = None
@@ -800,13 +796,14 @@ class HasUserContext(GenericHandler):
# wrap funcs to accept 'user' as positional arg for ease of use.
@classmethod
- def encrypt(cls, secret, user=None, **context):
- return super(HasUserContext, cls).encrypt(secret, user=user, **context)
+ def hash(cls, secret, user=None, **context):
+ return super(HasUserContext, cls).hash(secret, user=user, **context)
@classmethod
def verify(cls, secret, hash, user=None, **context):
return super(HasUserContext, cls).verify(secret, hash, user=user, **context)
+ # NOTE: deprecated
@classmethod
def genhash(cls, secret, config, user=None, **context):
return super(HasUserContext, cls).genhash(secret, config, user=user, **context)
@@ -1531,7 +1528,7 @@ class HasRounds(GenericHandler):
assert isinstance(rounds, int_types)
elif self.use_defaults:
# warn if rounds is outside desired bounds only if user provided explicit rounds
- # to .encrypt() -- hence the .use_defaults check, which will be false if we're
+ # to .hash() -- hence the .use_defaults check, which will be false if we're
# coming from .verify() / .genhash()
explicit = True
@@ -1927,6 +1924,7 @@ class HasManyBackends(GenericHandler):
#=============================================================================
# wrappers
#=============================================================================
+# XXX: should this inherit from PasswordHash?
class PrefixWrapper(object):
"""wraps another handler, adding a constant prefix.
@@ -2121,21 +2119,23 @@ class PrefixWrapper(object):
hash = self._unwrap_hash(hash)
return self.wrapped.identify(hash)
+ @deprecated_method(deprecated="1.7", removed="2.0", replacement=".hash('', **settings)")
def genconfig(self, **kwds):
- config = self.wrapped.genconfig(**kwds)
- if config is None:
- return None
- else:
- return self._wrap_hash(config)
+ return self.hash(u(""), config=True, **kwds)
+ @deprecated_method(deprecated="1.7", removed="2.0", replacement=".hash(config=config)")
def genhash(self, secret, config, **kwds):
- if config is not None:
- config = to_unicode(config, "ascii", "config/hash")
- config = self._unwrap_hash(config)
- return self._wrap_hash(self.wrapped.genhash(secret, config, **kwds))
+ return self.hash(secret, config=config, **kwds)
+ @deprecated_method(deprecated="1.7", removed="2.0", replacement=".hash()")
def encrypt(self, secret, **kwds):
- return self._wrap_hash(self.wrapped.encrypt(secret, **kwds))
+ return self.hash(secret, **kwds)
+
+ def hash(self, secret, config=None, **kwds):
+ if not (config is None or config is True):
+ config = to_unicode(config, "ascii", "config/hash")
+ kwds['config'] = self._unwrap_hash(config)
+ return self._wrap_hash(self.wrapped.hash(secret, **kwds))
def verify(self, secret, hash, **kwds):
hash = to_unicode(hash, "ascii", "hash")