From 62954ba04c86c4cea3b17a872ba6a0837730e17d Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Mon, 20 Aug 2012 22:45:13 +0200 Subject: [PATCH] [py3] Fixed #17040 -- ported django.utils.crypto.constant_time_compare. This is a private API; adding a type check is acceptable. --- django/utils/crypto.py | 13 ++++++++++--- tests/regressiontests/utils/crypto.py | 12 +++++++++++- tests/regressiontests/utils/tests.py | 2 +- 3 files changed, 22 insertions(+), 5 deletions(-) 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