What is the best way of ensuring that a user supplied password is a strong password in a registration or change password form?
One idea I had (in python)
<
If you have the time, run a password cracker against it.
With a series of checks to ensure it meets minimum criteria:
Here's a jQuery plugin that reports password strength (not tried it myself): http://phiras.wordpress.com/2007/04/08/password-strength-meter-a-jquery-plugin/
And the same thing ported to PHP: http://www.alixaxel.com/wordpress/2007/06/09/php-password-strength-algorithm/
Cracklib is great, and in newer packages there is a Python module available for it. However, on systems that don't yet have it, such as CentOS 5, I've written a ctypes wrapper for the system cryptlib. This would also work on a system that you can't install python-libcrypt. It does require python with ctypes available, so for CentOS 5 you have to install and use the python26 package.
It also has the advantage that it can take the username and check for passwords that contain it or are substantially similar, like the libcrypt "FascistGecos" function but without requiring the user to exist in /etc/passwd.
My ctypescracklib library is available on github
Some example uses:
>>> FascistCheck('jafo1234', 'jafo')
'it is based on your username'
>>> FascistCheck('myofaj123', 'jafo')
'it is based on your username'
>>> FascistCheck('jxayfoxo', 'jafo')
'it is too similar to your username'
>>> FascistCheck('cretse')
'it is based on a dictionary word'
The object-oriented approach would be a set of rules. Assign a weight to each rule and iterate through them. In psuedo-code:
abstract class Rule {
float weight;
float calculateScore( string password );
}
Calculating the total score:
float getPasswordStrength( string password ) {
float totalWeight = 0.0f;
float totalScore = 0.0f;
foreach ( rule in rules ) {
totalWeight += weight;
totalScore += rule.calculateScore( password ) * rule.weight;
}
return (totalScore / totalWeight) / rules.count;
}
An example rule algorithm, based on number of character classes present:
float calculateScore( string password ) {
float score = 0.0f;
// NUMBER_CLASS is a constant char array { '0', '1', '2', ... }
if ( password.contains( NUMBER_CLASS ) )
score += 1.0f;
if ( password.contains( UPPERCASE_CLASS ) )
score += 1.0f;
if ( password.contains( LOWERCASE_CLASS ) )
score += 1.0f;
// Sub rule as private method
if ( containsPunctuation( password ) )
score += 1.0f;
return score / 4.0f;
}
Well this is what I use:
var getStrength = function (passwd) {
intScore = 0;
intScore = (intScore + passwd.length);
if (passwd.match(/[a-z]/)) {
intScore = (intScore + 1);
}
if (passwd.match(/[A-Z]/)) {
intScore = (intScore + 5);
}
if (passwd.match(/\d+/)) {
intScore = (intScore + 5);
}
if (passwd.match(/(\d.*\d)/)) {
intScore = (intScore + 5);
}
if (passwd.match(/[!,@#$%^&*?_~]/)) {
intScore = (intScore + 5);
}
if (passwd.match(/([!,@#$%^&*?_~].*[!,@#$%^&*?_~])/)) {
intScore = (intScore + 5);
}
if (passwd.match(/[a-z]/) && passwd.match(/[A-Z]/)) {
intScore = (intScore + 2);
}
if (passwd.match(/\d/) && passwd.match(/\D/)) {
intScore = (intScore + 2);
}
if (passwd.match(/[a-z]/) && passwd.match(/[A-Z]/) && passwd.match(/\d/) && passwd.match(/[!,@#$%^&*?_~]/)) {
intScore = (intScore + 2);
}
return intScore;
}
In addition to the standard approach of mixing alpha,numeric and symbols, I noticed when I registered with MyOpenId last week, the password checker tells you if your password is based on a dictionary word, even if you add numbers or replace alphas with similar numbers (using zero instead of 'o', '1' instead of 'i', etc.).
I was quite impressed.