summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEli Collins <elic@assurancetechnologies.com>2013-12-22 23:36:41 -0500
committerEli Collins <elic@assurancetechnologies.com>2013-12-22 23:36:41 -0500
commit00fbf7362c1c8f85285188819b47c3a7668da244 (patch)
treecea3766dd0d4e179cb4598798c938d5f229f58d0
parentbffea42e623aa7229311f9b59144f600a8093815 (diff)
downloadpasslib-00fbf7362c1c8f85285188819b47c3a7668da244.tar.gz
updated rounds values based on timing tests. also:
* a number of hashes now feed off pbkdf2_XXX.default_rounds * added security note re: dlitz_pbkdf2_sha1
-rw-r--r--CHANGES2
-rw-r--r--docs/lib/passlib.hash.dlitz_pbkdf2_sha1.rst18
-rw-r--r--docs/password_hash_api.rst4
-rw-r--r--passlib/handlers/django.py9
-rw-r--r--passlib/handlers/fshp.py4
-rw-r--r--passlib/handlers/pbkdf2.py14
-rw-r--r--passlib/handlers/phpass.py4
-rw-r--r--passlib/handlers/scram.py4
-rw-r--r--passlib/handlers/sha1_crypt.py4
-rw-r--r--passlib/handlers/sha2_crypt.py10
-rw-r--r--passlib/handlers/sun_md5_crypt.py4
-rw-r--r--passlib/tests/test_ext_django.py5
-rw-r--r--tox.ini2
13 files changed, 55 insertions, 29 deletions
diff --git a/CHANGES b/CHANGES
index 96213a6..88365d4 100644
--- a/CHANGES
+++ b/CHANGES
@@ -7,6 +7,8 @@ Release History
**1.6.2** (NOT YET RELEASED)
============================
+ * Updated the :attr:`~passlib.ifc.PasswordHash.default_rounds` values for all of the hashes.
+
* *BCrypt*: Added support for the `bcrypt <https://pypi.python.org/pypi/bcrypt>`_
library as of the possible bcrypt backends that will be used if available.
(:issue:`49`)
diff --git a/docs/lib/passlib.hash.dlitz_pbkdf2_sha1.rst b/docs/lib/passlib.hash.dlitz_pbkdf2_sha1.rst
index b55ef05..d353fae 100644
--- a/docs/lib/passlib.hash.dlitz_pbkdf2_sha1.rst
+++ b/docs/lib/passlib.hash.dlitz_pbkdf2_sha1.rst
@@ -1,8 +1,13 @@
+.. index:: pbkdf2 hash; dlitz
+
===========================================================================
:class:`passlib.hash.dlitz_pbkdf2_sha1` - Dwayne Litzenberger's PBKDF2 hash
===========================================================================
-.. index:: pbkdf2 hash; dlitz
+.. note::
+
+ Due to a small flaw, this hash is not as strong as other PBKDF1-HMAC-SHA1
+ based hashes. It should probably not be used for new applications.
.. currentmodule:: passlib.hash
@@ -53,6 +58,17 @@ the specified number of rounds, and using HMAC-SHA1 as it's psuedorandom functio
24 bytes of derived key are requested, and the resulting key is encoded and used
as the checksum portion of the hash.
+Security Issues
+===============
+
+* *Extra Block:* This hash generates 24 bytes using PBKDF2-HMAC-SHA1.
+ Since SHA1 has a digest size of only 20 bytes, this means an second PBKDF2
+ block must be generated for each :class:`dlitz_pbkdf2_sha1` hash.
+ While a normal user has to calculate both blocks, a dedicated attacker
+ would only have to calculate the first block when brute-forcing,
+ taking half the time. That means this hash is half as strong as other
+ PBKDF2-HMAC-SHA1 based hashes (given a fixed amount of time spent by the user).
+
.. rubric:: Footnotes
.. [#dlitz] The reference for this hash format - `<http://www.dlitz.net/software/python-pbkdf2/>`_.
diff --git a/docs/password_hash_api.rst b/docs/password_hash_api.rst
index d2ec5d5..bb323cc 100644
--- a/docs/password_hash_api.rst
+++ b/docs/password_hash_api.rst
@@ -740,5 +740,5 @@ However, some older algorithms (e.g. :class:`~passlib.hash.bsdi_crypt`) are weak
a tradeoff must be made, choosing "secure but intolerably slow" over "fast but unacceptably insecure".
For this reason, it is strongly recommended to not use a value much lower than Passlib's default.
-.. [#avgsys] For Passlib 1.6, all hashes were retuned to take ~250ms on a
- system with a 3 ghz 64 bit CPU.
+.. [#avgsys] For Passlib 1.6, all hashes were retuned to take ~300ms on a
+ system with a 2.5 ghz 64 bit CPU.
diff --git a/passlib/handlers/django.py b/passlib/handlers/django.py
index 83e8860..cdb853b 100644
--- a/passlib/handlers/django.py
+++ b/passlib/handlers/django.py
@@ -11,7 +11,7 @@ import logging; log = logging.getLogger(__name__)
from warnings import warn
# site
# pkg
-from passlib.hash import bcrypt
+from passlib.hash import bcrypt, pbkdf2_sha1, pbkdf2_sha256
from passlib.utils import to_unicode, classproperty
from passlib.utils.compat import b, bytes, str_to_uascii, uascii_to_str, unicode, u
from passlib.utils.pbkdf2 import pbkdf2
@@ -270,7 +270,7 @@ class django_pbkdf2_sha256(DjangoVariableHash):
:type rounds: int
:param rounds:
Optional number of rounds to use.
- Defaults to 10000, but must be within ``range(1,1<<32)``.
+ Defaults to 20000, but must be within ``range(1,1<<32)``.
:type relaxed: bool
:param relaxed:
@@ -292,7 +292,7 @@ class django_pbkdf2_sha256(DjangoVariableHash):
max_rounds = 0xffffffff # setting at 32-bit limit for now
checksum_chars = uh.PADDED_BASE64_CHARS
checksum_size = 44 # 32 bytes -> base64
- default_rounds = 12000 # NOTE: using django default here
+ default_rounds = pbkdf2_sha256.default_rounds # NOTE: django 1.6 uses 12000
_prf = "hmac-sha256"
def _calc_checksum(self, secret):
@@ -323,7 +323,7 @@ class django_pbkdf2_sha1(django_pbkdf2_sha256):
:type rounds: int
:param rounds:
Optional number of rounds to use.
- Defaults to 10000, but must be within ``range(1,1<<32)``.
+ Defaults to 60000, but must be within ``range(1,1<<32)``.
:type relaxed: bool
:param relaxed:
@@ -342,6 +342,7 @@ class django_pbkdf2_sha1(django_pbkdf2_sha256):
django_name = "pbkdf2_sha1"
ident = u('pbkdf2_sha1$')
checksum_size = 28 # 20 bytes -> base64
+ default_rounds = pbkdf2_sha1.default_rounds # NOTE: django 1.6 uses 12000
_prf = "hmac-sha1"
#=============================================================================
diff --git a/passlib/handlers/fshp.py b/passlib/handlers/fshp.py
index 3ecb7b6..6efc782 100644
--- a/passlib/handlers/fshp.py
+++ b/passlib/handlers/fshp.py
@@ -40,7 +40,7 @@ class fshp(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
:param rounds:
Optional number of rounds to use.
- Defaults to 50000, must be between 1 and 4294967295, inclusive.
+ Defaults to 100000, must be between 1 and 4294967295, inclusive.
:param variant:
Optionally specifies variant of FSHP to use.
@@ -79,7 +79,7 @@ class fshp(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
#--HasRounds--
# FIXME: should probably use different default rounds
# based on the variant. setting for default variant (sha256) for now.
- default_rounds = 50000 # current passlib default, FSHP uses 4096
+ default_rounds = 100000 # current passlib default, FSHP uses 4096
min_rounds = 1 # set by FSHP
max_rounds = 4294967295 # 32-bit integer limit - not set by FSHP
rounds_cost = "linear"
diff --git a/passlib/handlers/pbkdf2.py b/passlib/handlers/pbkdf2.py
index 931521b..cadbbea 100644
--- a/passlib/handlers/pbkdf2.py
+++ b/passlib/handlers/pbkdf2.py
@@ -136,8 +136,8 @@ def create_pbkdf2_hash(hash_name, digest_size, rounds=12000, ident=None, module=
# derived handlers
#------------------------------------------------------------------------
pbkdf2_sha1 = create_pbkdf2_hash("sha1", 20, 60000, ident=u("$pbkdf2$"))
-pbkdf2_sha256 = create_pbkdf2_hash("sha256", 32)
-pbkdf2_sha512 = create_pbkdf2_hash("sha512", 64)
+pbkdf2_sha256 = create_pbkdf2_hash("sha256", 32, 20000)
+pbkdf2_sha512 = create_pbkdf2_hash("sha512", 64, 19000)
ldap_pbkdf2_sha1 = uh.PrefixWrapper("ldap_pbkdf2_sha1", pbkdf2_sha1, "{PBKDF2}", "$pbkdf2$", ident=True)
ldap_pbkdf2_sha256 = uh.PrefixWrapper("ldap_pbkdf2_sha256", pbkdf2_sha256, "{PBKDF2-SHA256}", "$pbkdf2-sha256$", ident=True)
@@ -202,7 +202,7 @@ class cta_pbkdf2_sha1(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.Generic
max_salt_size = 1024
#--HasRounds--
- default_rounds = 60000
+ default_rounds = pbkdf2_sha1.default_rounds
min_rounds = 1
max_rounds = 0xffffffff # setting at 32-bit limit for now
rounds_cost = "linear"
@@ -303,7 +303,9 @@ class dlitz_pbkdf2_sha1(uh.HasRounds, uh.HasSalt, uh.GenericHandler):
salt_chars = uh.HASH64_CHARS
#--HasRounds--
- default_rounds = 60000
+ # NOTE: for security, the default here is set to match pbkdf2_sha1,
+ # even though this hash's extra block makes it twice as slow.
+ default_rounds = pbkdf2_sha1.default_rounds
min_rounds = 1
max_rounds = 0xffffffff # setting at 32-bit limit for now
rounds_cost = "linear"
@@ -430,7 +432,7 @@ class grub_pbkdf2_sha512(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.Gene
:type rounds: int
:param rounds:
Optional number of rounds to use.
- Defaults to 12000, but must be within ``range(1,1<<32)``.
+ Defaults to 19000, but must be within ``range(1,1<<32)``.
:type relaxed: bool
:param relaxed:
@@ -455,7 +457,7 @@ class grub_pbkdf2_sha512(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.Gene
min_salt_size = 0
max_salt_size = 1024
- default_rounds = 12000
+ default_rounds = pbkdf2_sha512.default_rounds
min_rounds = 1
max_rounds = 0xffffffff # setting at 32-bit limit for now
rounds_cost = "linear"
diff --git a/passlib/handlers/phpass.py b/passlib/handlers/phpass.py
index bf09181..45bd9a6 100644
--- a/passlib/handlers/phpass.py
+++ b/passlib/handlers/phpass.py
@@ -42,7 +42,7 @@ class phpass(uh.HasManyIdents, uh.HasRounds, uh.HasSalt, uh.GenericHandler):
:type rounds: int
:param rounds:
Optional number of rounds to use.
- Defaults to 16, must be between 7 and 30, inclusive.
+ Defaults to 17, must be between 7 and 30, inclusive.
This value is logarithmic, the actual number of iterations used will be :samp:`2**{rounds}`.
:type ident: str
@@ -75,7 +75,7 @@ class phpass(uh.HasManyIdents, uh.HasRounds, uh.HasSalt, uh.GenericHandler):
salt_chars = uh.HASH64_CHARS
#--HasRounds--
- default_rounds = 16
+ default_rounds = 17
min_rounds = 7
max_rounds = 30
rounds_cost = "log2"
diff --git a/passlib/handlers/scram.py b/passlib/handlers/scram.py
index 0dbc823..1c5f9e8 100644
--- a/passlib/handlers/scram.py
+++ b/passlib/handlers/scram.py
@@ -49,7 +49,7 @@ class scram(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
:type rounds: int
:param rounds:
Optional number of rounds to use.
- Defaults to 6400, but must be within ``range(1,1<<32)``.
+ Defaults to 20000, but must be within ``range(1,1<<32)``.
:type algs: list of strings
:param algs:
@@ -102,7 +102,7 @@ class scram(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
max_salt_size = 1024
#--HasRounds--
- default_rounds = 6400
+ default_rounds = 20000
min_rounds = 1
max_rounds = 2**32-1
rounds_cost = "linear"
diff --git a/passlib/handlers/sha1_crypt.py b/passlib/handlers/sha1_crypt.py
index 4dfaf5a..885c67f 100644
--- a/passlib/handlers/sha1_crypt.py
+++ b/passlib/handlers/sha1_crypt.py
@@ -47,7 +47,7 @@ class sha1_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler
:type rounds: int
:param rounds:
Optional number of rounds to use.
- Defaults to 40000, must be between 1 and 4294967295, inclusive.
+ Defaults to 64000, must be between 1 and 4294967295, inclusive.
:type relaxed: bool
:param relaxed:
@@ -77,7 +77,7 @@ class sha1_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler
salt_chars = uh.HASH64_CHARS
#--HasRounds--
- default_rounds = 40000 # current passlib default
+ default_rounds = 64000 # current passlib default
min_rounds = 1 # really, this should be higher.
max_rounds = 4294967295 # 32-bit integer limit
rounds_cost = "linear"
diff --git a/passlib/handlers/sha2_crypt.py b/passlib/handlers/sha2_crypt.py
index dbd848f..c4faaad 100644
--- a/passlib/handlers/sha2_crypt.py
+++ b/passlib/handlers/sha2_crypt.py
@@ -374,7 +374,7 @@ class sha256_crypt(_SHA2_Common):
:type rounds: int
:param rounds:
Optional number of rounds to use.
- Defaults to 80000, must be between 1000 and 999999999, inclusive.
+ Defaults to 110000, must be between 1000 and 999999999, inclusive.
:type implicit_rounds: bool
:param implicit_rounds:
@@ -401,7 +401,8 @@ class sha256_crypt(_SHA2_Common):
name = "sha256_crypt"
ident = u("$5$")
checksum_size = 43
- default_rounds = 80000 # current passlib default
+ # NOTE: using 25/75 weighting of builtin & os_crypt backends
+ default_rounds = 110000
#===================================================================
# backends
@@ -434,7 +435,7 @@ class sha512_crypt(_SHA2_Common):
:type rounds: int
:param rounds:
Optional number of rounds to use.
- Defaults to 60000, must be between 1000 and 999999999, inclusive.
+ Defaults to 100000, must be between 1000 and 999999999, inclusive.
:type implicit_rounds: bool
:param implicit_rounds:
@@ -463,7 +464,8 @@ class sha512_crypt(_SHA2_Common):
ident = u("$6$")
checksum_size = 86
_cdb_use_512 = True
- default_rounds = 60000 # current passlib default
+ # NOTE: using 25/75 weighting of builtin & os_crypt backends
+ default_rounds = 100000
#===================================================================
# backend
diff --git a/passlib/handlers/sun_md5_crypt.py b/passlib/handlers/sun_md5_crypt.py
index a99289c..41d3331 100644
--- a/passlib/handlers/sun_md5_crypt.py
+++ b/passlib/handlers/sun_md5_crypt.py
@@ -193,7 +193,7 @@ class sun_md5_crypt(uh.HasRounds, uh.HasSalt, uh.GenericHandler):
:type rounds: int
:param rounds:
Optional number of rounds to use.
- Defaults to 5000, must be between 0 and 4294963199, inclusive.
+ Defaults to 5500, must be between 0 and 4294963199, inclusive.
:type bare_salt: bool
:param bare_salt:
@@ -231,7 +231,7 @@ class sun_md5_crypt(uh.HasRounds, uh.HasSalt, uh.GenericHandler):
max_salt_size = None
salt_chars = uh.HASH64_CHARS
- default_rounds = 5000 # current passlib default
+ default_rounds = 5500 # current passlib default
min_rounds = 0
max_rounds = 4294963199 ##2**32-1-4096
# XXX: ^ not sure what it does if past this bound... does 32 int roll over?
diff --git a/passlib/tests/test_ext_django.py b/passlib/tests/test_ext_django.py
index 54a6a78..dd40ab6 100644
--- a/passlib/tests/test_ext_django.py
+++ b/passlib/tests/test_ext_django.py
@@ -112,7 +112,9 @@ sample_hashes = {} # override sample hashes used in test cases
if DJANGO_VERSION >= (1,6):
stock_config = django16_context.to_dict()
stock_config.update(
- deprecated="auto"
+ deprecated="auto",
+ django_pbkdf2_sha1__default_rounds=12000,
+ django_pbkdf2_sha256__default_rounds=12000,
)
sample_hashes.update(
django_pbkdf2_sha256=("not a password", "pbkdf2_sha256$12000$rpUPFQOVetrY$cEcWG4DjjDpLrDyXnduM+XJUz25U63RcM3//xaFnBnw="),
@@ -121,6 +123,7 @@ elif DJANGO_VERSION >= (1,4):
stock_config = django14_context.to_dict()
stock_config.update(
deprecated="auto",
+ django_pbkdf2_sha1__default_rounds=10000,
django_pbkdf2_sha256__default_rounds=10000,
)
elif DJANGO_VERSION >= (1,0):
diff --git a/tox.ini b/tox.ini
index e9b8ea1..570d9b0 100644
--- a/tox.ini
+++ b/tox.ini
@@ -138,7 +138,7 @@ commands =
[testenv:django15]
deps =
django<1.6
- bcrypt
+ py-bcrypt
{[testenv]deps}
commands =
nosetests {posargs:passlib.tests.test_ext_django passlib.tests.test_handlers_django}