Link to home
Start Free TrialLog in
Avatar of credog
credog

asked on

PHP Password Check for Dictionary Word

I have a simple PHP password application that checks for strength of a password before changing it.  In the link below I am trying to use the part that checks that the password is not a dictionary word.  The password I'm checking doesn't seem like it should be a dictionary word, but I keep getting that it is.  The link is: https://docstore.mik.ua/orelly/webprog/pcook/ch14_06.htm.  

Test password is MzRZ=2wdu;x?NLk

Code snippet I'm using is:

 $word_file= '/usr/share/dict/words';
   $lc_pass = strtolower($newPass);
   $denum_pass = strtr($lc_pass,'5301!','seoll');

   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) {
                $message[] = "Your new password is based on a dictionary word.";
                return false;
            }
        }
    }

Open in new window

SOLUTION
Avatar of gr8gonzo
gr8gonzo
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of credog
credog

ASKER

Inserted the empty line and still seems to think it is a dictionary work.  Thanks
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of credog

ASKER

Thank you.  The file looks bigger than 250K.  Should I still try your suggestions?

Just for clarification, the /usr/share/dict/words word list is the standard one in a Redhat 6.9 system and looks to be about 4.8 M

$ ls -lh  /usr/share/dict/words
lrwxrwxrwx. 1 root root 11 Dec  3  2013 /usr/share/dict/words -> linux.words

# ls -lh  /usr/share/dict/linux.words
-rw-r--r--. 1 root root 4.8M Jan  7  2010 /usr/share/dict/linux.words
Hmmm, if it's that big, then it's debatable. I suppose it depends on how much memory you have overall and how much PHP processes are allowed to allocate. I think the default memory limit for PHP processes is either 8 or 16 megabytes (which is pretty low in my opinion). So an array containing 4.8 megabytes of strings would likely eat up most of an 8 megabyte limit. However, if you have lots of memory in your system and PHP can allocate 16 or more megs, then it's probably not an issue.
If it were me, I might have a separate process read the words file at night and then split it into 1 megabyte-sized files  that you loop through. All depends on your end goal.
Avatar of credog

ASKER

I guess my first goal is to just try to get this routine to not think that random password is a dictionary word.  So, how should I proceed since the if(empty($word)) { continue; } didn't seem to help the issue.  Should I just try your code on a test basis and if it works and  think about how to read the file (i.e. split it up, memory, etc. ) once it works.   I don't do this a lot so I'm I little confused why it's thinks that it is a dictionary word.
Well, my latest code also records which specific word was "found" in your password, so try what I gave you and see what the output is.
Avatar of credog

ASKER

I see what was causing the issue.  This word list has single characters in it for some reason, so if the password has a "f" in it it for example it will get caught as a dictionary word.  I took the word list and made a new one out of it that has 3 or more characters and it seems to be working better now.

I ran the original code and it took 5.6 seconds against the 3 character or more word list.  Your (gr8gonzo) code took .6 seconds on the same word list.  Pretty impressive.   I'm testing this on virtual machine at home, but will test on the real system tomorrow.

Instead of parsing out the word list and creating a new one, can your code be modified to only consider words in the list that are at least  2/3 characters (still trying to decide on how many) . That way I can keep the system as is without modifying the default word list.  

Also, by the way, your original suggestion of adding if(empty($word)) { continue; } to the original code ended up needing to be included in the original code.   The reason it appeared to not have an effect originally was that the program was hitting the single character before it got to the end of the file where it was needed.
ASKER CERTIFIED SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of credog

ASKER

gr8gonzo - I believe I correctly implemented your suggestions.  If you could verify I'd appreciate it.  The changes got it down to about .5 seconds and it appears to be working correctly.
 
if (is_readable($word_file)) {
        $lc_pass = strtolower($newPassword);
        $denum_pass = strtr($lc_pass,'5301!','seoll');

        $alphaCharsInPassword1 = strlen(preg_replace("@[^a-z]@","",$lc_pass));
        $alphaCharsInPassword2 = strlen(preg_replace("@[^a-z]@","",$denum_pass));
        $maxWordLength = max($alphaCharsInPassword1, $alphaCharsInPassword2);

        $words = file($word_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
        $found = false;
        foreach($words as $word) {
                $word = trim(strtolower($word));
                if(strlen($word) < 4) { continue; }
                if(strlen($word) > $maxWordLength) { continue; }
                $found = ( (stripos($lc_pass,$word) !== false) || (stripos($denum_pass,$word) !== false) );
                        if($found) {
                                $foundMatch = $word;
                                break;
                        }
        }

        if ($found) {
               return "Your new password is based on a dictionary word: [{$foundMatch}]";
                 return false;
         }
 }

Open in new window

Looks good