5 |
5 |
6 """ |
6 """ |
7 Module implementing a checker for password strength. |
7 Module implementing a checker for password strength. |
8 """ |
8 """ |
9 |
9 |
|
10 import enum |
10 import re |
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 |
11 |
34 |
12 |
35 |
13 class PasswordChecker: |
36 class PasswordChecker: |
14 """ |
37 """ |
15 Class implementing a checker for password strength. |
38 Class implementing a checker for password strength. |
16 """ |
39 """ |
17 |
40 |
18 # TODO: change this to an enum |
|
19 Complexity_VeryWeak = 0 |
|
20 Complexity_Weak = 1 |
|
21 Complexity_Good = 2 |
|
22 Complexity_Strong = 3 |
|
23 Complexity_VeryStrong = 4 |
|
24 |
|
25 # TODO: change this to an enum |
|
26 Status_Failed = 0 |
|
27 Status_Passed = 1 |
|
28 Status_Exceeded = 2 |
|
29 |
|
30 def __init__(self): |
41 def __init__(self): |
31 """ |
42 """ |
32 Constructor |
43 Constructor |
33 """ |
44 """ |
34 self.score = {"count": 0, "adjusted": 0, "beforeRedundancy": 0} |
45 self.score = {"count": 0, "adjusted": 0, "beforeRedundancy": 0} |
35 |
46 |
36 # complexity index |
47 # complexity index |
37 self.complexity = { |
48 self.complexity = { |
38 "limits": [20, 50, 60, 80, 100], |
49 "limits": [20, 50, 60, 80, 100], |
39 "value": self.Complexity_VeryWeak, |
50 "value": PasswordStrength.VeryWeak, |
40 } |
51 } |
41 |
52 |
42 # check categories follow |
53 # check categories follow |
43 |
54 |
44 # length of the password |
55 # length of the password |
45 self.passwordLength = { |
56 self.passwordLength = { |
46 "count": 0, |
57 "count": 0, |
47 "minimum": 6, |
58 "minimum": 6, |
48 "status": self.Status_Failed, |
59 "status": PasswordCheckStatus.Failed, |
49 "rating": 0, |
60 "rating": 0, |
50 "factor": 0.5, # per character bonus |
61 "factor": 0.5, # per character bonus |
51 "bonus": 10, # minimum reached? Get a bonus. |
62 "bonus": 10, # minimum reached? Get a bonus. |
52 "penalty": -20, # if we stay under minimum, we get punished |
63 "penalty": -20, # if we stay under minimum, we get punished |
53 } |
64 } |
54 |
65 |
55 # recommended password length |
66 # recommended password length |
56 self.recommendedPasswordLength = { |
67 self.recommendedPasswordLength = { |
57 "count": 0, |
68 "count": 0, |
58 "minimum": 8, |
69 "minimum": 8, |
59 "status": self.Status_Failed, |
70 "status": PasswordCheckStatus.Failed, |
60 "rating": 0, |
71 "rating": 0, |
61 "factor": 1.2, |
72 "factor": 1.2, |
62 "bonus": 10, |
73 "bonus": 10, |
63 "penalty": -10, |
74 "penalty": -10, |
64 } |
75 } |
85 self.redundancy = { |
96 self.redundancy = { |
86 "value": 1, # 1 means, not double characters, |
97 "value": 1, # 1 means, not double characters, |
87 # default to start |
98 # default to start |
88 "permitted": 2.0, # 2 means, in average every character |
99 "permitted": 2.0, # 2 means, in average every character |
89 # can occur twice |
100 # can occur twice |
90 "status": self.Status_Failed, |
101 "status": PasswordCheckStatus.Failed, |
91 "rating": 0, |
102 "rating": 0, |
92 } |
103 } |
93 |
104 |
94 # number of uppercase letters, such as A-Z |
105 # number of uppercase letters, such as A-Z |
95 self.uppercaseLetters = { |
106 self.uppercaseLetters = { |
96 "count": 0, |
107 "count": 0, |
97 "minimum": 1, |
108 "minimum": 1, |
98 "status": self.Status_Failed, |
109 "status": PasswordCheckStatus.Failed, |
99 "rating": 0, |
110 "rating": 0, |
100 "factor": 0.0, |
111 "factor": 0.0, |
101 "bonus": 10, |
112 "bonus": 10, |
102 "penalty": -10, |
113 "penalty": -10, |
103 } |
114 } |
104 |
115 |
105 # number of lowercase letters, such as a-z |
116 # number of lowercase letters, such as a-z |
106 self.lowercaseLetters = { |
117 self.lowercaseLetters = { |
107 "count": 0, |
118 "count": 0, |
108 "minimum": 1, |
119 "minimum": 1, |
109 "status": self.Status_Failed, |
120 "status": PasswordCheckStatus.Failed, |
110 "rating": 0, |
121 "rating": 0, |
111 "factor": 0.0, |
122 "factor": 0.0, |
112 "bonus": 10, |
123 "bonus": 10, |
113 "penalty": -10, |
124 "penalty": -10, |
114 } |
125 } |
115 |
126 |
116 # number of numeric characters |
127 # number of numeric characters |
117 self.numerics = { |
128 self.numerics = { |
118 "count": 0, |
129 "count": 0, |
119 "minimum": 1, |
130 "minimum": 1, |
120 "status": self.Status_Failed, |
131 "status": PasswordCheckStatus.Failed, |
121 "rating": 0, |
132 "rating": 0, |
122 "factor": 0.0, |
133 "factor": 0.0, |
123 "bonus": 10, |
134 "bonus": 10, |
124 "penalty": -10, |
135 "penalty": -10, |
125 } |
136 } |
126 |
137 |
127 # number of symbol characters |
138 # number of symbol characters |
128 self.symbols = { |
139 self.symbols = { |
129 "count": 0, |
140 "count": 0, |
130 "minimum": 1, |
141 "minimum": 1, |
131 "status": self.Status_Failed, |
142 "status": PasswordCheckStatus.Failed, |
132 "rating": 0, |
143 "rating": 0, |
133 "factor": 0.0, |
144 "factor": 0.0, |
134 "bonus": 10, |
145 "bonus": 10, |
135 "penalty": -10, |
146 "penalty": -10, |
136 } |
147 } |
137 |
148 |
138 # number of dedicated symbols in the middle |
149 # number of dedicated symbols in the middle |
139 self.middleSymbols = { |
150 self.middleSymbols = { |
140 "count": 0, |
151 "count": 0, |
141 "minimum": 1, |
152 "minimum": 1, |
142 "status": self.Status_Failed, |
153 "status": PasswordCheckStatus.Failed, |
143 "rating": 0, |
154 "rating": 0, |
144 "factor": 0.0, |
155 "factor": 0.0, |
145 "bonus": 10, |
156 "bonus": 10, |
146 "penalty": -10, |
157 "penalty": -10, |
147 } |
158 } |
148 |
159 |
149 # number of dedicated numbers in the middle |
160 # number of dedicated numbers in the middle |
150 self.middleNumerics = { |
161 self.middleNumerics = { |
151 "count": 0, |
162 "count": 0, |
152 "minimum": 1, |
163 "minimum": 1, |
153 "status": self.Status_Failed, |
164 "status": PasswordCheckStatus.Failed, |
154 "rating": 0, |
165 "rating": 0, |
155 "factor": 0.0, |
166 "factor": 0.0, |
156 "bonus": 10, |
167 "bonus": 10, |
157 "penalty": -10, |
168 "penalty": -10, |
158 } |
169 } |
201 "1234567890", # en numbers |
212 "1234567890", # en numbers |
202 "qazwsxedcrfvtgbyhnujmik,ol.p;/[']\\", # en up-down |
213 "qazwsxedcrfvtgbyhnujmik,ol.p;/[']\\", # en up-down |
203 ], |
214 ], |
204 "length": 4, # how long is the pattern to check and blame for? |
215 "length": 4, # how long is the pattern to check and blame for? |
205 "count": 0, # how many of these pattern can be found |
216 "count": 0, # how many of these pattern can be found |
206 "status": self.Status_Failed, |
217 "status": PasswordCheckStatus.Failed, |
207 "rating": 0, |
218 "rating": 0, |
208 "factor": -1.0, # each occurrence is punished with that factor |
219 "factor": -1.0, # each occurrence is punished with that factor |
209 "bonus": 0, |
220 "bonus": 0, |
210 "penalty": -10, |
221 "penalty": -10, |
211 } |
222 } |
212 |
223 |
213 # check for repeated sequences, like in catcat |
224 # check for repeated sequences, like in catcat |
214 self.repeatedSequences = { |
225 self.repeatedSequences = { |
215 "length": 3, |
226 "length": 3, |
216 "count": 0, |
227 "count": 0, |
217 "status": self.Status_Failed, |
228 "status": PasswordCheckStatus.Failed, |
218 "rating": 0, |
229 "rating": 0, |
219 "factor": 0.0, |
230 "factor": 0.0, |
220 "bonus": 0, |
231 "bonus": 0, |
221 "penalty": -10, |
232 "penalty": -10, |
222 } |
233 } |
223 |
234 |
224 # check for repeated mirrored sequences, like in tactac |
235 # check for repeated mirrored sequences, like in tactac |
225 self.mirroredSequences = { |
236 self.mirroredSequences = { |
226 "length": 3, |
237 "length": 3, |
227 "count": 0, |
238 "count": 0, |
228 "status": self.Status_Failed, |
239 "status": PasswordCheckStatus.Failed, |
229 "rating": 0, |
240 "rating": 0, |
230 "factor": 0.0, |
241 "factor": 0.0, |
231 "bonus": 0, |
242 "bonus": 0, |
232 "penalty": -10, |
243 "penalty": -10, |
233 } |
244 } |
252 """ |
263 """ |
253 Private method to determine the status. |
264 Private method to determine the status. |
254 |
265 |
255 @param value value to check |
266 @param value value to check |
256 @type int |
267 @type int |
257 @return status (Status_Failed, Status_Passed, Status_Exceeded) |
268 @return status |
258 @rtype int |
269 @rtype PasswordCheckStatus |
259 """ |
270 """ |
260 if value == 0: |
271 if value == 0: |
261 return self.Status_Passed |
272 return PasswordCheckStatus.Passed |
262 elif value > 0: |
273 elif value > 0: |
263 return self.Status_Exceeded |
274 return PasswordCheckStatus.Exceeded |
264 else: |
275 else: |
265 return self.Status_Failed |
276 return PasswordCheckStatus.Failed |
266 |
277 |
267 def __determineBinaryStatus(self, value): |
278 def __determineBinaryStatus(self, value): |
268 """ |
279 """ |
269 Private method to determine a binary status. |
280 Private method to determine a binary status. |
270 |
281 |
271 @param value value to check |
282 @param value value to check |
272 @type int |
283 @type int |
273 @return status (Status_Failed, Status_Passed) |
284 @return status |
274 @rtype int |
285 @rtype PasswordCheckStatus |
275 """ |
286 """ |
276 if value == 0: |
287 if value == 0: |
277 return self.Status_Passed |
288 return PasswordCheckStatus.Passed |
278 else: |
289 else: |
279 return self.Status_Failed |
290 return PasswordCheckStatus.Failed |
280 |
291 |
281 def checkPassword(self, password): |
292 def checkPassword(self, password): |
282 """ |
293 """ |
283 Public method to check a given password. |
294 Public method to check a given password. |
284 |
295 |
285 @param password password to be checked |
296 @param password password to be checked |
286 @type str |
297 @type str |
287 @return indication for the password strength (Complexity_VeryWeak, |
298 @return indication for the password strength |
288 Complexity_Weak, Complexity_Good, Complexity_Strong, |
299 @rtype PasswordStrength |
289 Complexity_VeryStrong) |
|
290 @rtype int |
|
291 """ |
300 """ |
292 # how long is the password? |
301 # how long is the password? |
293 self.passwordLength["count"] = len(password) |
302 self.passwordLength["count"] = len(password) |
294 self.recommendedPasswordLength["count"] = len(password) |
303 self.recommendedPasswordLength["count"] = len(password) |
295 |
304 |
554 |
563 |
555 # password length |
564 # password length |
556 self.passwordLength["status"] = self.__determineStatus( |
565 self.passwordLength["status"] = self.__determineStatus( |
557 self.passwordLength["count"] - self.passwordLength["minimum"] |
566 self.passwordLength["count"] - self.passwordLength["minimum"] |
558 ) |
567 ) |
559 if self.passwordLength["status"] != self.Status_Failed: |
568 if self.passwordLength["status"] != PasswordCheckStatus.Failed: |
560 # requirement met |
569 # requirement met |
561 self.basicRequirements["count"] += 1 |
570 self.basicRequirements["count"] += 1 |
562 |
571 |
563 # uppercase letters |
572 # uppercase letters |
564 self.uppercaseLetters["status"] = self.__determineStatus( |
573 self.uppercaseLetters["status"] = self.__determineStatus( |
565 self.uppercaseLetters["count"] - self.uppercaseLetters["minimum"] |
574 self.uppercaseLetters["count"] - self.uppercaseLetters["minimum"] |
566 ) |
575 ) |
567 if self.uppercaseLetters["status"] != self.Status_Failed: |
576 if self.uppercaseLetters["status"] != PasswordCheckStatus.Failed: |
568 # requirement met |
577 # requirement met |
569 self.basicRequirements["count"] += 1 |
578 self.basicRequirements["count"] += 1 |
570 |
579 |
571 # lowercase letters |
580 # lowercase letters |
572 self.lowercaseLetters["status"] = self.__determineStatus( |
581 self.lowercaseLetters["status"] = self.__determineStatus( |
573 self.lowercaseLetters["count"] - self.lowercaseLetters["minimum"] |
582 self.lowercaseLetters["count"] - self.lowercaseLetters["minimum"] |
574 ) |
583 ) |
575 if self.lowercaseLetters["status"] != self.Status_Failed: |
584 if self.lowercaseLetters["status"] != PasswordCheckStatus.Failed: |
576 # requirement met |
585 # requirement met |
577 self.basicRequirements["count"] += 1 |
586 self.basicRequirements["count"] += 1 |
578 |
587 |
579 # numerics |
588 # numerics |
580 self.numerics["status"] = self.__determineStatus( |
589 self.numerics["status"] = self.__determineStatus( |
581 self.numerics["count"] - self.numerics["minimum"] |
590 self.numerics["count"] - self.numerics["minimum"] |
582 ) |
591 ) |
583 if self.numerics["status"] != self.Status_Failed: |
592 if self.numerics["status"] != PasswordCheckStatus.Failed: |
584 # requirement met |
593 # requirement met |
585 self.basicRequirements["count"] += 1 |
594 self.basicRequirements["count"] += 1 |
586 |
595 |
587 # symbols |
596 # symbols |
588 self.symbols["status"] = self.__determineStatus( |
597 self.symbols["status"] = self.__determineStatus( |
589 self.symbols["count"] - self.symbols["minimum"] |
598 self.symbols["count"] - self.symbols["minimum"] |
590 ) |
599 ) |
591 if self.symbols["status"] != self.Status_Failed: |
600 if self.symbols["status"] != PasswordCheckStatus.Failed: |
592 # requirement met |
601 # requirement met |
593 self.basicRequirements["count"] += 1 |
602 self.basicRequirements["count"] += 1 |
594 |
603 |
595 # judge the requirement status |
604 # judge the requirement status |
596 self.basicRequirements["status"] = self.__determineStatus( |
605 self.basicRequirements["status"] = self.__determineStatus( |
597 self.basicRequirements["count"] - self.basicRequirements["minimum"] |
606 self.basicRequirements["count"] - self.basicRequirements["minimum"] |
598 ) |
607 ) |
599 if self.basicRequirements["status"] != self.Status_Failed: |
608 if self.basicRequirements["status"] != PasswordCheckStatus.Failed: |
600 self.basicRequirements["rating"] = ( |
609 self.basicRequirements["rating"] = ( |
601 self.basicRequirements["bonus"] |
610 self.basicRequirements["bonus"] |
602 + self.basicRequirements["factor"] * self.basicRequirements["count"] |
611 + self.basicRequirements["factor"] * self.basicRequirements["count"] |
603 ) |
612 ) |
604 else: |
613 else: |
631 self.mirroredSequences["status"] = self.__determineBinaryStatus( |
640 self.mirroredSequences["status"] = self.__determineBinaryStatus( |
632 self.mirroredSequences["count"] |
641 self.mirroredSequences["count"] |
633 ) |
642 ) |
634 |
643 |
635 # we apply them only, if the length is not awesome |
644 # we apply them only, if the length is not awesome |
636 if self.recommendedPasswordLength["status"] != self.Status_Exceeded: |
645 if self.recommendedPasswordLength["status"] != PasswordCheckStatus.Exceeded: |
637 # repeatedSequences |
646 # repeatedSequences |
638 # Honor or punish the use of repeated sequences |
647 # Honor or punish the use of repeated sequences |
639 if self.repeatedSequences["count"] == 0: |
648 if self.repeatedSequences["count"] == 0: |
640 self.repeatedSequences["rating"] = self.repeatedSequences["bonus"] |
649 self.repeatedSequences["rating"] = self.repeatedSequences["bonus"] |
641 else: |
650 else: |
658 self.score["beforeRedundancy"] = self.score["count"] |
667 self.score["beforeRedundancy"] = self.score["count"] |
659 |
668 |
660 # apply the redundancy |
669 # apply the redundancy |
661 # is the password length requirement fulfilled? |
670 # is the password length requirement fulfilled? |
662 if ( |
671 if ( |
663 self.recommendedPasswordLength["status"] != self.Status_Exceeded |
672 self.recommendedPasswordLength["status"] != PasswordCheckStatus.Exceeded |
664 and self.score["count"] > 0 |
673 and self.score["count"] > 0 |
665 ): |
674 ): |
666 # full penalty, because password is not long enough, only for |
675 # full penalty, because password is not long enough, only for |
667 # a positive score |
676 # a positive score |
668 self.score["count"] *= 1.0 / self.redundancy["value"] |
677 self.score["count"] *= 1.0 / self.redundancy["value"] |