diff --git a/django/utils/crypto.py b/django/utils/crypto.py index 70a07e7fde..e8fd2b1e2d 100644 --- a/django/utils/crypto.py +++ b/django/utils/crypto.py @@ -24,6 +24,7 @@ except NotImplementedError: from django.conf import settings from django.utils.encoding import smart_bytes +from django.utils import six from django.utils.six.moves import xrange @@ -81,15 +82,21 @@ def get_random_string(length=12, def constant_time_compare(val1, val2): """ - Returns True if the two strings are equal, False otherwise. + Returns True if the two bytestrings are equal, False otherwise. The time taken is independent of the number of characters that match. """ + if not (isinstance(val1, bytes) and isinstance(val2, bytes)): + raise TypeError("constant_time_compare only supports bytes") if len(val1) != len(val2): return False result = 0 - for x, y in zip(val1, val2): - result |= ord(x) ^ ord(y) + if six.PY3: + 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 diff --git a/tests/regressiontests/utils/crypto.py b/tests/regressiontests/utils/crypto.py index 52a286cb27..447b2e5b80 100644 --- a/tests/regressiontests/utils/crypto.py +++ b/tests/regressiontests/utils/crypto.py @@ -6,7 +6,17 @@ import timeit import hashlib from django.utils import unittest -from django.utils.crypto import pbkdf2 +from django.utils.crypto import constant_time_compare, pbkdf2 + + +class TestUtilsCryptoMisc(unittest.TestCase): + + def test_constant_time_compare(self): + # It's hard to test for constant time, just test the result. + self.assertTrue(constant_time_compare(b'spam', b'spam')) + self.assertFalse(constant_time_compare(b'spam', b'eggs')) + with self.assertRaises(TypeError): + constant_time_compare('spam', 'spam') class TestUtilsCryptoPBKDF2(unittest.TestCase): diff --git a/tests/regressiontests/utils/tests.py b/tests/regressiontests/utils/tests.py index 91c0fd67e8..08f5f760cc 100644 --- a/tests/regressiontests/utils/tests.py +++ b/tests/regressiontests/utils/tests.py @@ -6,7 +6,7 @@ from __future__ import absolute_import from .archive import TestBzip2Tar, TestGzipTar, TestTar, TestZip from .baseconv import TestBaseConv from .checksums import TestUtilsChecksums -from .crypto import TestUtilsCryptoPBKDF2 +from .crypto import TestUtilsCryptoMisc, TestUtilsCryptoPBKDF2 from .datastructures import (DictWrapperTests, ImmutableListTests, MergeDictTests, MultiValueDictTests, SortedDictTests) from .dateformat import DateFormatTests