Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Revert "Ensure that passwords are never long enough for a DoS."

This reverts commit aae5a96.

This fix is no longer necessary, our pbkdf2 (see next commit) implementation
no longer rehashes the password every iteration.
  • Loading branch information...
commit 5d74853e156105ea02a41f4731346dbe272c2412 1 parent 9a04180
Florian Apolloner authored September 21, 2013
48  django/contrib/auth/forms.py
@@ -14,9 +14,7 @@
14 14
 
15 15
 from django.contrib.auth import authenticate, get_user_model
16 16
 from django.contrib.auth.models import User
17  
-from django.contrib.auth.hashers import (
18  
-    MAXIMUM_PASSWORD_LENGTH, UNUSABLE_PASSWORD_PREFIX, identify_hasher,
19  
-)
  17
+from django.contrib.auth.hashers import UNUSABLE_PASSWORD_PREFIX, identify_hasher
20 18
 from django.contrib.auth.tokens import default_token_generator
21 19
 from django.contrib.sites.models import get_current_site
22 20
 
@@ -82,10 +80,9 @@ class UserCreationForm(forms.ModelForm):
82 80
             'invalid': _("This value may contain only letters, numbers and "
83 81
                          "@/./+/-/_ characters.")})
84 82
     password1 = forms.CharField(label=_("Password"),
85  
-        widget=forms.PasswordInput, max_length=MAXIMUM_PASSWORD_LENGTH)
  83
+        widget=forms.PasswordInput)
86 84
     password2 = forms.CharField(label=_("Password confirmation"),
87 85
         widget=forms.PasswordInput,
88  
-        max_length=MAXIMUM_PASSWORD_LENGTH,
89 86
         help_text=_("Enter the same password as above, for verification."))
90 87
 
91 88
     class Meta:
@@ -159,11 +156,7 @@ class AuthenticationForm(forms.Form):
159 156
     username/password logins.
160 157
     """
161 158
     username = forms.CharField(max_length=254)
162  
-    password = forms.CharField(
163  
-        label=_("Password"),
164  
-        widget=forms.PasswordInput,
165  
-        max_length=MAXIMUM_PASSWORD_LENGTH,
166  
-    )
  159
+    password = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
167 160
 
168 161
     error_messages = {
169 162
         'invalid_login': _("Please enter a correct %(username)s and password. "
@@ -286,16 +279,10 @@ class SetPasswordForm(forms.Form):
286 279
     error_messages = {
287 280
         'password_mismatch': _("The two password fields didn't match."),
288 281
     }
289  
-    new_password1 = forms.CharField(
290  
-        label=_("New password"),
291  
-        widget=forms.PasswordInput,
292  
-        max_length=MAXIMUM_PASSWORD_LENGTH,
293  
-    )
294  
-    new_password2 = forms.CharField(
295  
-        label=_("New password confirmation"),
296  
-        widget=forms.PasswordInput,
297  
-        max_length=MAXIMUM_PASSWORD_LENGTH,
298  
-    )
  282
+    new_password1 = forms.CharField(label=_("New password"),
  283
+                                    widget=forms.PasswordInput)
  284
+    new_password2 = forms.CharField(label=_("New password confirmation"),
  285
+                                    widget=forms.PasswordInput)
299 286
 
300 287
     def __init__(self, user, *args, **kwargs):
301 288
         self.user = user
@@ -328,11 +315,8 @@ class PasswordChangeForm(SetPasswordForm):
328 315
         'password_incorrect': _("Your old password was entered incorrectly. "
329 316
                                 "Please enter it again."),
330 317
     })
331  
-    old_password = forms.CharField(
332  
-        label=_("Old password"),
333  
-        widget=forms.PasswordInput,
334  
-        max_length=MAXIMUM_PASSWORD_LENGTH,
335  
-    )
  318
+    old_password = forms.CharField(label=_("Old password"),
  319
+                                   widget=forms.PasswordInput)
336 320
 
337 321
     def clean_old_password(self):
338 322
         """
@@ -359,16 +343,10 @@ class AdminPasswordChangeForm(forms.Form):
359 343
     error_messages = {
360 344
         'password_mismatch': _("The two password fields didn't match."),
361 345
     }
362  
-    password1 = forms.CharField(
363  
-        label=_("Password"),
364  
-        widget=forms.PasswordInput,
365  
-        max_length=MAXIMUM_PASSWORD_LENGTH,
366  
-    )
367  
-    password2 = forms.CharField(
368  
-        label=_("Password (again)"),
369  
-        widget=forms.PasswordInput,
370  
-        max_length=MAXIMUM_PASSWORD_LENGTH,
371  
-    )
  346
+    password1 = forms.CharField(label=_("Password"),
  347
+                                widget=forms.PasswordInput)
  348
+    password2 = forms.CharField(label=_("Password (again)"),
  349
+                                widget=forms.PasswordInput)
372 350
 
373 351
     def __init__(self, user, *args, **kwargs):
374 352
         self.user = user
29  django/contrib/auth/hashers.py
@@ -3,7 +3,6 @@
3 3
 import base64
4 4
 import binascii
5 5
 from collections import OrderedDict
6  
-import functools
7 6
 import hashlib
8 7
 import importlib
9 8
 
@@ -20,7 +19,6 @@
20 19
 
21 20
 UNUSABLE_PASSWORD_PREFIX = '!'  # This will never be a valid encoded hash
22 21
 UNUSABLE_PASSWORD_SUFFIX_LENGTH = 40  # number of random chars to add after UNUSABLE_PASSWORD_PREFIX
23  
-MAXIMUM_PASSWORD_LENGTH = 4096  # The maximum length a password can be to prevent DoS
24 22
 HASHERS = None  # lazily loaded from PASSWORD_HASHERS
25 23
 PREFERRED_HASHER = None  # defaults to first item in PASSWORD_HASHERS
26 24
 
@@ -33,18 +31,6 @@ def reset_hashers(**kwargs):
33 31
         PREFERRED_HASHER = None
34 32
 
35 33
 
36  
-def password_max_length(max_length):
37  
-    def inner(fn):
38  
-        @functools.wraps(fn)
39  
-        def wrapper(self, password, *args, **kwargs):
40  
-            if len(password) > max_length:
41  
-                raise ValueError("Invalid password; Must be less than or equal"
42  
-                                 " to %d bytes" % max_length)
43  
-            return fn(self, password, *args, **kwargs)
44  
-        return wrapper
45  
-    return inner
46  
-
47  
-
48 34
 def is_password_usable(encoded):
49 35
     if encoded is None or encoded.startswith(UNUSABLE_PASSWORD_PREFIX):
50 36
         return False
@@ -239,7 +225,6 @@ class PBKDF2PasswordHasher(BasePasswordHasher):
239 225
     iterations = 12000
240 226
     digest = hashlib.sha256
241 227
 
242  
-    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
243 228
     def encode(self, password, salt, iterations=None):
244 229
         assert password is not None
245 230
         assert salt and '$' not in salt
@@ -249,7 +234,6 @@ def encode(self, password, salt, iterations=None):
249 234
         hash = base64.b64encode(hash).decode('ascii').strip()
250 235
         return "%s$%d$%s$%s" % (self.algorithm, iterations, salt, hash)
251 236
 
252  
-    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
253 237
     def verify(self, password, encoded):
254 238
         algorithm, iterations, salt, hash = encoded.split('$', 3)
255 239
         assert algorithm == self.algorithm
@@ -296,7 +280,6 @@ def salt(self):
296 280
         bcrypt = self._load_library()
297 281
         return bcrypt.gensalt(self.rounds)
298 282
 
299  
-    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
300 283
     def encode(self, password, salt):
301 284
         bcrypt = self._load_library()
302 285
         # Need to reevaluate the force_bytes call once bcrypt is supported on
@@ -314,7 +297,6 @@ def encode(self, password, salt):
314 297
         data = bcrypt.hashpw(password, salt)
315 298
         return "%s$%s" % (self.algorithm, force_text(data))
316 299
 
317  
-    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
318 300
     def verify(self, password, encoded):
319 301
         algorithm, data = encoded.split('$', 1)
320 302
         assert algorithm == self.algorithm
@@ -371,14 +353,12 @@ class SHA1PasswordHasher(BasePasswordHasher):
371 353
     """
372 354
     algorithm = "sha1"
373 355
 
374  
-    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
375 356
     def encode(self, password, salt):
376 357
         assert password is not None
377 358
         assert salt and '$' not in salt
378 359
         hash = hashlib.sha1(force_bytes(salt + password)).hexdigest()
379 360
         return "%s$%s$%s" % (self.algorithm, salt, hash)
380 361
 
381  
-    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
382 362
     def verify(self, password, encoded):
383 363
         algorithm, salt, hash = encoded.split('$', 2)
384 364
         assert algorithm == self.algorithm
@@ -401,14 +381,12 @@ class MD5PasswordHasher(BasePasswordHasher):
401 381
     """
402 382
     algorithm = "md5"
403 383
 
404  
-    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
405 384
     def encode(self, password, salt):
406 385
         assert password is not None
407 386
         assert salt and '$' not in salt
408 387
         hash = hashlib.md5(force_bytes(salt + password)).hexdigest()
409 388
         return "%s$%s$%s" % (self.algorithm, salt, hash)
410 389
 
411  
-    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
412 390
     def verify(self, password, encoded):
413 391
         algorithm, salt, hash = encoded.split('$', 2)
414 392
         assert algorithm == self.algorithm
@@ -439,13 +417,11 @@ class UnsaltedSHA1PasswordHasher(BasePasswordHasher):
439 417
     def salt(self):
440 418
         return ''
441 419
 
442  
-    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
443 420
     def encode(self, password, salt):
444 421
         assert salt == ''
445 422
         hash = hashlib.sha1(force_bytes(password)).hexdigest()
446 423
         return 'sha1$$%s' % hash
447 424
 
448  
-    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
449 425
     def verify(self, password, encoded):
450 426
         encoded_2 = self.encode(password, '')
451 427
         return constant_time_compare(encoded, encoded_2)
@@ -475,12 +451,10 @@ class UnsaltedMD5PasswordHasher(BasePasswordHasher):
475 451
     def salt(self):
476 452
         return ''
477 453
 
478  
-    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
479 454
     def encode(self, password, salt):
480 455
         assert salt == ''
481 456
         return hashlib.md5(force_bytes(password)).hexdigest()
482 457
 
483  
-    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
484 458
     def verify(self, password, encoded):
485 459
         if len(encoded) == 37 and encoded.startswith('md5$$'):
486 460
             encoded = encoded[5:]
@@ -506,7 +480,6 @@ class CryptPasswordHasher(BasePasswordHasher):
506 480
     def salt(self):
507 481
         return get_random_string(2)
508 482
 
509  
-    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
510 483
     def encode(self, password, salt):
511 484
         crypt = self._load_library()
512 485
         assert len(salt) == 2
@@ -514,7 +487,6 @@ def encode(self, password, salt):
514 487
         # we don't need to store the salt, but Django used to do this
515 488
         return "%s$%s$%s" % (self.algorithm, '', data)
516 489
 
517  
-    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
518 490
     def verify(self, password, encoded):
519 491
         crypt = self._load_library()
520 492
         algorithm, salt, data = encoded.split('$', 2)
@@ -529,3 +501,4 @@ def safe_summary(self, encoded):
529 501
             (_('salt'), salt),
530 502
             (_('hash'), mask_hash(data, show=3)),
531 503
         ])
  504
+
85  django/contrib/auth/tests/test_hashers.py
@@ -5,12 +5,9 @@
5 5
 from unittest import skipUnless
6 6
 
7 7
 from django.conf.global_settings import PASSWORD_HASHERS as default_hashers
8  
-from django.contrib.auth.hashers import (
9  
-    is_password_usable, BasePasswordHasher, check_password, make_password,
10  
-    PBKDF2PasswordHasher, load_hashers, PBKDF2SHA1PasswordHasher, get_hasher,
11  
-    identify_hasher, UNUSABLE_PASSWORD_PREFIX, UNUSABLE_PASSWORD_SUFFIX_LENGTH,
12  
-    MAXIMUM_PASSWORD_LENGTH, password_max_length
13  
-)
  8
+from django.contrib.auth.hashers import (is_password_usable, BasePasswordHasher,
  9
+    check_password, make_password, PBKDF2PasswordHasher, load_hashers, PBKDF2SHA1PasswordHasher,
  10
+    get_hasher, identify_hasher, UNUSABLE_PASSWORD_PREFIX, UNUSABLE_PASSWORD_SUFFIX_LENGTH)
14 11
 from django.utils import six
15 12
 
16 13
 
@@ -42,12 +39,6 @@ def test_simple(self):
42 39
         self.assertTrue(is_password_usable(blank_encoded))
43 40
         self.assertTrue(check_password('', blank_encoded))
44 41
         self.assertFalse(check_password(' ', blank_encoded))
45  
-        # Long password
46  
-        self.assertRaises(
47  
-            ValueError,
48  
-            make_password,
49  
-            b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
50  
-        )
51 42
 
52 43
     def test_pkbdf2(self):
53 44
         encoded = make_password('lètmein', 'seasalt', 'pbkdf2_sha256')
@@ -63,14 +54,6 @@ def test_pkbdf2(self):
63 54
         self.assertTrue(is_password_usable(blank_encoded))
64 55
         self.assertTrue(check_password('', blank_encoded))
65 56
         self.assertFalse(check_password(' ', blank_encoded))
66  
-        # Long password
67  
-        self.assertRaises(
68  
-            ValueError,
69  
-            make_password,
70  
-            b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
71  
-            "seasalt",
72  
-            "pbkdf2_sha256",
73  
-        )
74 57
 
75 58
     def test_sha1(self):
76 59
         encoded = make_password('lètmein', 'seasalt', 'sha1')
@@ -86,14 +69,6 @@ def test_sha1(self):
86 69
         self.assertTrue(is_password_usable(blank_encoded))
87 70
         self.assertTrue(check_password('', blank_encoded))
88 71
         self.assertFalse(check_password(' ', blank_encoded))
89  
-        # Long password
90  
-        self.assertRaises(
91  
-            ValueError,
92  
-            make_password,
93  
-            b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
94  
-            "seasalt",
95  
-            "sha1",
96  
-        )
97 72
 
98 73
     def test_md5(self):
99 74
         encoded = make_password('lètmein', 'seasalt', 'md5')
@@ -109,14 +84,6 @@ def test_md5(self):
109 84
         self.assertTrue(is_password_usable(blank_encoded))
110 85
         self.assertTrue(check_password('', blank_encoded))
111 86
         self.assertFalse(check_password(' ', blank_encoded))
112  
-        # Long password
113  
-        self.assertRaises(
114  
-            ValueError,
115  
-            make_password,
116  
-            b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
117  
-            "seasalt",
118  
-            "md5",
119  
-        )
120 87
 
121 88
     def test_unsalted_md5(self):
122 89
         encoded = make_password('lètmein', '', 'unsalted_md5')
@@ -135,14 +102,6 @@ def test_unsalted_md5(self):
135 102
         self.assertTrue(is_password_usable(blank_encoded))
136 103
         self.assertTrue(check_password('', blank_encoded))
137 104
         self.assertFalse(check_password(' ', blank_encoded))
138  
-        # Long password
139  
-        self.assertRaises(
140  
-            ValueError,
141  
-            make_password,
142  
-            b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
143  
-            "",
144  
-            "unsalted_md5",
145  
-        )
146 105
 
147 106
     def test_unsalted_sha1(self):
148 107
         encoded = make_password('lètmein', '', 'unsalted_sha1')
@@ -160,14 +119,6 @@ def test_unsalted_sha1(self):
160 119
         self.assertTrue(is_password_usable(blank_encoded))
161 120
         self.assertTrue(check_password('', blank_encoded))
162 121
         self.assertFalse(check_password(' ', blank_encoded))
163  
-        # Long password
164  
-        self.assertRaises(
165  
-            ValueError,
166  
-            make_password,
167  
-            b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
168  
-            "",
169  
-            "unslated_sha1",
170  
-        )
171 122
 
172 123
     @skipUnless(crypt, "no crypt module to generate password.")
173 124
     def test_crypt(self):
@@ -183,14 +134,6 @@ def test_crypt(self):
183 134
         self.assertTrue(is_password_usable(blank_encoded))
184 135
         self.assertTrue(check_password('', blank_encoded))
185 136
         self.assertFalse(check_password(' ', blank_encoded))
186  
-        # Long password
187  
-        self.assertRaises(
188  
-            ValueError,
189  
-            make_password,
190  
-            b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
191  
-            "seasalt",
192  
-            "crypt",
193  
-        )
194 137
 
195 138
     @skipUnless(bcrypt, "bcrypt not installed")
196 139
     def test_bcrypt_sha256(self):
@@ -213,13 +156,6 @@ def test_bcrypt_sha256(self):
213 156
         self.assertTrue(is_password_usable(blank_encoded))
214 157
         self.assertTrue(check_password('', blank_encoded))
215 158
         self.assertFalse(check_password(' ', blank_encoded))
216  
-        # Long password
217  
-        self.assertRaises(
218  
-            ValueError,
219  
-            make_password,
220  
-            b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
221  
-            hasher="bcrypt_sha256",
222  
-        )
223 159
 
224 160
     @skipUnless(bcrypt, "bcrypt not installed")
225 161
     def test_bcrypt(self):
@@ -235,13 +171,6 @@ def test_bcrypt(self):
235 171
         self.assertTrue(is_password_usable(blank_encoded))
236 172
         self.assertTrue(check_password('', blank_encoded))
237 173
         self.assertFalse(check_password(' ', blank_encoded))
238  
-        # Long password
239  
-        self.assertRaises(
240  
-            ValueError,
241  
-            make_password,
242  
-            b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
243  
-            hasher="bcrypt",
244  
-        )
245 174
 
246 175
     def test_unusable(self):
247 176
         encoded = make_password(None)
@@ -274,14 +203,6 @@ def test_bad_encoded(self):
274 203
         self.assertFalse(is_password_usable('lètmein_badencoded'))
275 204
         self.assertFalse(is_password_usable(''))
276 205
 
277  
-    def test_max_password_length_decorator(self):
278  
-        @password_max_length(10)
279  
-        def encode(s, password, salt):
280  
-            return True
281  
-
282  
-        self.assertTrue(encode(None, b"1234", b"1234"))
283  
-        self.assertRaises(ValueError, encode, None, b"1234567890A", b"1234")
284  
-
285 206
     def test_low_level_pkbdf2(self):
286 207
         hasher = PBKDF2PasswordHasher()
287 208
         encoded = hasher.encode('lètmein', 'seasalt2')

0 notes on commit 5d74853

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