1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2011 - 2024 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a checker for password strength. |
|
8 """ |
|
9 |
|
10 import enum |
|
11 import re |
|
12 |
|
13 |
|
14 class PasswordStrength(enum.IntEnum): |
|
15 """ |
|
16 Class defining the password strength. |
|
17 """ |
|
18 |
|
19 VeryWeak = 0 |
|
20 Weak = 1 |
|
21 Good = 2 |
|
22 Strong = 3 |
|
23 VeryStrong = 4 |
|
24 |
|
25 |
|
26 class PasswordCheckStatus(enum.Enum): |
|
27 """ |
|
28 Class defining the status of a password check. |
|
29 """ |
|
30 |
|
31 Failed = 0 |
|
32 Passed = 1 |
|
33 Exceeded = 2 |
|
34 |
|
35 |
|
36 class PasswordChecker: |
|
37 """ |
|
38 Class implementing a checker for password strength. |
|
39 """ |
|
40 |
|
41 def __init__(self): |
|
42 """ |
|
43 Constructor |
|
44 """ |
|
45 self.score = {"count": 0, "adjusted": 0, "beforeRedundancy": 0} |
|
46 |
|
47 # complexity index |
|
48 self.complexity = { |
|
49 "limits": [20, 50, 60, 80, 100], |
|
50 "value": PasswordStrength.VeryWeak, |
|
51 } |
|
52 |
|
53 # check categories follow |
|
54 |
|
55 # length of the password |
|
56 self.passwordLength = { |
|
57 "count": 0, |
|
58 "minimum": 6, |
|
59 "status": PasswordCheckStatus.Failed, |
|
60 "rating": 0, |
|
61 "factor": 0.5, # per character bonus |
|
62 "bonus": 10, # minimum reached? Get a bonus. |
|
63 "penalty": -20, # if we stay under minimum, we get punished |
|
64 } |
|
65 |
|
66 # recommended password length |
|
67 self.recommendedPasswordLength = { |
|
68 "count": 0, |
|
69 "minimum": 8, |
|
70 "status": PasswordCheckStatus.Failed, |
|
71 "rating": 0, |
|
72 "factor": 1.2, |
|
73 "bonus": 10, |
|
74 "penalty": -10, |
|
75 } |
|
76 |
|
77 # Basic requirements are: |
|
78 # 1) Password Length |
|
79 # 2) Uppercase letter use |
|
80 # 3) Lowercase letter use |
|
81 # 4) Numeric character use |
|
82 # 5) Symbol use |
|
83 self.basicRequirements = { |
|
84 "count": 0, |
|
85 "minimum": 3, # have to be matched to get the bonus |
|
86 "status": PasswordCheckStatus.Failed, |
|
87 "rating": 0, |
|
88 "factor": 1.0, |
|
89 "bonus": 10, |
|
90 "penalty": -10, |
|
91 } |
|
92 |
|
93 # how much redundancy is permitted, if the password is |
|
94 # long enough. we will skip the redudancy penalty if this |
|
95 # number is not exceeded (meaning redundancy < this number) |
|
96 self.redundancy = { |
|
97 "value": 1, # 1 means, not double characters, |
|
98 # default to start |
|
99 "permitted": 2.0, # 2 means, in average every character |
|
100 # can occur twice |
|
101 "status": PasswordCheckStatus.Failed, |
|
102 "rating": 0, |
|
103 } |
|
104 |
|
105 # number of uppercase letters, such as A-Z |
|
106 self.uppercaseLetters = { |
|
107 "count": 0, |
|
108 "minimum": 1, |
|
109 "status": PasswordCheckStatus.Failed, |
|
110 "rating": 0, |
|
111 "factor": 0.0, |
|
112 "bonus": 10, |
|
113 "penalty": -10, |
|
114 } |
|
115 |
|
116 # number of lowercase letters, such as a-z |
|
117 self.lowercaseLetters = { |
|
118 "count": 0, |
|
119 "minimum": 1, |
|
120 "status": PasswordCheckStatus.Failed, |
|
121 "rating": 0, |
|
122 "factor": 0.0, |
|
123 "bonus": 10, |
|
124 "penalty": -10, |
|
125 } |
|
126 |
|
127 # number of numeric characters |
|
128 self.numerics = { |
|
129 "count": 0, |
|
130 "minimum": 1, |
|
131 "status": PasswordCheckStatus.Failed, |
|
132 "rating": 0, |
|
133 "factor": 0.0, |
|
134 "bonus": 10, |
|
135 "penalty": -10, |
|
136 } |
|
137 |
|
138 # number of symbol characters |
|
139 self.symbols = { |
|
140 "count": 0, |
|
141 "minimum": 1, |
|
142 "status": PasswordCheckStatus.Failed, |
|
143 "rating": 0, |
|
144 "factor": 0.0, |
|
145 "bonus": 10, |
|
146 "penalty": -10, |
|
147 } |
|
148 |
|
149 # number of dedicated symbols in the middle |
|
150 self.middleSymbols = { |
|
151 "count": 0, |
|
152 "minimum": 1, |
|
153 "status": PasswordCheckStatus.Failed, |
|
154 "rating": 0, |
|
155 "factor": 0.0, |
|
156 "bonus": 10, |
|
157 "penalty": -10, |
|
158 } |
|
159 |
|
160 # number of dedicated numbers in the middle |
|
161 self.middleNumerics = { |
|
162 "count": 0, |
|
163 "minimum": 1, |
|
164 "status": PasswordCheckStatus.Failed, |
|
165 "rating": 0, |
|
166 "factor": 0.0, |
|
167 "bonus": 10, |
|
168 "penalty": -10, |
|
169 } |
|
170 |
|
171 # how many sequential characters should be checked |
|
172 # such as "abc" or "MNO" to be not part of the password |
|
173 self.sequentialLetters = { |
|
174 "data": "abcdefghijklmnopqrstuvwxyz", |
|
175 "length": 3, |
|
176 "count": 0, |
|
177 "status": PasswordCheckStatus.Failed, |
|
178 "rating": 0, |
|
179 "factor": -1.0, |
|
180 "bonus": 0, |
|
181 "penalty": -10, |
|
182 } |
|
183 |
|
184 # how many sequential characters should be checked |
|
185 # such as "123" to be not part of the password |
|
186 self.sequentialNumerics = { |
|
187 "data": "0123456789", |
|
188 "length": 3, |
|
189 "count": 0, |
|
190 "status": PasswordCheckStatus.Failed, |
|
191 "rating": 0, |
|
192 "factor": -1.0, |
|
193 "bonus": 0, |
|
194 "penalty": -10, |
|
195 } |
|
196 |
|
197 # keyboard patterns to check, typical sequences from your |
|
198 # keyboard |
|
199 self.keyboardPatterns = { |
|
200 # German and English keyboard text |
|
201 "data": [ |
|
202 "qwertzuiop", |
|
203 "asdfghjkl", |
|
204 "yxcvbnm", |
|
205 '!"§$%&/()=', # de |
|
206 "1234567890", # de numbers |
|
207 "qaywsxedcrfvtgbzhnujmik,ol.pö-üä+#", # de up-down |
|
208 "qwertyuiop", |
|
209 "asdfghjkl", |
|
210 "zyxcvbnm", |
|
211 "!@#$%^&*()_", # en |
|
212 "1234567890", # en numbers |
|
213 "qazwsxedcrfvtgbyhnujmik,ol.p;/[']\\", # en up-down |
|
214 ], |
|
215 "length": 4, # how long is the pattern to check and blame for? |
|
216 "count": 0, # how many of these pattern can be found |
|
217 "status": PasswordCheckStatus.Failed, |
|
218 "rating": 0, |
|
219 "factor": -1.0, # each occurrence is punished with that factor |
|
220 "bonus": 0, |
|
221 "penalty": -10, |
|
222 } |
|
223 |
|
224 # check for repeated sequences, like in catcat |
|
225 self.repeatedSequences = { |
|
226 "length": 3, |
|
227 "count": 0, |
|
228 "status": PasswordCheckStatus.Failed, |
|
229 "rating": 0, |
|
230 "factor": 0.0, |
|
231 "bonus": 0, |
|
232 "penalty": -10, |
|
233 } |
|
234 |
|
235 # check for repeated mirrored sequences, like in tactac |
|
236 self.mirroredSequences = { |
|
237 "length": 3, |
|
238 "count": 0, |
|
239 "status": PasswordCheckStatus.Failed, |
|
240 "rating": 0, |
|
241 "factor": 0.0, |
|
242 "bonus": 0, |
|
243 "penalty": -10, |
|
244 } |
|
245 |
|
246 self.uppercaseRe = re.compile("[A-Z]") |
|
247 self.lowercaseRe = re.compile("[a-z]") |
|
248 self.numberRe = re.compile("[0-9]") |
|
249 self.symbolRe = re.compile("[^a-zA-Z0-9]") |
|
250 |
|
251 def __strReverse(self, string): |
|
252 """ |
|
253 Private method to reverse a string. |
|
254 |
|
255 @param string string to be reversed |
|
256 @type str |
|
257 @return reversed string |
|
258 @rtype str |
|
259 """ |
|
260 return "".join(reversed(string)) |
|
261 |
|
262 def __determineStatus(self, value): |
|
263 """ |
|
264 Private method to determine the status. |
|
265 |
|
266 @param value value to check |
|
267 @type int |
|
268 @return status |
|
269 @rtype PasswordCheckStatus |
|
270 """ |
|
271 if value == 0: |
|
272 return PasswordCheckStatus.Passed |
|
273 elif value > 0: |
|
274 return PasswordCheckStatus.Exceeded |
|
275 else: |
|
276 return PasswordCheckStatus.Failed |
|
277 |
|
278 def __determineBinaryStatus(self, value): |
|
279 """ |
|
280 Private method to determine a binary status. |
|
281 |
|
282 @param value value to check |
|
283 @type int |
|
284 @return status |
|
285 @rtype PasswordCheckStatus |
|
286 """ |
|
287 if value == 0: |
|
288 return PasswordCheckStatus.Passed |
|
289 else: |
|
290 return PasswordCheckStatus.Failed |
|
291 |
|
292 def checkPassword(self, password): |
|
293 """ |
|
294 Public method to check a given password. |
|
295 |
|
296 @param password password to be checked |
|
297 @type str |
|
298 @return indication for the password strength |
|
299 @rtype PasswordStrength |
|
300 """ |
|
301 # how long is the password? |
|
302 self.passwordLength["count"] = len(password) |
|
303 self.recommendedPasswordLength["count"] = len(password) |
|
304 |
|
305 # Loop through password to check for Symbol, Numeric, Lowercase |
|
306 # and Uppercase pattern matches |
|
307 for index in range(len(password)): |
|
308 if self.uppercaseRe.match(password[index]): |
|
309 self.uppercaseLetters["count"] += 1 |
|
310 elif self.lowercaseRe.match(password[index]): |
|
311 self.lowercaseLetters["count"] += 1 |
|
312 elif self.numberRe.match(password[index]): |
|
313 if index > 0 and index < len(password) - 1: |
|
314 self.middleNumerics["count"] += 1 |
|
315 self.numerics["count"] += 1 |
|
316 elif self.symbolRe.match(password[index]): |
|
317 if index > 0 and index < len(password) - 1: |
|
318 self.middleSymbols["count"] += 1 |
|
319 self.symbols["count"] += 1 |
|
320 |
|
321 # check the variance of symbols or better the redundancy |
|
322 # makes only sense for at least two characters |
|
323 if len(password) > 1: |
|
324 uniqueCharacters = [] |
|
325 for index1 in range(len(password)): |
|
326 found = False |
|
327 for index2 in range(index1 + 1, len(password)): |
|
328 if password[index1] == password[index2]: |
|
329 found = True |
|
330 break |
|
331 if not found: |
|
332 uniqueCharacters.append(password[index1]) |
|
333 |
|
334 # calculate a redundancy number |
|
335 self.redundancy["value"] = len(password) / len(uniqueCharacters) |
|
336 |
|
337 # Check for sequential alpha string patterns (forward and reverse) |
|
338 # but only, if the string has already a length to check for, does |
|
339 # not make sense to check the password "ab" for the sequential data |
|
340 # "abc" |
|
341 lowercasedPassword = password.lower() |
|
342 |
|
343 if self.passwordLength["count"] >= self.sequentialLetters["length"]: |
|
344 for index in range( |
|
345 len(self.sequentialLetters["data"]) |
|
346 - self.sequentialLetters["length"] |
|
347 + 1 |
|
348 ): |
|
349 fwd = self.sequentialLetters["data"][ |
|
350 index : index + self.sequentialLetters["length"] |
|
351 ] |
|
352 rev = self.__strReverse(fwd) |
|
353 if lowercasedPassword.find(fwd) != -1: |
|
354 self.sequentialLetters["count"] += 1 |
|
355 if lowercasedPassword.find(rev) != -1: |
|
356 self.sequentialLetters["count"] += 1 |
|
357 |
|
358 # Check for sequential numeric string patterns (forward and reverse) |
|
359 if self.passwordLength["count"] >= self.sequentialNumerics["length"]: |
|
360 for index in range( |
|
361 len(self.sequentialNumerics["data"]) |
|
362 - self.sequentialNumerics["length"] |
|
363 + 1 |
|
364 ): |
|
365 fwd = self.sequentialNumerics["data"][ |
|
366 index : index + self.sequentialNumerics["length"] |
|
367 ] |
|
368 rev = self.__strReverse(fwd) |
|
369 if lowercasedPassword.find(fwd) != -1: |
|
370 self.sequentialNumerics["count"] += 1 |
|
371 if lowercasedPassword.find(rev) != -1: |
|
372 self.sequentialNumerics["count"] += 1 |
|
373 |
|
374 # Check common keyboard patterns |
|
375 patternsMatched = [] |
|
376 if self.passwordLength["count"] >= self.keyboardPatterns["length"]: |
|
377 for pattern in self.keyboardPatterns["data"]: |
|
378 for index in range(len(pattern) - self.keyboardPatterns["length"] + 1): |
|
379 fwd = pattern[index : index + self.keyboardPatterns["length"]] |
|
380 rev = self.__strReverse(fwd) |
|
381 if ( |
|
382 lowercasedPassword.find(fwd) != -1 |
|
383 and fwd not in patternsMatched |
|
384 ): |
|
385 self.keyboardPatterns["count"] += 1 |
|
386 patternsMatched.append(fwd) |
|
387 if ( |
|
388 lowercasedPassword.find(rev) != -1 |
|
389 and fwd not in patternsMatched |
|
390 ): |
|
391 self.keyboardPatterns["count"] += 1 |
|
392 patternsMatched.append(rev) |
|
393 |
|
394 # Try to find repeated sequences of characters. |
|
395 if self.passwordLength["count"] >= self.repeatedSequences["length"]: |
|
396 for index in range( |
|
397 len(lowercasedPassword) - self.repeatedSequences["length"] + 1 |
|
398 ): |
|
399 fwd = lowercasedPassword[ |
|
400 index : index + self.repeatedSequences["length"] |
|
401 ] |
|
402 if ( |
|
403 lowercasedPassword.find( |
|
404 fwd, index + self.repeatedSequences["length"] |
|
405 ) |
|
406 != -1 |
|
407 ): |
|
408 self.repeatedSequences["count"] += 1 |
|
409 |
|
410 # Try to find mirrored sequences of characters. |
|
411 if self.passwordLength["count"] >= self.mirroredSequences["length"]: |
|
412 for index in range( |
|
413 len(lowercasedPassword) - self.mirroredSequences["length"] + 1 |
|
414 ): |
|
415 fwd = lowercasedPassword[ |
|
416 index : index + self.mirroredSequences["length"] |
|
417 ] |
|
418 rev = self.__strReverse(fwd) |
|
419 if ( |
|
420 lowercasedPassword.find( |
|
421 fwd, index + self.mirroredSequences["length"] |
|
422 ) |
|
423 != -1 |
|
424 ): |
|
425 self.mirroredSequences["count"] += 1 |
|
426 |
|
427 # Initial score based on length |
|
428 self.score["count"] = ( |
|
429 self.passwordLength["count"] * self.passwordLength["factor"] |
|
430 ) |
|
431 |
|
432 # passwordLength |
|
433 # credit additional length or punish "under" length |
|
434 if self.passwordLength["count"] >= self.passwordLength["minimum"]: |
|
435 # credit additional characters over minimum |
|
436 self.passwordLength["rating"] = ( |
|
437 self.passwordLength["bonus"] |
|
438 + (self.passwordLength["count"] - self.passwordLength["minimum"]) |
|
439 * self.passwordLength["factor"] |
|
440 ) |
|
441 else: |
|
442 self.passwordLength["rating"] = self.passwordLength["penalty"] |
|
443 self.score["count"] += self.passwordLength["rating"] |
|
444 |
|
445 # recommendedPasswordLength |
|
446 # Credit reaching the recommended password length or put a |
|
447 # penalty on it |
|
448 if self.passwordLength["count"] >= self.recommendedPasswordLength["minimum"]: |
|
449 self.recommendedPasswordLength["rating"] = ( |
|
450 self.recommendedPasswordLength["bonus"] |
|
451 + ( |
|
452 self.passwordLength["count"] |
|
453 - self.recommendedPasswordLength["minimum"] |
|
454 ) |
|
455 * self.recommendedPasswordLength["factor"] |
|
456 ) |
|
457 else: |
|
458 self.recommendedPasswordLength["rating"] = self.recommendedPasswordLength[ |
|
459 "penalty" |
|
460 ] |
|
461 self.score["count"] += self.recommendedPasswordLength["rating"] |
|
462 |
|
463 # lowercaseLetters |
|
464 # Honor or punish the lowercase letter use |
|
465 if self.lowercaseLetters["count"] > 0: |
|
466 self.lowercaseLetters["rating"] = ( |
|
467 self.lowercaseLetters["bonus"] |
|
468 + self.lowercaseLetters["count"] * self.lowercaseLetters["factor"] |
|
469 ) |
|
470 else: |
|
471 self.lowercaseLetters["rating"] = self.lowercaseLetters["penalty"] |
|
472 self.score["count"] += self.lowercaseLetters["rating"] |
|
473 |
|
474 # uppercaseLetters |
|
475 # Honor or punish the lowercase letter use |
|
476 if self.uppercaseLetters["count"] > 0: |
|
477 self.uppercaseLetters["rating"] = ( |
|
478 self.uppercaseLetters["bonus"] |
|
479 + self.uppercaseLetters["count"] * self.uppercaseLetters["factor"] |
|
480 ) |
|
481 else: |
|
482 self.uppercaseLetters["rating"] = self.uppercaseLetters["penalty"] |
|
483 self.score["count"] += self.uppercaseLetters["rating"] |
|
484 |
|
485 # numerics |
|
486 # Honor or punish the numerics use |
|
487 if self.numerics["count"] > 0: |
|
488 self.numerics["rating"] = ( |
|
489 self.numerics["bonus"] |
|
490 + self.numerics["count"] * self.numerics["factor"] |
|
491 ) |
|
492 else: |
|
493 self.numerics["rating"] = self.numerics["penalty"] |
|
494 self.score["count"] += self.numerics["rating"] |
|
495 |
|
496 # symbols |
|
497 # Honor or punish the symbols use |
|
498 if self.symbols["count"] > 0: |
|
499 self.symbols["rating"] = ( |
|
500 self.symbols["bonus"] + self.symbols["count"] * self.symbols["factor"] |
|
501 ) |
|
502 else: |
|
503 self.symbols["rating"] = self.symbols["penalty"] |
|
504 self.score["count"] += self.symbols["rating"] |
|
505 |
|
506 # middleSymbols |
|
507 # Honor or punish the middle symbols use |
|
508 if self.middleSymbols["count"] > 0: |
|
509 self.middleSymbols["rating"] = ( |
|
510 self.middleSymbols["bonus"] |
|
511 + self.middleSymbols["count"] * self.middleSymbols["factor"] |
|
512 ) |
|
513 else: |
|
514 self.middleSymbols["rating"] = self.middleSymbols["penalty"] |
|
515 self.score["count"] += self.middleSymbols["rating"] |
|
516 |
|
517 # middleNumerics |
|
518 # Honor or punish the middle numerics use |
|
519 if self.middleNumerics["count"] > 0: |
|
520 self.middleNumerics["rating"] = ( |
|
521 self.middleNumerics["bonus"] |
|
522 + self.middleNumerics["count"] * self.middleNumerics["factor"] |
|
523 ) |
|
524 else: |
|
525 self.middleNumerics["rating"] = self.middleNumerics["penalty"] |
|
526 self.score["count"] += self.middleNumerics["rating"] |
|
527 |
|
528 # sequentialLetters |
|
529 # Honor or punish the sequential letter use |
|
530 if self.sequentialLetters["count"] == 0: |
|
531 self.sequentialLetters["rating"] = ( |
|
532 self.sequentialLetters["bonus"] |
|
533 + self.sequentialLetters["count"] * self.sequentialLetters["factor"] |
|
534 ) |
|
535 else: |
|
536 self.sequentialLetters["rating"] = self.sequentialLetters["penalty"] |
|
537 self.score["count"] += self.sequentialLetters["rating"] |
|
538 |
|
539 # sequentialNumerics |
|
540 # Honor or punish the sequential numerics use |
|
541 if self.sequentialNumerics["count"] == 0: |
|
542 self.sequentialNumerics["rating"] = ( |
|
543 self.sequentialNumerics["bonus"] |
|
544 + self.sequentialNumerics["count"] * self.sequentialNumerics["factor"] |
|
545 ) |
|
546 else: |
|
547 self.sequentialNumerics["rating"] = self.sequentialNumerics["penalty"] |
|
548 self.score["count"] += self.sequentialNumerics["rating"] |
|
549 |
|
550 # keyboardPatterns |
|
551 # Honor or punish the keyboard patterns use |
|
552 if self.keyboardPatterns["count"] == 0: |
|
553 self.keyboardPatterns["rating"] = ( |
|
554 self.keyboardPatterns["bonus"] |
|
555 + self.keyboardPatterns["count"] * self.keyboardPatterns["factor"] |
|
556 ) |
|
557 else: |
|
558 self.keyboardPatterns["rating"] = self.keyboardPatterns["penalty"] |
|
559 self.score["count"] += self.keyboardPatterns["rating"] |
|
560 |
|
561 # Count our basicRequirements and set the status |
|
562 self.basicRequirements["count"] = 0 |
|
563 |
|
564 # password length |
|
565 self.passwordLength["status"] = self.__determineStatus( |
|
566 self.passwordLength["count"] - self.passwordLength["minimum"] |
|
567 ) |
|
568 if self.passwordLength["status"] != PasswordCheckStatus.Failed: |
|
569 # requirement met |
|
570 self.basicRequirements["count"] += 1 |
|
571 |
|
572 # uppercase letters |
|
573 self.uppercaseLetters["status"] = self.__determineStatus( |
|
574 self.uppercaseLetters["count"] - self.uppercaseLetters["minimum"] |
|
575 ) |
|
576 if self.uppercaseLetters["status"] != PasswordCheckStatus.Failed: |
|
577 # requirement met |
|
578 self.basicRequirements["count"] += 1 |
|
579 |
|
580 # lowercase letters |
|
581 self.lowercaseLetters["status"] = self.__determineStatus( |
|
582 self.lowercaseLetters["count"] - self.lowercaseLetters["minimum"] |
|
583 ) |
|
584 if self.lowercaseLetters["status"] != PasswordCheckStatus.Failed: |
|
585 # requirement met |
|
586 self.basicRequirements["count"] += 1 |
|
587 |
|
588 # numerics |
|
589 self.numerics["status"] = self.__determineStatus( |
|
590 self.numerics["count"] - self.numerics["minimum"] |
|
591 ) |
|
592 if self.numerics["status"] != PasswordCheckStatus.Failed: |
|
593 # requirement met |
|
594 self.basicRequirements["count"] += 1 |
|
595 |
|
596 # symbols |
|
597 self.symbols["status"] = self.__determineStatus( |
|
598 self.symbols["count"] - self.symbols["minimum"] |
|
599 ) |
|
600 if self.symbols["status"] != PasswordCheckStatus.Failed: |
|
601 # requirement met |
|
602 self.basicRequirements["count"] += 1 |
|
603 |
|
604 # judge the requirement status |
|
605 self.basicRequirements["status"] = self.__determineStatus( |
|
606 self.basicRequirements["count"] - self.basicRequirements["minimum"] |
|
607 ) |
|
608 if self.basicRequirements["status"] != PasswordCheckStatus.Failed: |
|
609 self.basicRequirements["rating"] = ( |
|
610 self.basicRequirements["bonus"] |
|
611 + self.basicRequirements["factor"] * self.basicRequirements["count"] |
|
612 ) |
|
613 else: |
|
614 self.basicRequirements["rating"] = self.basicRequirements["penalty"] |
|
615 self.score["count"] += self.basicRequirements["rating"] |
|
616 |
|
617 # beyond basic requirements |
|
618 self.recommendedPasswordLength["status"] = self.__determineStatus( |
|
619 self.recommendedPasswordLength["count"] |
|
620 - self.recommendedPasswordLength["minimum"] |
|
621 ) |
|
622 self.middleNumerics["status"] = self.__determineStatus( |
|
623 self.middleNumerics["count"] - self.middleNumerics["minimum"] |
|
624 ) |
|
625 self.middleSymbols["status"] = self.__determineStatus( |
|
626 self.middleSymbols["count"] - self.middleSymbols["minimum"] |
|
627 ) |
|
628 self.sequentialLetters["status"] = self.__determineBinaryStatus( |
|
629 self.sequentialLetters["count"] |
|
630 ) |
|
631 self.sequentialNumerics["status"] = self.__determineBinaryStatus( |
|
632 self.sequentialNumerics["count"] |
|
633 ) |
|
634 self.keyboardPatterns["status"] = self.__determineBinaryStatus( |
|
635 self.keyboardPatterns["count"] |
|
636 ) |
|
637 self.repeatedSequences["status"] = self.__determineBinaryStatus( |
|
638 self.repeatedSequences["count"] |
|
639 ) |
|
640 self.mirroredSequences["status"] = self.__determineBinaryStatus( |
|
641 self.mirroredSequences["count"] |
|
642 ) |
|
643 |
|
644 # we apply them only, if the length is not awesome |
|
645 if self.recommendedPasswordLength["status"] != PasswordCheckStatus.Exceeded: |
|
646 # repeatedSequences |
|
647 # Honor or punish the use of repeated sequences |
|
648 if self.repeatedSequences["count"] == 0: |
|
649 self.repeatedSequences["rating"] = self.repeatedSequences["bonus"] |
|
650 else: |
|
651 self.repeatedSequences["rating"] = ( |
|
652 self.repeatedSequences["penalty"] |
|
653 + self.repeatedSequences["count"] * self.repeatedSequences["factor"] |
|
654 ) |
|
655 |
|
656 # mirroredSequences |
|
657 # Honor or punish the use of mirrored sequences |
|
658 if self.mirroredSequences["count"] == 0: |
|
659 self.mirroredSequences["rating"] = self.mirroredSequences["bonus"] |
|
660 else: |
|
661 self.mirroredSequences["rating"] = ( |
|
662 self.mirroredSequences["penalty"] |
|
663 + self.mirroredSequences["count"] * self.mirroredSequences["factor"] |
|
664 ) |
|
665 |
|
666 # save value before redundancy |
|
667 self.score["beforeRedundancy"] = self.score["count"] |
|
668 |
|
669 # apply the redundancy |
|
670 # is the password length requirement fulfilled? |
|
671 if ( |
|
672 self.recommendedPasswordLength["status"] != PasswordCheckStatus.Exceeded |
|
673 and self.score["count"] > 0 |
|
674 ): |
|
675 # full penalty, because password is not long enough, only for |
|
676 # a positive score |
|
677 self.score["count"] *= 1.0 / self.redundancy["value"] |
|
678 |
|
679 # level it out |
|
680 if self.score["count"] > 100: |
|
681 self.score["adjusted"] = 100 |
|
682 elif self.score["count"] < 0: |
|
683 self.score["adjusted"] = 0 |
|
684 else: |
|
685 self.score["adjusted"] = self.score["count"] |
|
686 |
|
687 # judge it |
|
688 for index in range(len(self.complexity["limits"])): |
|
689 if self.score["adjusted"] <= self.complexity["limits"][index]: |
|
690 self.complexity["value"] = PasswordStrength(index) |
|
691 break |
|
692 |
|
693 return self.complexity["value"] |
|