|
1 /* |
|
2 * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between |
|
3 * Copyright (C) 2006-2007 Dan Fuhry |
|
4 * pwstrength - Password evaluation and strength testing algorithm |
|
5 * |
|
6 * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License |
|
7 * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. |
|
8 * |
|
9 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied |
|
10 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details. |
|
11 */ |
|
12 |
|
13 function password_score_len(password) |
|
14 { |
|
15 if ( typeof(password) != "string" ) |
|
16 { |
|
17 return -10; |
|
18 } |
|
19 var len = password.length; |
|
20 var score = len - 7; |
|
21 return score; |
|
22 } |
|
23 |
|
24 function password_score(password) |
|
25 { |
|
26 if ( typeof(password) != "string" ) |
|
27 { |
|
28 return -10; |
|
29 } |
|
30 var score = 0; |
|
31 var debug = []; |
|
32 // length check |
|
33 var lenscore = password_score_len(password); |
|
34 |
|
35 debug.push(''+lenscore+' points for length'); |
|
36 |
|
37 score += lenscore; |
|
38 |
|
39 var has_upper_lower = false; |
|
40 var has_symbols = false; |
|
41 var has_numbers = false; |
|
42 |
|
43 // contains uppercase and lowercase |
|
44 if ( password.match(/[A-z]+/) && password.toLowerCase() != password ) |
|
45 { |
|
46 score += 1; |
|
47 has_upper_lower = true; |
|
48 debug.push('1 point for having uppercase and lowercase'); |
|
49 } |
|
50 |
|
51 // contains symbols |
|
52 if ( password.match(/[^A-z0-9]+/) ) |
|
53 { |
|
54 score += 1; |
|
55 has_symbols = true; |
|
56 debug.push('1 point for having nonalphanumeric characters (matching /[^A-z0-9]+/)'); |
|
57 } |
|
58 |
|
59 // contains numbers |
|
60 if ( password.match(/[0-9]+/) ) |
|
61 { |
|
62 score += 1; |
|
63 has_numbers = true; |
|
64 debug.push('1 point for having numbers'); |
|
65 } |
|
66 |
|
67 if ( has_upper_lower && has_symbols && has_numbers && password.length >= 9 ) |
|
68 { |
|
69 // if it has uppercase and lowercase letters, symbols, and numbers, and is of considerable length, add some serious points |
|
70 score += 4; |
|
71 debug.push('4 points for having uppercase and lowercase, numbers, and nonalphanumeric and being more than 8 characters'); |
|
72 } |
|
73 else if ( has_upper_lower && has_symbols && has_numbers && password.length >= 6 ) |
|
74 { |
|
75 // still give some points for passing complexity check |
|
76 score += 2; |
|
77 debug.push('2 points for having uppercase and lowercase, numbers, and nonalphanumeric'); |
|
78 } |
|
79 else if(( ( has_upper_lower && has_symbols ) || |
|
80 ( has_upper_lower && has_numbers ) || |
|
81 ( has_symbols && has_numbers ) ) && password.length >= 6 ) |
|
82 { |
|
83 // if 2 of the three main complexity checks passed, add a point |
|
84 score += 1; |
|
85 debug.push('1 point for having 2 of 3 complexity checks'); |
|
86 } |
|
87 else if ( ( !has_upper_lower && !has_numbers && has_symbols ) || |
|
88 ( !has_upper_lower && !has_symbols && has_numbers ) || |
|
89 ( !has_numbers && !has_symbols && has_upper_lower ) ) |
|
90 { |
|
91 score += -2; |
|
92 debug.push('-2 points for only meeting 1 complexity check'); |
|
93 } |
|
94 else if ( password.match(/^[0-9]*?([a-z]+)[0-9]?$/) ) |
|
95 { |
|
96 // password is something like magnum1 which will be cracked in seconds |
|
97 score += -4; |
|
98 debug.push('-4 points for being of the form [number][word][number], which is easily cracked'); |
|
99 } |
|
100 else if ( !has_upper_lower && !has_numbers && !has_symbols ) |
|
101 { |
|
102 // this is if somehow the user inputs a password that doesn't match the rule above, but still doesn't contain upper and lowercase, numbers, or symbols |
|
103 debug.push('-3 points for not meeting any complexity checks'); |
|
104 score += -3; |
|
105 } |
|
106 |
|
107 // |
|
108 // Repetition |
|
109 // Example: foobar12345 should be deducted points, where f1o2o3b4a5r should be given points |
|
110 // None of the positive ones kick in unless the length is at least 8 |
|
111 // |
|
112 |
|
113 if ( password.match(/([A-Z][A-Z][A-Z][A-Z]|[a-z][a-z][a-z][a-z])/) ) |
|
114 { |
|
115 debug.push('-2 points for having more than 4 letters of the same case in a row'); |
|
116 score += -2; |
|
117 } |
|
118 else if ( password.match(/([A-Z][A-Z][A-Z]|[a-z][a-z][a-z])/) ) |
|
119 { |
|
120 debug.push('-1 points for having more than 3 letters of the same case in a row'); |
|
121 score += -1; |
|
122 } |
|
123 else if ( password.match(/[A-z]/) && !password.match(/([A-Z][A-Z][A-Z]|[a-z][a-z][a-z])/) && password.length >= 8 ) |
|
124 { |
|
125 debug.push('1 point for never having more than 2 letters of the same case in a row'); |
|
126 score += 1; |
|
127 } |
|
128 |
|
129 if ( password.match(/[0-9][0-9][0-9][0-9]/) ) |
|
130 { |
|
131 debug.push('-2 points for having 4 or more numbers in a row'); |
|
132 score += -2; |
|
133 } |
|
134 else if ( password.match(/[0-9][0-9][0-9]/) ) |
|
135 { |
|
136 debug.push('-1 points for having 3 or more numbers in a row'); |
|
137 score += -1; |
|
138 } |
|
139 else if ( has_numbers && !password.match(/[0-9][0-9][0-9]/) && password.length >= 8 ) |
|
140 { |
|
141 debug.push('1 point for never more than 2 numbers in a row'); |
|
142 score += -1; |
|
143 } |
|
144 |
|
145 // make passwords like fooooooooooooooooooooooooooooooooooooo totally die by subtracting a point for each character repeated at least 3 times in a row |
|
146 var prev_char = ''; |
|
147 var warn = false; |
|
148 var loss = 0; |
|
149 for ( var i = 0; i < password.length; i++ ) |
|
150 { |
|
151 var chr = password.substr(i, 1); |
|
152 if ( chr == prev_char && warn ) |
|
153 { |
|
154 loss += -1; |
|
155 } |
|
156 else if ( chr == prev_char && !warn ) |
|
157 { |
|
158 warn = true; |
|
159 } |
|
160 else if ( chr != prev_char && warn ) |
|
161 { |
|
162 warn = false; |
|
163 } |
|
164 prev_char = chr; |
|
165 } |
|
166 if ( loss < 0 ) |
|
167 { |
|
168 debug.push(''+loss+' points for immediate character repetition'); |
|
169 score += loss; |
|
170 // this can bring the score below -10 sometimes |
|
171 if ( score < -10 ) |
|
172 { |
|
173 debug.push('Score set to -10 because it went below that floor'); |
|
174 score = -10; |
|
175 } |
|
176 } |
|
177 |
|
178 var debug_txt = "<b>How this score was calculated</b>\nYour score was tallied up based on an extensive algorithm which outputted\nthe following scores based on traits of your password. Above you can see the\ncomposite score; your individual scores based on certain tests are below.\n\nThe scale is open-ended, with a minimum score of -10. 10 is very strong, 4\nis strong, 1 is good and -3 is fair. Below -3 scores \"Weak.\"\n\n"; |
|
179 for ( var i = 0; i < debug.length; i++ ) |
|
180 { |
|
181 debug_txt += debug[i] + "\n"; |
|
182 } |
|
183 |
|
184 if ( window.console ) |
|
185 window.console.info(debug_txt); |
|
186 else if ( document.getElementById('passdebug') ) |
|
187 document.getElementById('passdebug').innerHTML = debug_txt; |
|
188 |
|
189 return score; |
|
190 } |
|
191 |
|
192 function password_score_draw(score) |
|
193 { |
|
194 // some colors are from the Gmail sign-up form |
|
195 if ( score >= 10 ) |
|
196 { |
|
197 var color = '#000000'; |
|
198 var fgcolor = '#666666'; |
|
199 var str = 'Very strong (score: '+score+')'; |
|
200 } |
|
201 else if ( score > 3 ) |
|
202 { |
|
203 var color = '#008000'; |
|
204 var fgcolor = '#004000'; |
|
205 var str = 'Strong (score: '+score+')'; |
|
206 } |
|
207 else if ( score >= 1 ) |
|
208 { |
|
209 var color = '#6699cc'; |
|
210 var fgcolor = '#4477aa'; |
|
211 var str = 'Good (score: '+score+')'; |
|
212 } |
|
213 else if ( score >= -3 ) |
|
214 { |
|
215 var color = '#f5ac00'; |
|
216 var fgcolor = '#ffcc33'; |
|
217 var str = 'Fair (score: '+score+')'; |
|
218 } |
|
219 else |
|
220 { |
|
221 var color = '#aa0033'; |
|
222 var fgcolor = '#FF6060'; |
|
223 var str = 'Weak (score: '+score+')'; |
|
224 } |
|
225 return { |
|
226 color: color, |
|
227 fgcolor: fgcolor, |
|
228 str: str |
|
229 }; |
|
230 } |
|
231 |
|
232 function password_score_field(field) |
|
233 { |
|
234 var indicator = false; |
|
235 if ( field.nextSibling ) |
|
236 { |
|
237 if ( field.nextSibling.className == 'password-checker' ) |
|
238 { |
|
239 indicator = field.nextSibling; |
|
240 } |
|
241 } |
|
242 if ( !indicator ) |
|
243 { |
|
244 var indicator = document.createElement('span'); |
|
245 indicator.className = 'password-checker'; |
|
246 if ( field.nextSibling ) |
|
247 { |
|
248 field.parentNode.insertBefore(indicator, field.nextSibling); |
|
249 } |
|
250 else |
|
251 { |
|
252 field.parentNode.appendChild(indicator); |
|
253 } |
|
254 } |
|
255 var score = password_score(field.value); |
|
256 var data = password_score_draw(score); |
|
257 indicator.style.color = data.color; |
|
258 indicator.style.fontWeight = 'bold'; |
|
259 indicator.innerHTML = ' ' + data.str; |
|
260 |
|
261 if ( document.getElementById('pwmeter') ) |
|
262 { |
|
263 var div = document.getElementById('pwmeter'); |
|
264 div.style.width = '250px'; |
|
265 score += 10; |
|
266 if ( score > 25 ) |
|
267 score = 25; |
|
268 div.style.backgroundColor = data.color; |
|
269 var width = Math.round( score * (250 / 25) ); |
|
270 div.innerHTML = '<div style="width: '+width+'px; background-color: '+data.fgcolor+'; height: 8px;"></div>'; |
|
271 } |
|
272 } |
|
273 |