Book HomePHP CookbookSearch this book

14.6. Checking Password Strength

14.6.1. Problem

You want to make sure users pick passwords that are hard to guess.

14.6.2. Solution

Test a user's password choice with the pc_passwordcheck( ) function, shown later in Example 14-1. For example:

if ($err = pc_passwordcheck($_REQUEST['username'],$_REQUEST['password'])) {
    print "Bad password: $err";
    // Make the user pick another password
}

14.6.3. Discussion

The pc_passwordcheck( ) function, shown in Example 14-1, performs some tests on user-entered passwords to make sure they are harder to crack. It returns a string describing the problem if the password doesn't meet its criteria. The password must be at least six characters long and must have a mix of uppercase letters, lowercase letters, numerals, and special characters. The password can't contain the username either in regular order or reverse order. Additionally, the password can't contain a dictionary word. The filename for the word list used for dictionary checking is stored in $word_file.

The checks for the username or dictionary words in the password are also applied to a version of the password with letters substituted for lookalike numbers. For example, if the supplied password is w0rd$%, the function also checks the string word$% for the username and dictionary words. The "0" character is turned into an "o." Also, "5" is turned into "s," "3" into "e," and both "1" and "!" into "l" (el).

Example 14-1. pc_passwordcheck( )

function pc_passwordcheck($user,$pass) {
    $word_file = '/usr/share/dict/words';
    
    $lc_pass = strtolower($pass);
    // also check password with numbers or punctuation subbed for letters
    $denum_pass = strtr($lc_pass,'5301!','seoll');
    $lc_user = strtolower($user);

    // the password must be at least six characters
    if (strlen($pass) < 6) {
        return 'The password is too short.';
    }

    // the password can't be the username (or reversed username) 
    if (($lc_pass == $lc_user) || ($lc_pass == strrev($lc_user)) ||
        ($denum_pass == $lc_user) || ($denum_pass == strrev($lc_user))) {
        return 'The password is based on the username.';
    }

    // count how many lowercase, uppercase, and digits are in the password 
    $uc = 0; $lc = 0; $num = 0; $other = 0;
    for ($i = 0, $j = strlen($pass); $i < $j; $i++) {
        $c = substr($pass,$i,1);
        if (preg_match('/^[[:upper:]]$/',$c)) {
            $uc++;
        } elseif (preg_match('/^[[:lower:]]$/',$c)) {
            $lc++;
        } elseif (preg_match('/^[[:digit:]]$/',$c)) {
            $num++;
        } else {
            $other++;
        }
    }

    // the password must have more than two characters of at least 
    // two different kinds 
    $max = $j - 2;
    if ($uc > $max) {
        return "The password has too many upper case characters.";
    }
    if ($lc > $max) {
        return "The password has too many lower case characters.";
    }
    if ($num > $max) {
        return "The password has too many numeral characters.";
    }
    if ($other > $max) {
        return "The password has too many special characters.";
    }

    // the password must not contain a dictionary word 
    if (is_readable($word_file)) {
        if ($fh = fopen($word_file,'r')) {
            $found = false;
            while (! ($found || feof($fh))) {
                $word = preg_quote(trim(strtolower(fgets($fh,1024))),'/');
                if (preg_match("/$word/",$lc_pass) ||
                    preg_match("/$word/",$denum_pass)) {
                    $found = true;
                }
            }
            fclose($fh);
            if ($found) {
                return 'The password is based on a dictionary word.';
            }
        }
    }

    return false;
}

14.6.4. See Also

Helpful password choosing guidelines are available at http://tns.sdsu.edu/security/passwd.html.



Library Navigation Links

Copyright © 2003 O'Reilly & Associates. All rights reserved.