Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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.


git-svn-id: http://code.djangoproject.com/svn/django/trunk@17418 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 1030d66a14c29026efc6c8d3ad69ad2c57bf4589 1 parent ccbadbc
Paul McMillan authored February 02, 2012
30  django/utils/crypto.py
@@ -10,8 +10,8 @@
10 10
 from django.conf import settings
11 11
 
12 12
 
13  
-trans_5c = "".join([chr(x ^ 0x5C) for x in xrange(256)])
14  
-trans_36 = "".join([chr(x ^ 0x36) for x in xrange(256)])
  13
+_trans_5c = "".join([chr(x ^ 0x5C) for x in xrange(256)])
  14
+_trans_36 = "".join([chr(x ^ 0x36) for x in xrange(256)])
15 15
 
16 16
 
17 17
 def salted_hmac(key_salt, value, secret=None):
@@ -66,7 +66,7 @@ def constant_time_compare(val1, val2):
66 66
     return result == 0
67 67
 
68 68
 
69  
-def bin_to_long(x):
  69
+def _bin_to_long(x):
70 70
     """
71 71
     Convert a binary string into a long integer
72 72
 
@@ -75,17 +75,15 @@ def bin_to_long(x):
75 75
     return long(x.encode('hex'), 16)
76 76
 
77 77
 
78  
-def long_to_bin(x):
  78
+def _long_to_bin(x, hex_format_string):
79 79
     """
80  
-    Convert a long integer into a binary string
  80
+    Convert a long integer into a binary string.
  81
+    hex_format_string is like "%020x" for padding 10 characters.
81 82
     """
82  
-    hex = "%x" % (x)
83  
-    if len(hex) % 2 == 1:
84  
-        hex = '0' + hex
85  
-    return binascii.unhexlify(hex)
  83
+    return binascii.unhexlify(hex_format_string % x)
86 84
 
87 85
 
88  
-def fast_hmac(key, msg, digest):
  86
+def _fast_hmac(key, msg, digest):
89 87
     """
90 88
     A trimmed down version of Python's HMAC implementation
91 89
     """
@@ -93,9 +91,9 @@ def fast_hmac(key, msg, digest):
93 91
     if len(key) > dig1.block_size:
94 92
         key = digest(key).digest()
95 93
     key += chr(0) * (dig1.block_size - len(key))
96  
-    dig1.update(key.translate(trans_36))
  94
+    dig1.update(key.translate(_trans_36))
97 95
     dig1.update(msg)
98  
-    dig2.update(key.translate(trans_5c))
  96
+    dig2.update(key.translate(_trans_5c))
99 97
     dig2.update(dig1.digest())
100 98
     return dig2
101 99
 
@@ -123,13 +121,15 @@ def pbkdf2(password, salt, iterations, dklen=0, digest=None):
123 121
     l = -(-dklen // hlen)
124 122
     r = dklen - (l - 1) * hlen
125 123
 
  124
+    hex_format_string = "%%0%ix" % (hlen * 2)
  125
+
126 126
     def F(i):
127 127
         def U():
128 128
             u = salt + struct.pack('>I', i)
129 129
             for j in xrange(int(iterations)):
130  
-                u = fast_hmac(password, u, digest).digest()
131  
-                yield bin_to_long(u)
132  
-        return long_to_bin(reduce(operator.xor, U()))
  130
+                u = _fast_hmac(password, u, digest).digest()
  131
+                yield _bin_to_long(u)
  132
+        return _long_to_bin(reduce(operator.xor, U()), hex_format_string)
133 133
 
134 134
     T = [F(x) for x in range(1, l + 1)]
135 135
     return ''.join(T[:-1]) + T[-1][:r]
23  tests/regressiontests/utils/crypto.py
@@ -108,6 +108,17 @@ class TestUtilsCryptoPBKDF2(unittest.TestCase):
108 108
                        "c4007d5298f9033c0241d5ab69305e7b64eceeb8d"
109 109
                        "834cfec"),
110 110
         },
  111
+        # Check leading zeros are not stripped (#17481) 
  112
+        {
  113
+            "args": { 
  114
+                "password": chr(186), 
  115
+                "salt": "salt", 
  116
+                "iterations": 1, 
  117
+                "dklen": 20, 
  118
+                "digest": hashlib.sha1, 
  119
+            }, 
  120
+            "result": '0053d3b91a7f1e54effebd6d68771e8a6e0b2c5b',
  121
+        },
111 122
     ]
112 123
 
113 124
     def test_public_vectors(self):
@@ -125,11 +136,15 @@ def test_performance_scalability(self):
125 136
         Theory: If you run with 100 iterations, it should take 100
126 137
         times as long as running with 1 iteration.
127 138
         """
128  
-        n1, n2 = 1000, 100000
129  
-        elapsed = lambda f: timeit.Timer(f, 'from django.utils.crypto import pbkdf2').timeit(number=1)
  139
+        # These values are chosen as a reasonable tradeoff between time
  140
+        # to run the test suite and false positives caused by imprecise
  141
+        # measurement.
  142
+        n1, n2 = 200000, 800000
  143
+        elapsed = lambda f: timeit.Timer(f, 
  144
+                    'from django.utils.crypto import pbkdf2').timeit(number=1)
130 145
         t1 = elapsed('pbkdf2("password", "salt", iterations=%d)' % n1)
131 146
         t2 = elapsed('pbkdf2("password", "salt", iterations=%d)' % n2)
132 147
         measured_scale_exponent = math.log(t2 / t1, n2 / n1)
133  
-        # This should be less than 1. We allow up to 1.1 so that tests don't 
  148
+        # This should be less than 1. We allow up to 1.2 so that tests don't 
134 149
         # fail nondeterministically too often.
135  
-        self.assertLess(measured_scale_exponent, 1.1)
  150
+        self.assertLess(measured_scale_exponent, 1.2)

0 notes on commit 1030d66

Please sign in to comment.
Something went wrong with that request. Please try again.