Author: PaulM Date: 2012-02-01 20:44:17 -0800 (Wed, 01 Feb 2012) New Revision: 17418
Modified: django/trunk/django/utils/crypto.py django/trunk/tests/regressiontests/utils/crypto.py Log: Fixed #17481. pbkdf2 hashes no longer ommit leading zeros. Some existing user passwords may need to be reset or converted after this change. See the 1.4-beta release notes for more details. Thanks bhuztez for the report and initial patch, claudep for the test. Modified: django/trunk/django/utils/crypto.py =================================================================== --- django/trunk/django/utils/crypto.py 2012-02-01 21:36:18 UTC (rev 17417) +++ django/trunk/django/utils/crypto.py 2012-02-02 04:44:17 UTC (rev 17418) @@ -10,8 +10,8 @@ from django.conf import settings -trans_5c = "".join([chr(x ^ 0x5C) for x in xrange(256)]) -trans_36 = "".join([chr(x ^ 0x36) for x in xrange(256)]) +_trans_5c = "".join([chr(x ^ 0x5C) for x in xrange(256)]) +_trans_36 = "".join([chr(x ^ 0x36) for x in xrange(256)]) def salted_hmac(key_salt, value, secret=None): @@ -66,7 +66,7 @@ return result == 0 -def bin_to_long(x): +def _bin_to_long(x): """ Convert a binary string into a long integer @@ -75,17 +75,15 @@ return long(x.encode('hex'), 16) -def long_to_bin(x): +def _long_to_bin(x, hex_format_string): """ - Convert a long integer into a binary string + Convert a long integer into a binary string. + hex_format_string is like "%020x" for padding 10 characters. """ - hex = "%x" % (x) - if len(hex) % 2 == 1: - hex = '0' + hex - return binascii.unhexlify(hex) + return binascii.unhexlify(hex_format_string % x) -def fast_hmac(key, msg, digest): +def _fast_hmac(key, msg, digest): """ A trimmed down version of Python's HMAC implementation """ @@ -93,9 +91,9 @@ if len(key) > dig1.block_size: key = digest(key).digest() key += chr(0) * (dig1.block_size - len(key)) - dig1.update(key.translate(trans_36)) + dig1.update(key.translate(_trans_36)) dig1.update(msg) - dig2.update(key.translate(trans_5c)) + dig2.update(key.translate(_trans_5c)) dig2.update(dig1.digest()) return dig2 @@ -123,13 +121,15 @@ l = -(-dklen // hlen) r = dklen - (l - 1) * hlen + hex_format_string = "%%0%ix" % (hlen * 2) + def F(i): def U(): u = salt + struct.pack('>I', i) for j in xrange(int(iterations)): - u = fast_hmac(password, u, digest).digest() - yield bin_to_long(u) - return long_to_bin(reduce(operator.xor, U())) + u = _fast_hmac(password, u, digest).digest() + yield _bin_to_long(u) + return _long_to_bin(reduce(operator.xor, U()), hex_format_string) T = [F(x) for x in range(1, l + 1)] return ''.join(T[:-1]) + T[-1][:r] Modified: django/trunk/tests/regressiontests/utils/crypto.py =================================================================== --- django/trunk/tests/regressiontests/utils/crypto.py 2012-02-01 21:36:18 UTC (rev 17417) +++ django/trunk/tests/regressiontests/utils/crypto.py 2012-02-02 04:44:17 UTC (rev 17418) @@ -108,6 +108,17 @@ "c4007d5298f9033c0241d5ab69305e7b64eceeb8d" "834cfec"), }, + # Check leading zeros are not stripped (#17481) + { + "args": { + "password": chr(186), + "salt": "salt", + "iterations": 1, + "dklen": 20, + "digest": hashlib.sha1, + }, + "result": '0053d3b91a7f1e54effebd6d68771e8a6e0b2c5b', + }, ] def test_public_vectors(self): @@ -125,11 +136,15 @@ Theory: If you run with 100 iterations, it should take 100 times as long as running with 1 iteration. """ - n1, n2 = 1000, 100000 - elapsed = lambda f: timeit.Timer(f, 'from django.utils.crypto import pbkdf2').timeit(number=1) + # These values are chosen as a reasonable tradeoff between time + # to run the test suite and false positives caused by imprecise + # measurement. + n1, n2 = 200000, 800000 + elapsed = lambda f: timeit.Timer(f, + 'from django.utils.crypto import pbkdf2').timeit(number=1) t1 = elapsed('pbkdf2("password", "salt", iterations=%d)' % n1) t2 = elapsed('pbkdf2("password", "salt", iterations=%d)' % n2) measured_scale_exponent = math.log(t2 / t1, n2 / n1) - # This should be less than 1. We allow up to 1.1 so that tests don't + # This should be less than 1. We allow up to 1.2 so that tests don't # fail nondeterministically too often. - self.assertLess(measured_scale_exponent, 1.1) + self.assertLess(measured_scale_exponent, 1.2) -- You received this message because you are subscribed to the Google Groups "Django updates" group. To post to this group, send email to django-updates@googlegroups.com. To unsubscribe from this group, send email to django-updates+unsubscr...@googlegroups.com. For more options, visit this group at http://groups.google.com/group/django-updates?hl=en.