134
+ − 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