| How secure is your password? | |
In this tutorial we create a checker to help educate users about the strength of their passsword.
As the user types in hir or her password, a small meter will tell them how secure it is. Google and others have long since introduced systems similar to this.
This tutorial will show how you can create a password-strength UI control in JavaScript which will show a user a measure of how secure his or her password is.
Before we can do this, we need to work out some methodology for answering "how good is a password?". There are several metrics that we can very easily check in JavaScript:
How are we going to work out which metric is most important? Should the password be long, or just complicated?
Instead of trying to work out which is best, we're going to approach the question from the viewport of a hacker trying to access your account. For the sake of simplicity we shall assume that the hacker has already ascertained how long your password is. How long would it take him to guess your password using a brute-force approach, going through every possible permutation of characters until granted access?
Let's use an example. Say that you have a password that is 5 letters long, and that each of them is a lower case letter, so each letter may be one among only 26 possibilities. If our hacker knows this, he would have to try:
26 * 26 * 26 * 26 * 26
= 26 ^ 5
= 11,881,376 combinations
If the hacker starts trying passwords at a rate of 200 a second, say, he would crack your website in 59,406 seconds which is less than one day. This assumes, of course, that your password is the very last one he tries: on average, he'll find your password in half that time. This means that you would do well to change your password it every few hours.
Let's see what happens if the password also contained upper case letters. In this case there are 52 different
letters that could be used in each of the 5 positions. This time the hacker would need to try:
52 * 52 * 52 * 52 * 52
= 380,204,032 combinations
380 million combinations: this is much better. The hacker would now (on average) take 11 days to decrypt the password, at the same rate of 200 attempts per second. So you can keep your password for a good week. W00t.
Most people, however, find passwords inconvenient enough as it is and prefer to use the same password for months or years on end. Assuming this is the case, we'll define a define a "strong" password as one that will take longer than a year to crack. If the hacker does dedicate an entire year of computer time trying out passwords (and your server lets him) and does eventually break in, fair play to him.
The first step in the implementation is to inspect the password entered by the user and check for the presence of:
We can detect the presence of the above using regular expressions, which JavaScript supports nicely. We'll capture it all in a configuration object so each regular expression is mapped to the number of unique characters it scans for:
PasswordValidator.prototype.expressions = [
{
regex : /[A-Z]+/,
uniqueChars : 26
},
{
regex : /[a-z]+/,
uniqueChars : 26
},
{
regex : /[0-9]+/,
uniqueChars : 10
},
{
regex : /[!.@$£#*()%~<>{}\[\]]+/,
uniqueChars : 17
}
];
We'll also use variables for our estimates about the productivity of our hacker, and the laziness of our user. All stuck here on the prototype for easy documentation.
/** * How long a password can be expected to last */ PasswordValidator.prototype.passwordLifeTimeInDays = 365; /** * An estimate of how many attempts could be made per second to guess a password */ PasswordValidator.prototype.passwordAttemptsPerSecond = 500;
Now, given a password, we can simply loop over the array, and check if each regular expression matches one or more letters in the password. If so, we increment the possibilitiesPerLetterInPassword variable by the number of unique characters associated with the regex.
/** * Checks the supplied password * @param {String} password * @return The predicted lifetime of the password, as a percentage of the defined password lifetime. */ PasswordValidator.prototype.checkPassword = function(password) { var expressions = this.expressions, i, l = expressions.length, expression, possibilitiesPerLetterInPassword = 0; for (i = 0; i < l; i++) { expression = expressions[i]; if (expression.regex.exec(password)) { possibilitiesPerLetterInPassword += expression.uniqueChars; } }
We then do the small piece of maths explained above
var
totalCombinations = Math.pow(possibilitiesPerLetterInPassword, password.length),
// how long, on average, it would take to crack this
crackTime = ((totalCombinations / this.passwordAttemptsPerSecond) / 2) / secondsInADay,
// how close is the time to the expected lifetime?
percentage = crackTime / this.passwordLifeTimeInDays;
return Math.min(Math.max(password.length * 5, percentage * 100), 100);
};
You'll notice that the final statement provides a minimal percentage for any password, defined as 5% per letter. Because the calculation is exponential in nature, the first 3 or 4 characters probably won't register above 1%. However, we would still like to provide some visual feedback to the user, which will encourage them to keep typing.
Having created a function to perform the logic, we can now construct a view to display the user how inspired or hopeless their password is. Essentially we'll create a mini progress bar, which shows the percentage calculated above, and which changes colour at different intervals.
To wrap this nicely in to reusable code, we'll make use of the lovely jQuery library, which you may already be using on your site. Note here I use the jQuery namespace rather than $. There is no difference between the two as they are the same object (jQuery === $); I prefer to use jQuery as it is more obvious what you're using.
The updatePassword function (below) calls the Password Validator, and updates the style of the progress bar, by changing the width of the strength bar and choosing a CSS class to change its colour. Instead of changing the width directly, we use jQuery's animate function to animate the width. This makes the widget feel a lot smoother. In case the previous animation hasn't completed before the next one starts, we call the stop() function to finish it.
/** * jQuery plugin which allows you to add password validation to any * form element. */ function(IW, jQuery) { function updatePassword() { var percentage = IW.PasswordValidator.checkPassword(this.val()), progressBar = this.parent().find(".passwordStrengthBar div"); progressBar .removeClass("strong medium weak useless") .stop() .animate({"width": percentage + "%"}); if (percentage > 90) { progressBar.addClass("strong"); } else if (percentage > 50) { progressBar.addClass("medium") } else if (percentage > 10) { progressBar.addClass("weak"); } else { progressBar.addClass("useless"); } }
The jQuery extension binds this function to the keyup event of the element, and also adds the password strength markup immediately after. With the CSS rules I've written, the bar appears directly below the input box.
We'll write a simple plugin, which you can do simply by appending your custom function onto jQuery.fn. Easy.
jQuery.fn.passwordValidate = function() {
this
.bind('keyup', jQuery.proxy(updatePassword, this))
.after("<div class='passwordStrengthBar'>" +
"<div></div>" +
"</div>");
updatePassword.apply(this);
return this; // for chaining
}
})(IW, jQuery);
Now we have everything prepared, applying validation to any element on your page is childishly simple. The following call will turn all password boxes on your page into password strength validators.
jQuery("input[type='password']").passwordValidate();
And that's it!
This code will not protect your password against dictionary attacks. That is, if the user types a reasonably common word, the hacker will look for that first and find it in moments. However, this form of validation is one for the server, not the client.
Our scenario assumes that your server is exceptionally permissive, and allows people to endlessly attack your authentication system. In practice you would also hope your server admin installs a system such as Fail2Ban, which blocks IP addresses that repeatedly fail to access your system.
It is tempting to make the password validation rules extremely strict in order to guarantee unbreakable passwords. However, as the security rules become more draconian, actual security may decrease. If the password are so hard that a normal human being can't possibly remember it, they might well do the natural thing and stick a PostIt reminder onto their monitor, and then all the rest of your security apparatus will be in vain!
