PHP: Measuring password strength
In our previous article on Password Validation using regular expressions and HTML5 we focused on client-side JavaScript methods for enforcing strong passwords, but to meet the latest NIST guidelines requires a dictionary check which can really only be done server-side.
NIST Guidelines
The major changes in the guidelines are:
- to do away with what they call 'algorithmic' requirements
(e.g. requiring that a password contains "at least one upper case letter, digit, special character"); - to have the minimum length of at least 8 characters;
- to not require regular password changes; and
- to use a dictionary check.
The logic behind these changes is that the rules we've all been using up to now have failed miserably in coercing users to choose strong passwords.
In fact, passwords created using the old rules are often less secure because a large proportion of users end up settling on some variation of "Amanda1969" or "Password123" as their password.
Fortunately for us there are some easy to use open source command-line tools for checking password strength using dictionary checks and pattern recognition. They will flag passwords which are based on common words, lacking in complexity, or otherwise vulnerable to brute-force attacks.
CrackLib and libpwquality
On Debian GNU/Linux cracklib-check is available in the cracklib-runtime package, and pwscore in the libpwquality-tools package. Something equivalent should be available on other server platforms.
cracklib-check
The cracklib-check script will return either "OK" for a password that meets it's requirements, or a message indicating the problem:
# echo "Amanda1969" | cracklib-check
Amanda1969: OK
# echo "Password123" | cracklib-check
Password123: it is based on a dictionary word
Other possible responses include:
- it is too short;
- it is too simplistic/systematic;
- it does not contain enough DIFFERENT characters;
- ...
pwscore
pwscore works in a similar fashion, but will also assign a score from 0 to 100 representing the password strength, or a reason for failure:
# echo "Amanda1969" | pwscore
53
# echo "Password123" | pwscore
Password quality check failed:
The password fails the dictionary check - it is based on a dictionary word
You can see from the above examples that there is some overlap between the two scripts, but in most cases they agree on whether a password is strong or weak.
One thing you should be aware of is that pwscore does not like palindromes, even if they would otherwise pass based on their complexity.
There are limited configuration options for these scripts, and you may need to find a dictionary to use.
Calling from PHP
The following password_is_vulnerable method appears in our Tools helper class as a static function:
<?PHP
namespace Chirp;
// Original PHP code by Chirp Internet: www.chirpinternet.eu
// Please acknowledge use of this code by including this header.
class Tools
{
public static function password_is_vulnerable($pw, $score = FALSE)
{
$CRACKLIB = "/path/to/cracklib-check";
$PWSCORE = "/path/to/pwscore";
// prevent UTF-8 characters being stripped by escapeshellarg
setlocale(LC_ALL, 'en_US.utf-8');
$out = [];
$ret = NULL;
$command = "echo " . escapeshellarg($pw) . " | {$CRACKLIB}";
exec($command, $out, $ret);
if((0 == $ret) && preg_match("/: ([^:]+)$/", $out[0], $regs)) {
list(, $msg) = $regs;
switch($msg)
{
case "OK":
if($score) {
$command = "echo " . escapeshellarg($pw) . " | {$PWSCORE}";
exec($command, $out, $ret);
if((0 == $ret) && is_numeric($out[1])) {
return (int) $out[1]; // return score
} else {
return FALSE; // probably OK, but may be too short, or a palindrome
}
} else {
return FALSE; // OK
}
break;
default:
$msg = str_replace("dictionary word", "common word, name or pattern", $msg);
return $msg; // not OK - return cracklib message
}
}
return FALSE; // possibly OK
}
}
Usage:
<?PHP
use \Chirp\Tools;
// use just cracklib-check, return FALSE or message
$retval = Tools::password_is_vulnerable($pw);
// as above, but also use pwscore to measure password strength
$retval = Tools::password_is_vulnerable($pw, TRUE);
?>
Example output:
Here you can see some sample output for our function both with and without the $score variable set to TRUE. Note that the final line receives no score because pwscore rejects it as a palindrome. Otherwise all passwords that satisfy cracklib-check are scored:
Password | cracklib-check | pwscore |
---|---|---|
Amanda1969 | bool(false) | int(53) |
ilovemypiano | bool(false) | int(65) |
jellyfi$h | "it is based on a common word, name or pattern" | - |
jelly22fi$h | bool(false) | int(71) |
Password123 | "it is based on a common word, name or pattern" | - |
zaq1zaq1 | "it does not contain enough DIFFERENT characters" | - |
12345678 | "it is too simplistic/systematic" | - |
use longer passwords | bool(false) | int(100) |
theartofwebewfotraeht | bool(false) | bool(false) |
A cracklib-check return value of FALSE indicates that the password is acceptable.
Demonstration
In practice what we've done it wrap our PHP code into an Ajax script with it's own custom JavaScript class for calling and displaying the output in a nice format.
Here you can try it out for yourself:
The UI displays either an error message, or an attractive gradient scale:
You can decide for yourself the minimum password length to use and whether to accept or reject low scoring passwords. Longer and higher-scoring passwords should be encouraged.
References
- NIST Guidelines
- nixCraft: HowTo: Linux Check Password Strength With Cracklib-check Command
- pwscore(1) - Linux man page
Related Articles - Form Validation
- HTML HTML5 Form Validation Examples
- HTML Validating a checkbox with HTML5
- JavaScript Preventing Double Form Submission
- JavaScript Counting words in a text area
- JavaScript Date and Time
- JavaScript Password Validation using regular expressions and HTML5
- JavaScript Tweaking the HTML5 Color Input
- JavaScript Credit Card numbers
- JavaScript A simple modal feedback form with no plugins
- JavaScript Form Validation
- JavaScript Allowing the user to toggle password INPUT visibility
- PHP Measuring password strength
- PHP Protecting forms using a CAPTCHA
- PHP Basic Form Handling in PHP
- PHP Creating a CAPTCHA with no Cookies
Morgan Kinney 28 November, 2022
Hi,
My name is Morgan from Comparitech.
My colleague Stephen has prepared some "crib notes" on NIST's latest password guidelines.
Here's the link - comparite.ch/passwordguide
The NIST guidelines do represent best practices to implement over password security; however, important action points are buried in officious terminology and government procedural formatting which is where our guide comes in.
I hope this is helpful.