14.7.1 Problem
14.7.2 Solution
Generate a new password and send it to the user's email address
(which you should have on file):
// generate new password
$new_password = '';
$i = 8;
while ($i--) { $new_password .= chr(mt_rand(33,126)); }
// encrypt new password
$encrypted_password = crypt($new_password);
// save new encrypted password to the database
$dbh->query('UPDATE users SET password = ? WHERE username = ?',
array($encrypted_password,$username));
// email new plaintext password to user
mail($email,"New Password","Your new password is $new_password");
14.7.3 Discussion
If a user forgets his password, and you store encrypted
passwords as recommended in Section 14.5, you can't provide the forgotten password. The one-way nature of
crypt( ) prevents you from retrieving the unencrypted password.
Instead, generate a new password and send that to his
preexisting contact address. If you send the new password to an address you
don't already have on file for that user, you don't have a way to verify that
the new address really belongs to the user. It may be an attacker attempting to
impersonate the real user.
Because the email containing the new password isn't encrypted,
the code in the Solution doesn't include the username in the email message to
reduce the chances that an attacker that eavesdrops on the email message can
steal the password. To avoid disclosing a new password
by email at all, let a user authenticate himself without a password by answering
one or more personal questions (the answers to which you have on file). These
questions can be "What was the name of your first pet?" or "What's your mother's
maiden name?" — anything a malicious attacker is unlikely to know. If the user
provides the correct answers to your questions, you can let him choose a new
password.
One way to compromise between security and readability is to
generate a password for a user out of actual words interrupted by some numbers.
$words =
array('dished','mother','basset','detain','sudden','fellow','logged','sonora',
'earths','remove','dustin','snails','direct','serves','daring','cretan',
'chirps','reward','snakes','mchugh','uphold','wiring','gaston','nurses',
'regent','ornate','dogmas','singed','mended','hinges','latent','verbal',
'grimes','ritual','drying','hobbes','chests','newark','sourer','rumple');
mt_srand((double) microtime() * 1000000);
$word_count = count($words);
$password = sprintf('%s%02d%s',
$words[mt_rand(0,$word_count - 1)],
mt_rand(0,99),
$words[mt_rand(0,$word_count - 1)]);
print $password;
This code produces passwords that are two six-letter words with
two numbers between them, like mother43hinges or
verbal08chirps. The passwords are long, but remembering them is made
easier by the words in them.