Secure passwords
When I first set up this blog, I used a simple password storage mechanism that really isn't very safe; it simply hid the username & password combination behind a folder hidden by .htaccess
This isn't really much protection at all, as .htaccess can fail during, for instance, a software upgrade on your web server. A better way to store it would be to put it in a MySQL database, however a better way still would be to not store the password at all.
Instead of storing the password, I store a hash of the password, and check the entered password against that.
Here's how it's done:
When a new user account is created, their username is stored along with a hash of the password like so:
$Config_Global["Users"][$username] = password_hash($password, PASSWORD_DEFAULT);
This is saved to a file hidden within a .htaccess protected directory on the server.
Now, when that same user enters his username and password to login, the following code gets processed:
password_verify($pass,$Config_Global["Users"][$user])
This simply returns boolean TRUE or FALSE, and if TRUE, then I set a $_SESSION variable which permits access to the admin area.
This does present a new problem; because the password is not stored anywhere, if the user forgets it, there is no way to send it to him. Instead, we need to create a way for the user to request a password reset.
I do this by using the code below:
if ($_GET['reset']=='send') {
$message = "<a href='http://$ScriptDomain/admin/admin.php?reset=confirm&key=" . md5($Config_Global['account_email'] . $_SERVER['REMOTE_ADDR'])."'>Click to reset password</a>n";
mail($Config_Global['account_email'], $ScriptDomain.' Password Reset', $_SERVER['REMOTE_ADDR'] . "nn" . $message, "From: noreply@$ScriptDomain");
echo "Password reset link has been emailed<br>n";
} elseif ($_GET['reset']=='confirm') {
if ($_GET['key'] == md5($Config_Global['account_email'].$_SERVER['REMOTE_ADDR'])) {
if (isset($_SERVER["REQUEST_METHOD"]) && $_SERVER["REQUEST_METHOD"] == "POST") {
$password = (preg_match('/^[A-z0-9!@#$%^&*()_-+|;:<>,.?]+$/', $_POST['password'])) ? $_POST['password'] : '';
$confirm_password = (preg_match('/^[A-z0-9!@#$%^&*()_-+|;:<>,.?]+$/', $_POST['confirm_password'])) ? $_POST['confirm_password'] : '';
if (!empty($password) && $password == $confirm_password) {
$Config_Global["Users"][key($Config_Global["Users"])]=password_hash($password, PASSWORD_DEFAULT);
file_write_flock("data/config_global.dat" , serialize($Config_Global));
echo "Password has been reset. You may now <a href='admin.php'>login</a> using the new password.<br>n";
} else echo "Error updating password, invalid characters or password mismatch<br>n";
} else {
echo "<form method="POST" action="admin.php?reset=confirm&key=" . md5($Config_Global['account_email'] . $_SERVER['REMOTE_ADDR'])."">";
echo "New Password: <input type="password" name="password"><BR>";
echo "Confirm Password: <input type="password" name="confirm_password"><BR>";
echo "<input type="submit" value="Reset">";
echo "</form>";
}
} else {
echo "Invalid key, or your IP address has changed since requesting the reset email.<br>n";
}
} else {
// put the login prompt here
echo "<form method="POST" action="admin.php">";
echo "Username: <input type="text" name="username"><BR>";
echo "Password: <input type="password" name="password"><BR>";
echo "<input type="submit" value="Log In">";
echo "</form>";
echo "<a href='admin.php?reset=send'>Reset Password</a><br>n";
}
What this does is offer a password reset link, which if clicked, generates an email containing a unique link with a key that is the md5 sum of the email address and the user's IP address. When the user receives the email, he clicks the link which takes him back to the site along with that key. The script then generates another md5 hash of his email address and IP, checks it against the key he presented, and if they match he is then allowed to change the password.
In this way, only a user with the same IP address as the requester, who is also in control of that email account, may change the password. The downside is that anyone with access to that email account may change the password, so it shouldn't be a shared email account.
Eventually I may add an extra layer of security to this login procedure by keeping track of the IP address of attempted logins/password resets, and if that IP has accessed it more than X times in Y minutes, I could then block the IP temporarily, thus thwarting brute force attacks.
Category: Cms