diff --git a/django/utils/crypto.py b/django/utils/crypto.py index d02ee5351d..8bf884a72f 100644 --- a/django/utils/crypto.py +++ b/django/utils/crypto.py @@ -77,27 +77,31 @@ def get_random_string(length=12, return ''.join(random.choice(allowed_chars) for i in range(length)) -def constant_time_compare(val1, val2): - """ - Returns True if the two strings are equal, False otherwise. +if hasattr(hmac, "compare_digest"): + # Prefer the stdlib implementation, when available. + constant_time_compare = hmac.compare_digest +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. + 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 six.PY3 and 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 + 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 six.PY3 and 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 _bin_to_long(x):