diff options
author | Eli Collins <elic@assurancetechnologies.com> | 2013-12-22 23:36:41 -0500 |
---|---|---|
committer | Eli Collins <elic@assurancetechnologies.com> | 2013-12-22 23:36:41 -0500 |
commit | 00fbf7362c1c8f85285188819b47c3a7668da244 (patch) | |
tree | cea3766dd0d4e179cb4598798c938d5f229f58d0 | |
parent | bffea42e623aa7229311f9b59144f600a8093815 (diff) | |
download | passlib-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-- | CHANGES | 2 | ||||
-rw-r--r-- | docs/lib/passlib.hash.dlitz_pbkdf2_sha1.rst | 18 | ||||
-rw-r--r-- | docs/password_hash_api.rst | 4 | ||||
-rw-r--r-- | passlib/handlers/django.py | 9 | ||||
-rw-r--r-- | passlib/handlers/fshp.py | 4 | ||||
-rw-r--r-- | passlib/handlers/pbkdf2.py | 14 | ||||
-rw-r--r-- | passlib/handlers/phpass.py | 4 | ||||
-rw-r--r-- | passlib/handlers/scram.py | 4 | ||||
-rw-r--r-- | passlib/handlers/sha1_crypt.py | 4 | ||||
-rw-r--r-- | passlib/handlers/sha2_crypt.py | 10 | ||||
-rw-r--r-- | passlib/handlers/sun_md5_crypt.py | 4 | ||||
-rw-r--r-- | passlib/tests/test_ext_django.py | 5 | ||||
-rw-r--r-- | tox.ini | 2 |
13 files changed, 55 insertions, 29 deletions
@@ -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): @@ -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} |