includes/clientside/static/pwstrength.js
changeset 134 175776498ef1
equal deleted inserted replaced
133:af0f6ec48de3 134:175776498ef1
       
     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