From cb68eb3e6dd740185734ffcbc4e287860fcc118d Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 17 Apr 2014 11:02:42 -0700 Subject: [PATCH] Use the stdlib's PBKDF2 implementation when available. This is a bit faster than ours, which is good, because it lets you increase the iteration counts. This will be used on Python 3.4+, and, pending the acceptance of PEP466, on newer Python 2.7s. --- django/db/models/expressions.py | 7 ++- django/utils/crypto.py | 101 +++++++++++++++++++------------- 2 files changed, 63 insertions(+), 45 deletions(-) diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py index 38b656162db..b84398d9ce9 100644 --- a/django/db/models/expressions.py +++ b/django/db/models/expressions.py @@ -15,9 +15,10 @@ class ExpressionNode(tree.Node): MUL = '*' DIV = '/' POW = '^' - MOD = '%%' # This is a quoted % operator - it is quoted - # because it can be used in strings that also - # have parameter substitution. + # This is a quoted % operator - it is quoted + # because it can be used in strings that also + # have parameter substitution. + MOD = '%%' # Bitwise operators - note that these are generated by .bitand() # and .bitor(), the '&' and '|' are reserved for boolean operator diff --git a/django/utils/crypto.py b/django/utils/crypto.py index 21395fe8a3c..c70f9bbdebf 100644 --- a/django/utils/crypto.py +++ b/django/utils/crypto.py @@ -117,51 +117,68 @@ def _long_to_bin(x, hex_format_string): return binascii.unhexlify((hex_format_string % x).encode('ascii')) -def pbkdf2(password, salt, iterations, dklen=0, digest=None): - """ - Implements PBKDF2 as defined in RFC 2898, section 5.2 +if hasattr(hashlib, "pbkdf2_hmac"): + def pbkdf2(password, salt, iterations, dklen=0, digest=None): + """ + Implements PBDF2 with the same API as Django's existing implementation, + using the stdlib. - HMAC+SHA256 is used as the default pseudo random function. + This is used in Python 3.4 and up. + """ + if digest is None: + digest = hashlib.sha256 + if not dklen: + dklen = None + password = force_bytes(password) + salt = force_bytes(salt) + return hashlib.pbkdf2_hmac( + digest().name, password, salt, iterations, dklen) +else: + def pbkdf2(password, salt, iterations, dklen=0, digest=None): + """ + Implements PBKDF2 as defined in RFC 2898, section 5.2 - As of 2011, 10,000 iterations was the recommended default which - took 100ms on a 2.2Ghz Core 2 Duo. This is probably the bare - minimum for security given 1000 iterations was recommended in - 2001. This code is very well optimized for CPython and is only - four times slower than openssl's implementation. Look in - django.contrib.auth.hashers for the present default. - """ - assert iterations > 0 - if not digest: - digest = hashlib.sha256 - password = force_bytes(password) - salt = force_bytes(salt) - hlen = digest().digest_size - if not dklen: - dklen = hlen - if dklen > (2 ** 32 - 1) * hlen: - raise OverflowError('dklen too big') - l = -(-dklen // hlen) - r = dklen - (l - 1) * hlen + HMAC+SHA256 is used as the default pseudo random function. - hex_format_string = "%%0%ix" % (hlen * 2) + As of 2011, 10,000 iterations was the recommended default which + took 100ms on a 2.2Ghz Core 2 Duo. This is probably the bare + minimum for security given 1000 iterations was recommended in + 2001. This code is very well optimized for CPython and is only + four times slower than openssl's implementation. Look in + django.contrib.auth.hashers for the present default. + """ + assert iterations > 0 + if not digest: + digest = hashlib.sha256 + password = force_bytes(password) + salt = force_bytes(salt) + hlen = digest().digest_size + if not dklen: + dklen = hlen + if dklen > (2 ** 32 - 1) * hlen: + raise OverflowError('dklen too big') + l = -(-dklen // hlen) + r = dklen - (l - 1) * hlen - inner, outer = digest(), digest() - if len(password) > inner.block_size: - password = digest(password).digest() - password += b'\x00' * (inner.block_size - len(password)) - inner.update(password.translate(hmac.trans_36)) - outer.update(password.translate(hmac.trans_5C)) + hex_format_string = "%%0%ix" % (hlen * 2) - def F(i): - u = salt + struct.pack(b'>I', i) - result = 0 - for j in xrange(int(iterations)): - dig1, dig2 = inner.copy(), outer.copy() - dig1.update(u) - dig2.update(dig1.digest()) - u = dig2.digest() - result ^= _bin_to_long(u) - return _long_to_bin(result, hex_format_string) + inner, outer = digest(), digest() + if len(password) > inner.block_size: + password = digest(password).digest() + password += b'\x00' * (inner.block_size - len(password)) + inner.update(password.translate(hmac.trans_36)) + outer.update(password.translate(hmac.trans_5C)) - T = [F(x) for x in range(1, l)] - return b''.join(T) + F(l)[:r] + def F(i): + u = salt + struct.pack(b'>I', i) + result = 0 + for j in xrange(int(iterations)): + dig1, dig2 = inner.copy(), outer.copy() + dig1.update(u) + dig2.update(dig1.digest()) + u = dig2.digest() + result ^= _bin_to_long(u) + return _long_to_bin(result, hex_format_string) + + T = [F(x) for x in range(1, l)] + return b''.join(T) + F(l)[:r]