author | Dan Fuhry <dan@enanocms.org> |
Mon, 15 Nov 2010 19:21:40 -0500 | |
changeset 1311 | a228f7e8fb15 |
parent 1227 | bdac73ed481e |
permissions | -rw-r--r-- |
/* * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between * Copyright (C) 2006-2007 Dan Fuhry * pwstrength - Password evaluation and strength testing algorithm * * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details. */ function password_score_len(password) { if ( typeof(password) != "string" ) { return -10; } var len = password.length; var score = len - 7; return score; } function password_score(password) { if ( typeof(password) != "string" ) { return -10; } var score = 0; var debug = []; // length check var lenscore = password_score_len(password); debug.push(''+lenscore+' points for length'); score += lenscore; var has_upper_lower = false; var has_symbols = false; var has_numbers = false; // contains uppercase and lowercase if ( password.match(/[A-z]+/) && password.toLowerCase() != password ) { score += 1; has_upper_lower = true; debug.push('1 point for having uppercase and lowercase'); } // contains symbols if ( password.match(/[^A-z0-9]+/) ) { score += 1; has_symbols = true; debug.push('1 point for having nonalphanumeric characters (matching /[^A-z0-9]+/)'); } // contains numbers if ( password.match(/[0-9]+/) ) { score += 1; has_numbers = true; debug.push('1 point for having numbers'); } if ( has_upper_lower && has_symbols && has_numbers && password.length >= 9 ) { // if it has uppercase and lowercase letters, symbols, and numbers, and is of considerable length, add some serious points score += 4; debug.push('4 points for having uppercase and lowercase, numbers, and nonalphanumeric and being more than 8 characters'); } else if ( has_upper_lower && has_symbols && has_numbers && password.length >= 6 ) { // still give some points for passing complexity check score += 2; debug.push('2 points for having uppercase and lowercase, numbers, and nonalphanumeric'); } else if(( ( has_upper_lower && has_symbols ) || ( has_upper_lower && has_numbers ) || ( has_symbols && has_numbers ) ) && password.length >= 6 ) { // if 2 of the three main complexity checks passed, add a point score += 1; debug.push('1 point for having 2 of 3 complexity checks'); } else if ( ( !has_upper_lower && !has_numbers && has_symbols ) || ( !has_upper_lower && !has_symbols && has_numbers ) || ( !has_numbers && !has_symbols && has_upper_lower ) ) { score += -2; debug.push('-2 points for only meeting 1 complexity check'); } else if ( password.match(/^[0-9]*?([a-z]+)[0-9]?$/) ) { // password is something like magnum1 which will be cracked in seconds score += -4; debug.push('-4 points for being of the form [number][word][number], which is easily cracked'); } else if ( !has_upper_lower && !has_numbers && !has_symbols ) { // 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 debug.push('-3 points for not meeting any complexity checks'); score += -3; } // // Repetition // Example: foobar12345 should be deducted points, where f1o2o3b4a5r should be given points // None of the positive ones kick in unless the length is at least 8 // if ( password.match(/([A-Z][A-Z][A-Z][A-Z]|[a-z][a-z][a-z][a-z])/) ) { debug.push('-2 points for having more than 4 letters of the same case in a row'); score += -2; } else if ( password.match(/([A-Z][A-Z][A-Z]|[a-z][a-z][a-z])/) ) { debug.push('-1 points for having more than 3 letters of the same case in a row'); score += -1; } else if ( password.match(/[A-z]/) && !password.match(/([A-Z][A-Z][A-Z]|[a-z][a-z][a-z])/) && password.length >= 8 ) { debug.push('1 point for never having more than 2 letters of the same case in a row'); score += 1; } if ( password.match(/[0-9][0-9][0-9][0-9]/) ) { debug.push('-2 points for having 4 or more numbers in a row'); score += -2; } else if ( password.match(/[0-9][0-9][0-9]/) ) { debug.push('-1 points for having 3 or more numbers in a row'); score += -1; } else if ( has_numbers && !password.match(/[0-9][0-9][0-9]/) && password.length >= 8 ) { debug.push('1 point for never more than 2 numbers in a row'); score += -1; } // make passwords like fooooooooooooooooooooooooooooooooooooo totally die by subtracting a point for each character repeated at least 3 times in a row var prev_char = ''; var warn = false; var loss = 0; for ( var i = 0; i < password.length; i++ ) { var chr = password.substr(i, 1); if ( chr == prev_char && warn ) { loss += -1; } else if ( chr == prev_char && !warn ) { warn = true; } else if ( chr != prev_char && warn ) { warn = false; } prev_char = chr; } if ( loss < 0 ) { debug.push(''+loss+' points for immediate character repetition'); score += loss; // this can bring the score below -10 sometimes if ( score < -10 ) { debug.push('Score set to -10 because it went below that floor'); score = -10; } } 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"; for ( var i = 0; i < debug.length; i++ ) { debug_txt += debug[i] + "\n"; } // For users that really want to know why their password sucks. // Not localized because the feature is really only used for debugging the algorithm. if ( document.getElementById('passdebug') ) document.getElementById('passdebug').innerHTML = debug_txt; return score; } function password_score_draw(score) { if ( !$lang ) { // $lang isn't initted yet, this happens sometimes on the usercp/emailpassword form. // Try to init it if we have ENANO_LANG_ID and enano_lang; if not, report an error. load_component('l10n'); if ( typeof(enano_lang) == 'object' && typeof(ENANO_LANG_ID) == 'number' ) { language_onload(); } else { return { 'color' : '#000000', 'fgcolor' : '#666666', 'str' : 'Language init failed' }; } } // some colors are from the Gmail sign-up form if ( score >= 10 ) { var color = '#010101'; var fgcolor = '#666666'; var str = $lang.get('usercp_pwstrength_score_verystrong', { score: score }); } else if ( score > 3 ) { var color = '#008000'; var fgcolor = '#004000'; var str = $lang.get('usercp_pwstrength_score_strong', { score: score }); } else if ( score >= 1 ) { var color = '#6699cc'; var fgcolor = '#4477aa'; var str = $lang.get('usercp_pwstrength_score_good', { score: score }); } else if ( score >= -3 ) { var color = '#f5ac00'; var fgcolor = '#ffcc33'; var str = $lang.get('usercp_pwstrength_score_fair', { score: score }); } else { var color = '#aa0033'; var fgcolor = '#FF6060'; var str = $lang.get('usercp_pwstrength_score_weak', { score: score }); } var ret = { color: color, fgcolor: fgcolor, str: str }; return ret; } function password_score_field(field) { var indicator = false; if ( field.nextSibling ) { if ( field.nextSibling.className == 'password-checker' ) { indicator = field.nextSibling; } } if ( !indicator ) { var indicator = document.createElement('span'); indicator.className = 'password-checker'; if ( field.nextSibling ) { field.parentNode.insertBefore(indicator, field.nextSibling); } else { field.parentNode.appendChild(indicator); } } var score = password_score(field.value); var data = password_score_draw(score); indicator.style.color = data.color; indicator.style.fontWeight = 'bold'; indicator.innerHTML = ' ' + data.str; if ( document.getElementById('pwmeter') ) { var div = document.getElementById('pwmeter'); div.style.width = '250px'; score += 10; if ( score > 25 ) score = 25; div.style.backgroundColor = data.color; var width = Math.round( score * (250 / 25) ); div.innerHTML = '<div style="width: '+width+'px; background-color: '+data.fgcolor+'; height: 8px;"></div>'; } }