Javascript Password Strength Meter

What makes a strong password? This quick and dirty password strength meter is meant to help users learn how to create stronger passwords. Because it's written in Javascript the password is never sent over the network. Feel free to audit the code and recommend some better regular expressions, weightings, or bug fixes by submitting a comment.

NOTE: This was meant as a quick and dirty educational tool. It served my purposes many years ago. If you want to make it better please submit a comment with a patch or some type of improvement. Other than that I'm going to ignore comments like, "I put in XYZ password at it said it was weak, strong, whatever."

Tips for strong passwords:

  1. Make your password 8 characters or more
  2. Use mixed case letters (upper and lower case)
  3. Use more than one number
  4. Use special characters (!,@,#,$,%,^,&,*,?,_,~)
  5. Use L33t
  6. Use a random password generator/password vault like Password Safe or pwsafe
  7. Use PasswordMaker



Type the password:


Strength score is:   Strength verdict:


Log:

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Great

Very usefull tips

Different approach to password strength calculation

I could see it in my mind: a kind of frequency spectrum bar-chart of password components pulsing up and down like the graphic VU meter on a DJ's mixing panel. The height of the vertical bars represents the number of lowercase, uppercase, numbers and special characters present in the password as it is typed by an unsuspecting User--a password histogram. If the bars are more or less the same height the password is stronger than if the heights of the bars vary. The more irregular the heights are, the weaker is the password.

Also, as the password length increases, the overall height of the bars increases. Thus, the taller the meter, the stronger, yet, is the password. In my gut I knew there was a way to express this mathematically and after fiddling around with it, I drew the bars on a piece of paper, and that's when I saw it: each bar is a dimension on a crazy 4-dimensional solid. Multiply the dimensions together and you get a volume. The larger the volume, the stronger the password. All that is left is to determine the thresholds for the various degrees of strength.

It's easier to see in 3 dimensions. Picture a hexahedron with rectangular faces. It has a height, a width and a depth. Assign the uppercase count to the height, the numbers count to the width, and the special chars count to the depth. If all the counts are equal, then the shape is a cube. If, the counts vary, then the shape is composed of rectangles. Say the password is 9 characters long, and it has 3 uppercase, 3 numbers and 3 special chars. The volume is 3*3*3 or 27. Now, suppose the counts are: 7 uppercase, 1 number and 1 special char. The volume becomes a long thin sort of square rod kind of solid with a volume of 7*1*1 or 7 (much less than 27). How about something in the middle: 5 uppercase, 2 numbers and 2 special chars. That's 5*2*2 or 20, which is still less than 27. A cube is always going to have the greatest volume.

This also includes a "runs" detector. It's basically a digital hi-pass filter (or edge detector) with rectification (absolute zero applied to each difference). See comment on the detectRuns() method for details (this, almost certainly doesn't work with Unicode.). This, actually, could be used to generate an overall password strength, if the various char ranges were weighted to produce filterable edge intensities.

Here's the listing:

/**
 * Determines the strength of a given password based on
 * frequency of occurrence of lowercase, uppercase,
 * numbers and the special characters passed via the spc_chars
 * argument.  The more even the spread of occurrences, the
 * stronger the password.
 *
 * This class contains the following public parameters:
 *   'lcase_count'    : lowercase occurrence count.
 *   'ucase_count'    : uppercase occurrence count.
 *   'num_count'      : number occurrence count.
 *   'schar_count'    : special character occurrence count.
 *   'length'         : length of password string.
 *   'strength'       : strength value of password.
 *   'verdict'        : textual strength indication
 *                      ['weak', 'medium', 'strong'].
 *
 * @param string arg_password  The password
 * @param string arg_spc_chars A string of special characters
 *     to search for in the password. By making this an
 *     argument, the range of special characters can be
 *     controlled  externally.
 * @return string The verdict as 'Weak'|'Medium'|'Strong'
 */
function Password(arg_password, arg_spc_chars)
{
    var password = arg_password;
    var spc_chars = arg_spc_chars;
    this.lcase_count = 0;
    this.ucase_count = 0;
    this.num_count = 0;
    this.schar_count = 0;
    this.length = 0;
    this.strength = 0;
    this.runs_score = 0;
    this.verdict = '';

    // These numbers are just guesses on my part (and not
    // all that educated, either ;) Adjust accordingly.
    var verdict_conv = {'weak':2.7, 'medium':53, 'strong':150};

    // These are weighting factors.  I figure that including
    // numbers is a little better than including uppercase
    // because numbers probably are not vulnerable to
    // dictionary searches, and including special chars is
    // even better.  These factors provide yet another
    // dimension.  Again, there are only guesses.
    var flc = 1.0;  // lowercase factor
    var fuc = 1.0;  // uppercase factor
    var fnm = 1.3;  // number factor
    var fsc = 1.5;  // special char factor

    this.getStrength = function()
    {
        if ((this.run_score = this.detectRuns()) <= 1)
        {
            return "Very weak";
        }

        var regex_sc = new RegExp('['+spc_chars+']', 'g');

        this.lcase_count = password.match(/[a-z]/g);
        this.lcase_count = (this.lcase_count) ? this.lcase_count.length : 0;
        this.ucase_count = password.match(/[A-Z]/g);
        this.ucase_count = (this.ucase_count) ? this.ucase_count.length : 0;
        this.num_count   = password.match(/[0-9]/g);
        this.num_count   = (this.num_count) ? this.num_count.length : 0;
        this.schar_count = password.match(regex_sc);
        this.schar_count = (this.schar_count) ? this.schar_count.length : 0;
        this.length = password.length;

        var avg = this.length / 4;

        // I'm dividing by (avg + 1) to linearize the strength a bit.
        // To get a result that ranges from 0 to 1, divide 
        // by Math.pow(avg + 1, 4)
        this.strength = ((this.lcase_count * flc + 1) * 
                         (this.ucase_count * fuc + 1) *
                         (this.num_count * fnm + 1) * 
                         (this.schar_count * fsc + 1)) / (avg + 1);

        if (this.strength > verdict_conv.strong)
            this.verdict = 'Strong';
        else if (this.strength > verdict_conv.medium)
            this.verdict = 'Medium';
        else if (this.strength > verdict_conv.weak)
            this.verdict = 'Weak';
        else
            this.verdict = "Forget it!";

        return this.verdict;
    }

    // This is basically an edge detector with a 'rectified' (or
    // absolute zero) result.  The difference of adjacent equivalent 
    // char values is zero.  The greater the difference, the higher
    // the result.  'aaaaa' sums to 0. 'abcde' sums to 1.  'acegi'
    // sums to 2, etc.  'aaazz', which has a sharp edge, sums to  
    // 6.25.  Any thing 1 or below is a run, and should be considered
    // weak.
    this.detectRuns = function()
    {
        var parts = password.split('');
        var ords = new Array();
        for (i in parts)
        {
            ords[i] = parts[i].charCodeAt(0);
        }

        var accum = 0;
        var lasti = ords.length-1

        for (var i=0; i < lasti; ++i)
        {
            accum += Math.abs(ords[i] - ords[i+1]);
        }

        return accum/lasti;
    }


    this.toString = function()
    {
        return 'lcase: '+this.lcase_count+
               ' -- ucase: '+this.ucase_count+
               ' -- nums: '+this.num_count+
               ' -- schar: '+this.schar_count+
               ' -- strength: '+this.strength+
               ' -- verdict: '+this.verdict;
    }
}

Usage:

function checkPassword()
{
    var special_chars = "~!@#$%&*";

    var pw = new Password(document.getElementById('da_password').value, 
                           special_chars);

    var verdict = pw.getStrength();
    var hint = '';
    if (pw.ucase_count == 0) hint += "Try adding some uppercase letters. ";
    if (pw.num_count == 0) hint += "Try adding some numbers. ";
    if (pw.schar_count == 0) hint += 
        "Try adding one or more of the following characters: "+
        special_chars+".";
    if (pw.run_score <= 1) hint += "Avoid runs (e.g. 'aaaa', 'efghi', '1234'). ";
    element = document.getElementById("hint");
    element.innerHTML = hint;
    element = document.getElementById("strength");
    element.innerHTML = verdict;
}

The HTML:

 
    <input type="text" id="da_password" onkeyup="checkPassword()"/>
    <div id="hint"></div>
    <div id="strength"></div>

Missing is a dictionary look-up. Also, certain special chars will break this (such as '^').

some work on the regex

Hi, found the code while googlin' for a JS password checker - nice one, and useful for my purposes. So, I decided to adopt it for my project, with some minor mods, especially by re-writing the regex.
Ah, one word to the combination changes: I decided not to implement them, because IMHO all these are checked beforehand and don't really add strength.
Well, here's my version. It's written as a JS function, and just returning the strength, since this is what I needed - maybe someone can use it.


checkpwd: function(password) {
/*function to calculate "strength" of a password
* expects password, returns strength
*/
//calculate strength out of length
var strength = 0;
var length = password.length;
if (length > 16) {
strength = 18;
} else {
if (length > 8) {
strength = 12
} else {
if (length > 5) {
strength = 6;
} else {
if (length > 0) {
strength = 3;
} else {
return 0; //no password means no strength, :-)
}
}
}
}
//Now consider the letters
if (password.match(/[a-z]/i)) {
strength += 5; //at least one letter, 5 points
if (password.match(/[a-z][A-Z]|[A-Z][a-z]/)) {
strength += 2; //mixed-case adds 2 points
}
} //no letter, no points
//Now check for numbers
count = password.match(/[0-9]/g);
if (count) {
strength += 5; //at least one number, 5 points
if (count > 2) {
strength += 2; //three or more numbers, 2 extra points;
}
} //no number, no points
//Now check for special characters
count = password.match(/[!,@,#,$,%,^,*,?,_,~,-]/g)
if (count) {
strength += 5; //at least one special characters, 5 points
if (count > 1) {
strenth += 2; //two or more special characters, 2 extra points
}
} //no special characters, no points
return strength;
}

Flex version

Used your scoring system (with some minor modifications) to create a Flex/Flash component, also MIT licensed. Check it out...
http://www.mattholden.com/dslabs/PasswordEntry.swf
(demo)
http://www.mattholden.com/dslabs/PasswordEntry.mxml
(code) Thanks!

great, if not for the bugs still in it!

Quote: Flex version
Submitted by Jaeden on Wed, 2008-06-25 22:22.
Used your scoring system (with some minor modifications) to create a Flex/Flash component, also MIT licensed. Check it out...
http://www.mattholden.com/dslabs/PasswordEntry.swf (demo)
http://www.mattholden.com/dslabs/PasswordEntry.mxml (code)


Even with all the alterations afterwards, one simple problem remains: IE COPY AND PASTE SCREWS IT UP
probably 50% of the time people will instead of re-typing the password, will copy / paste it. And with your code, when they do, the whole page gets screwed, the status bar goes haywire and jumps slap to the right going off the edge, etc...
great code, just needs tweaked.

passwordmeter.js

Very nice - thanks.


A rather trivial submission but saves CPU cycles.

if(intScore < 16)
{
strVerdict = "very weak"
}
else if (intScore < 25)
{
strVerdict = "weak"
}
else if (intScore < 35)
{
strVerdict = "mediocre"
}
else if (intScore < 45)
{
strVerdict = "strong"
}
else
{
strVerdict = "stronger"
}

a dictionnary would be nice...

because it doesn't detect simple words.

I was gonna look at that

I was going look at that, once I was happy with the original code.

Weighting

The weighting in the following code is much better. https://www.doxpop.com/prod/js/passwordStrength.js eg. A full combo means that a bruteforce attack would take a long time. They still have broken regex's though.

Erm... I'm talking rubbish.

Erm... I'm talking rubbish. The weighting is the same. Anyone know of any good websites that show bruteforce cracking times for different character usage?

This page has some

This page has some analysis.
http://www.lockdown.co.uk/?pg=combi I'll work out some better weighing from this.

From that analysis...

<html>

<head>

<script language="JavaScript1.1">

<!-- Begin

/* ************************************************************

Created: 20060120

Author: Steve Moitozo

Description: This is a quick and dirty password quality meter

written in JavaScript so that the password does

not pass over the network

Revision Author: Dick Ervasti (dick dot ervasti at quty dot com)

Revision Description: Exchanged text based prompts for a graphic thermometer

Revision Author: Matt Culverwell (zebadger@hotmail.com)

Revision Description: Fixed regular expressions

Revision Author: Matt Culverwell (zebadger@hotmail.com)

Revision Description: Added brand new weighting based on figures from http://www.lockdown.co.uk/?pg=combi



************************************************************ */

function testPassword(passwd)

{

var description = new Array();

description[0] = "<table><tr><td><table cellpadding=0 cellspacing=2><tr><td height=4 width=30 bgcolor=#ff0000></td><td height=4 width=120 bgcolor=tan></td></tr></table></td><td>   <b>Weakest</b></td></tr></table>";

description[1] = "<table><tr><td><table cellpadding=0 cellspacing=2><tr><td height=4 width=60 bgcolor=#990000></td><td height=4 width=90 bgcolor=tan></td></tr></table></td><td>   <b>Weak</b></td></tr></table>";

description[2] = "<table><tr><td><table cellpadding=0 cellspacing=2><tr><td height=4 width=90 bgcolor=#990099></td><td height=4 width=60 bgcolor=tan></td></tr></table></td><td>   <b>Improving</b></td></tr></table>";

description[3] = "<table><tr><td><table cellpadding=0 cellspacing=2><tr><td height=4 width=120 bgcolor=#000099></td><td height=4 width=30 bgcolor=tan></td></tr></table></td><td>   <b>Strong</b></td></tr></table>";

description[4] = "<table><tr><td><table><tr><td height=4 width=150 bgcolor=#0000ff></td></tr></table></td><td>   <b>Strongest</b></td></tr></table>";

description[5] = "<table><tr><td><table><tr><td height=4 width=150 bgcolor=tan></td></tr></table></td><td>   <b>Begin Typing</b></td></tr></table>";



var base = 0

var combos = 0



if (passwd.match(/[a-z]/))

{

base = (base+26);

}



if (passwd.match(/[A-Z]/))

{

base = (base+26);

}



if (passwd.match(/\d+/))

{

base = (base+10);

}



if (passwd.match(/[>!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~]/))

{

base = (base+33);

}



combos=Math.pow(base,passwd.length);



if(combos == 1)

{

strVerdict = description[5];

}

else if(combos > 1 && combos < 1000000)

{

strVerdict = description[0];

}

else if (combos >= 1000000 && combos < 1000000000000)

{

strVerdict = description[1];

}

else if (combos >= 1000000000000 && combos < 1000000000000000000)

{

strVerdict = description[2];

}

else if (combos >= 1000000000000000000 && combos < 1000000000000000000000000)

{

strVerdict = description[3];

}

else

{

strVerdict = description[4];

}





document.getElementById("Words").innerHTML= (strVerdict);



}

// End-->

</script>



</head>



<body>

<table><tr valign=top><td><form name="commandForm">

Type password: <input type=password size=30 maxlength=50 name=password onkeyup="testPassword(document.forms.commandForm.password.value);" value="">

<br/><font color="#808080">Minimum 6 Characters</td><td><font size="1">  Password Strength:</font><a id="Words"><table><tr><td><table><tr><td height=4 width=150 bgcolor=tan></td></tr></table></td><td>   <b>Begin Typing</b></td></tr></table></a></td></tr></table>

</td></tr></table>



</form>



<pre>

This works by working out how many sets of characters you have used from

<UL>

<LI>Upper (set of 26)</LI>

<LI>Lower (set of 26)</LI>

<LI>Numeric (set of 10)</LI>

<LI>Symbols (set of 33)</LI>

</UL>

It then works out the number of combinations of passwords that could be made based on the sets that you have used and the password length.





Weakest < 1000000

Weak < 1000000000000

Improving < 1000000000000000000

Strong < 1000000000000000000000000

Strongest >=1000000000000000000000000



</pre>





</body>

</html>

I've written the above code

I've written the above code that calculates the number of combinations that the sets of characters use and then weights them based on that. Not sure that I've given it good numbers for the weighting.

Fixed Regex

I've made some changes to the regex to make it work (like the last combo) and in some cases just to make it easier. Here is the whole regex section.
// LETTERS
if (passwd.match(/[a-z]/)) // [verified] at least one lower case letter
{
intScore = (intScore+1)
} if (passwd.match(/[A-Z]/)) // [verified] at least one upper case letter
{
intScore = (intScore+5)
} // NUMBERS
if (passwd.match(/\d+/)) // [verified] at least one number
{
intScore = (intScore+5)
} if (passwd.match(/(\d.*\d.*\d)/)) // [verified] at least three numbers
{
intScore = (intScore+5)
} // SPECIAL CHAR
if (passwd.match(/[!,@#$%^&*?_~]/)) // [verified] at least one special character
{
intScore = (intScore+5)
} if (passwd.match(/([!,@#$%^&*?_~].*[!,@#$%^&*?_~])/)) // [verified] at least two special characters
{
intScore = (intScore+5)
} // COMBOS
if (passwd.match(/[a-z]/) && passwd.match(/[A-Z]/)) // [verified] both upper and lower case
{
intScore = (intScore+2)
} if (passwd.match(/\d/) && passwd.match(/\D/)) // [verified] both letters and numbers
{
intScore = (intScore+2)
} // [Verified] Upper Letters, Lower Letters, numbers and special characters
if (passwd.match(/[a-z]/) && passwd.match(/[A-Z]/) && passwd.match(/\d/) && passwd.match(/[!,@#$%^&*?_~]/))
{
intScore = (intScore+2)
}

Scriptaculous version

Hi Guys,
I used this script to create a dynamic version with scriptaculous, click here to see the (nice :-) ) result.

download

where do i download the javascript ?

From here

You can download the script from here.

Another taker

doxpop.com is using a derivative of the password strength meter on their subscription page. They too have replaced the verbose output with a slick bar graph. Nicely done Ryan!

And another

Source code license?

Hi Steve, Your script looks very useful, but I was wondering what license it is distributed under, as it isn't explicitly stated in the source code. Is it BSD? GPL? Public domain? Perhaps I'm just not looking in the right place. Thanks.

Re: Source code license

I just added the MIT License to the source code. So feel free to use it however you like within the terms of the license.

Java version with minor regex fixes

Nice job Steve. I made some minor changes to the regex and used your code to derive a Java version. An example jar file with source and instructions can be found at http://justwild.us/examples/password/ Thanks...
Jim

Cool

Thanks Jim. Care to send me a patch with regex fixes for my JavaScript?

new regex for combo

hi... thanks for posting this script... it's really great. it's easy to use and modify for any visual feedback desired. i've changed it slightly and now the combo points for letters and numbers seem to be working for me...

if (passwd.match(/([a-zA-Z])/) && passwd.match(/([0-9])/)) { // both letters and numbers

you might have already changed that... or please let me know if that doesn't work. thanks again!
---
zack
zacksmithdesign.com

Thanks!

Thanks for the code Zack! I've applied your modification and given you credit.

Excellent application that was easy to integrate!

I have always enjoyed the leading edge developments that Google, Amazon, and Yahoo are always trying to introduce to their user experiences. When Google first introduced their password strength meter, I must admit to a touch a jealousy. I instantly wanted something like that on Quty. While looking at the various server-side offerings out there, I wasn't pleased with any of the solutions. It just seemed like they would have presented us with a very awkward integration project. But, thanks to Steve's core code, I was able to exchange the text based prompt messages with an HTML-based graphics thermometer. In less than 36 hours, my applications lab has tested it and installed it on our Registration page. See it at:
https://secure.quty.com/q,d/Register?cmd=user_country&QDD=global Thanks for a great app Steve! -Dick Ervasti
Cofounder / CTO
Quty Global Auction Network

Thanks

Thanks for the kind words Dick. It's cool that you could find this useful.

CSS meter Version

Found this today and put together a CSS version.

Thanks

I took the liberty of putting it together in a file and putting the MIT license on it so people can feel free to use it. I also merged in a bug fix for one of the regex.