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:
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;
}
}
}
SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
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.word s
-rw-r--r--. 1 root root 4.8M Jan 7 2010 /usr/share/dict/linux.word s
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.word
-rw-r--r--. 1 root root 4.8M Jan 7 2010 /usr/share/dict/linux.word
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.
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.
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.
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
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
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;
}
}
Looks good
ASKER