1
+ import functools
1
2
import hashlib
2
3
3
4
from django .conf import settings
11
12
12
13
13
14
UNUSABLE_PASSWORD = '!' # This will never be a valid encoded hash
15
+ MAXIMUM_PASSWORD_LENGTH = 4096 # The maximum length a password can be to prevent DoS
14
16
HASHERS = None # lazily loaded from PASSWORD_HASHERS
15
17
PREFERRED_HASHER = None # defaults to first item in PASSWORD_HASHERS
16
18
17
19
20
+ def password_max_length (max_length ):
21
+ def inner (fn ):
22
+ @functools .wraps (fn )
23
+ def wrapper (self , password , * args , ** kwargs ):
24
+ if len (password ) > max_length :
25
+ raise ValueError ("Invalid password; Must be less than or equal"
26
+ " to %d bytes" % max_length )
27
+ return fn (self , password , * args , ** kwargs )
28
+ return wrapper
29
+ return inner
30
+
31
+
18
32
def is_password_usable (encoded ):
19
33
return (encoded is not None and encoded != UNUSABLE_PASSWORD )
20
34
@@ -202,6 +216,7 @@ class PBKDF2PasswordHasher(BasePasswordHasher):
202
216
iterations = 10000
203
217
digest = hashlib .sha256
204
218
219
+ @password_max_length (MAXIMUM_PASSWORD_LENGTH )
205
220
def encode (self , password , salt , iterations = None ):
206
221
assert password
207
222
assert salt and '$' not in salt
@@ -211,6 +226,7 @@ def encode(self, password, salt, iterations=None):
211
226
hash = hash .encode ('base64' ).strip ()
212
227
return "%s$%d$%s$%s" % (self .algorithm , iterations , salt , hash )
213
228
229
+ @password_max_length (MAXIMUM_PASSWORD_LENGTH )
214
230
def verify (self , password , encoded ):
215
231
algorithm , iterations , salt , hash = encoded .split ('$' , 3 )
216
232
assert algorithm == self .algorithm
@@ -256,11 +272,13 @@ def salt(self):
256
272
bcrypt = self ._load_library ()
257
273
return bcrypt .gensalt (self .rounds )
258
274
275
+ @password_max_length (MAXIMUM_PASSWORD_LENGTH )
259
276
def encode (self , password , salt ):
260
277
bcrypt = self ._load_library ()
261
278
data = bcrypt .hashpw (password , salt )
262
279
return "%s$%s" % (self .algorithm , data )
263
280
281
+ @password_max_length (MAXIMUM_PASSWORD_LENGTH )
264
282
def verify (self , password , encoded ):
265
283
algorithm , data = encoded .split ('$' , 1 )
266
284
assert algorithm == self .algorithm
@@ -285,12 +303,14 @@ class SHA1PasswordHasher(BasePasswordHasher):
285
303
"""
286
304
algorithm = "sha1"
287
305
306
+ @password_max_length (MAXIMUM_PASSWORD_LENGTH )
288
307
def encode (self , password , salt ):
289
308
assert password
290
309
assert salt and '$' not in salt
291
310
hash = hashlib .sha1 (salt + password ).hexdigest ()
292
311
return "%s$%s$%s" % (self .algorithm , salt , hash )
293
312
313
+ @password_max_length (MAXIMUM_PASSWORD_LENGTH )
294
314
def verify (self , password , encoded ):
295
315
algorithm , salt , hash = encoded .split ('$' , 2 )
296
316
assert algorithm == self .algorithm
@@ -313,12 +333,14 @@ class MD5PasswordHasher(BasePasswordHasher):
313
333
"""
314
334
algorithm = "md5"
315
335
336
+ @password_max_length (MAXIMUM_PASSWORD_LENGTH )
316
337
def encode (self , password , salt ):
317
338
assert password
318
339
assert salt and '$' not in salt
319
340
hash = hashlib .md5 (salt + password ).hexdigest ()
320
341
return "%s$%s$%s" % (self .algorithm , salt , hash )
321
342
343
+ @password_max_length (MAXIMUM_PASSWORD_LENGTH )
322
344
def verify (self , password , encoded ):
323
345
algorithm , salt , hash = encoded .split ('$' , 2 )
324
346
assert algorithm == self .algorithm
@@ -349,11 +371,13 @@ class UnsaltedSHA1PasswordHasher(BasePasswordHasher):
349
371
def salt (self ):
350
372
return ''
351
373
374
+ @password_max_length (MAXIMUM_PASSWORD_LENGTH )
352
375
def encode (self , password , salt ):
353
376
assert salt == ''
354
377
hash = hashlib .sha1 (password ).hexdigest ()
355
378
return 'sha1$$%s' % hash
356
379
380
+ @password_max_length (MAXIMUM_PASSWORD_LENGTH )
357
381
def verify (self , password , encoded ):
358
382
encoded_2 = self .encode (password , '' )
359
383
return constant_time_compare (encoded , encoded_2 )
@@ -383,10 +407,12 @@ class UnsaltedMD5PasswordHasher(BasePasswordHasher):
383
407
def salt (self ):
384
408
return ''
385
409
410
+ @password_max_length (MAXIMUM_PASSWORD_LENGTH )
386
411
def encode (self , password , salt ):
387
412
assert salt == ''
388
413
return hashlib .md5 (password ).hexdigest ()
389
414
415
+ @password_max_length (MAXIMUM_PASSWORD_LENGTH )
390
416
def verify (self , password , encoded ):
391
417
if len (encoded ) == 37 and encoded .startswith ('md5$$' ):
392
418
encoded = encoded [5 :]
@@ -412,13 +438,15 @@ class CryptPasswordHasher(BasePasswordHasher):
412
438
def salt (self ):
413
439
return get_random_string (2 )
414
440
441
+ @password_max_length (MAXIMUM_PASSWORD_LENGTH )
415
442
def encode (self , password , salt ):
416
443
crypt = self ._load_library ()
417
444
assert len (salt ) == 2
418
445
data = crypt .crypt (password , salt )
419
446
# we don't need to store the salt, but Django used to do this
420
447
return "%s$%s$%s" % (self .algorithm , '' , data )
421
448
449
+ @password_max_length (MAXIMUM_PASSWORD_LENGTH )
422
450
def verify (self , password , encoded ):
423
451
crypt = self ._load_library ()
424
452
algorithm , salt , data = encoded .split ('$' , 2 )
@@ -433,4 +461,3 @@ def safe_summary(self, encoded):
433
461
(_ ('salt' ), salt ),
434
462
(_ ('hash' ), mask_hash (data , show = 3 )),
435
463
])
436
-
0 commit comments