diff --git a/django/utils/crypto.py b/django/utils/crypto.py index 74dd9fd7b08..e5ae2108f29 100644 --- a/django/utils/crypto.py +++ b/django/utils/crypto.py @@ -1,11 +1,9 @@ """ Django's standard crypto functions and utilities. """ -import binascii import hashlib import hmac import random -import struct import time from django.conf import settings @@ -73,115 +71,17 @@ def get_random_string(length=12, return ''.join(random.choice(allowed_chars) for i in range(length)) -if hasattr(hmac, "compare_digest"): - # Prefer the stdlib implementation, when available. - def constant_time_compare(val1, val2): - return hmac.compare_digest(force_bytes(val1), force_bytes(val2)) -else: - def constant_time_compare(val1, val2): - """ - Returns True if the two strings are equal, False otherwise. - - The time taken is independent of the number of characters that match. - - For the sake of simplicity, this function executes in constant time only - when the two strings have the same length. It short-circuits when they - have different lengths. Since Django only uses it to compare hashes of - known expected length, this is acceptable. - """ - if len(val1) != len(val2): - return False - result = 0 - if isinstance(val1, bytes) and isinstance(val2, bytes): - for x, y in zip(val1, val2): - result |= x ^ y - else: - for x, y in zip(val1, val2): - result |= ord(x) ^ ord(y) - return result == 0 +def constant_time_compare(val1, val2): + """Return True if the two strings are equal, False otherwise.""" + return hmac.compare_digest(force_bytes(val1), force_bytes(val2)) -def _bin_to_long(x): - """ - Convert a binary string into a long integer - - This is a clever optimization for fast xor vector math - """ - return int(binascii.hexlify(x), 16) - - -def _long_to_bin(x, hex_format_string): - """ - Convert a long integer into a binary string. - hex_format_string is like "%020x" for padding 10 characters. - """ - return binascii.unhexlify((hex_format_string % x).encode('ascii')) - - -if hasattr(hashlib, "pbkdf2_hmac"): - def pbkdf2(password, salt, iterations, dklen=0, digest=None): - """ - Implements PBKDF2 with the same API as Django's existing - implementation, using the stdlib. - - This is used in Python 2.7.8+ and 3.4+. - """ - 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 - - HMAC+SHA256 is used as the default pseudo random function. - - As of 2014, 100,000 iterations was the recommended default which took - 100ms on a 2.7Ghz Intel i7 with an optimized implementation. 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 about five times slower than OpenSSL's implementation. Look in - django.contrib.auth.hashers for the present default, it is lower than - the recommended 100,000 because of the performance difference between - this and an optimized implementation. - """ - 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 - - hex_format_string = "%%0%ix" % (hlen * 2) - - 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)) - - def F(i): - u = salt + struct.pack(b'>I', i) - result = 0 - for j in range(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] +def pbkdf2(password, salt, iterations, dklen=0, digest=None): + """Return the hash of password using pbkdf2.""" + 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)