src/eric7/Utilities/PasswordChecker.py

branch
eric7
changeset 10482
72d9b5ea39b4
parent 10475
ee41fab001f2
equal deleted inserted replaced
10481:9aea3575bd16 10482:72d9b5ea39b4
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 }
70 # 4) Numeric character use 81 # 4) Numeric character use
71 # 5) Symbol use 82 # 5) Symbol use
72 self.basicRequirements = { 83 self.basicRequirements = {
73 "count": 0, 84 "count": 0,
74 "minimum": 3, # have to be matched to get the bonus 85 "minimum": 3, # have to be matched to get the bonus
75 "status": self.Status_Failed, 86 "status": PasswordCheckStatus.Failed,
76 "rating": 0, 87 "rating": 0,
77 "factor": 1.0, 88 "factor": 1.0,
78 "bonus": 10, 89 "bonus": 10,
79 "penalty": -10, 90 "penalty": -10,
80 } 91 }
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 }
161 # such as "abc" or "MNO" to be not part of the password 172 # such as "abc" or "MNO" to be not part of the password
162 self.sequentialLetters = { 173 self.sequentialLetters = {
163 "data": "abcdefghijklmnopqrstuvwxyz", 174 "data": "abcdefghijklmnopqrstuvwxyz",
164 "length": 3, 175 "length": 3,
165 "count": 0, 176 "count": 0,
166 "status": self.Status_Failed, 177 "status": PasswordCheckStatus.Failed,
167 "rating": 0, 178 "rating": 0,
168 "factor": -1.0, 179 "factor": -1.0,
169 "bonus": 0, 180 "bonus": 0,
170 "penalty": -10, 181 "penalty": -10,
171 } 182 }
174 # such as "123" to be not part of the password 185 # such as "123" to be not part of the password
175 self.sequentialNumerics = { 186 self.sequentialNumerics = {
176 "data": "0123456789", 187 "data": "0123456789",
177 "length": 3, 188 "length": 3,
178 "count": 0, 189 "count": 0,
179 "status": self.Status_Failed, 190 "status": PasswordCheckStatus.Failed,
180 "rating": 0, 191 "rating": 0,
181 "factor": -1.0, 192 "factor": -1.0,
182 "bonus": 0, 193 "bonus": 0,
183 "penalty": -10, 194 "penalty": -10,
184 } 195 }
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"]
676 self.score["adjusted"] = self.score["count"] 685 self.score["adjusted"] = self.score["count"]
677 686
678 # judge it 687 # judge it
679 for index in range(len(self.complexity["limits"])): 688 for index in range(len(self.complexity["limits"])):
680 if self.score["adjusted"] <= self.complexity["limits"][index]: 689 if self.score["adjusted"] <= self.complexity["limits"][index]:
681 self.complexity["value"] = index 690 self.complexity["value"] = PasswordStrength(index)
682 break 691 break
683 692
684 return self.complexity["value"] 693 return self.complexity["value"]

eric ide

mercurial