📖 PHP Password Encryption
Using passwords to authenticate visitors is a long standing method for managing site access. While newer methods are being used to improve site security, passwords remain a staple in user authentication. Site hacks can result in the exposure of user passwords to malicious actors. It is now considered a standard of care to encrypt user passwords for database storage so they cannot be used if the database is breached and the passwords are exposed.
There are a variety of ways to encrypt passwords and you will find many discussions about which way is best or if using passwords in today's environment is even good. Many large corporations, banks and government sites have been breached, but we are not trying to protect the US Treasury. We just want to try to keep our site relatively secure from "honest" people. If someone is determined to hack our site, they might well do it.
Security is a layered approached. First, we are not going to ask for more information than we need from the user. Second, we are going to encourage our members to use improved passwords and we are going to provide basic account authentication in an attempt to keep control of access to site resources. Part of our site security should also be keeping regular backups of the site files and database. In this article, we are going to talk about how to obfuscate member messages and passwords using encryption.
So for this beginner lesson on password security, let's focus on 3 things.
- Secure Connection
- Password Encryption
- Password Strength
Secured Connections
When you visit a web site, you type in the URL of a site resource (web page) and the server responds to the http request by "serving" the page back to the client. In the early days of the internet we had mostly static pages with low threat of security issues. With the growth of the WWW, web traffic spans the globe. Your request for your friend's social media can now go through a variety of countries to get to you. Those countries are out of the control of the US which makes it easier for bad actors to intercept your communication. Compound that with all the personal and financial transactions taking place, and an open internet with readable messages is no longer safe.
Web sites are now improving the transfer of your internet data by encrypting the messages between the servers using special keys (security certificates) to allow the sender and receiver only to read the message. Always look for the https in the URL. This tells you the web site uses secure http with an authenticated site security certificate. This helps tremendously for active web traffic, but does little for the information stored on the server in the database. That's where password encryption comes in.
Password Encryption
A similar method of encryption can be used to encode passwords for storage on the server to prevent anyone from being able to read the passwords if they get access to the database tables. Encryption takes the readable user password like 'fluffythecat2401' and converts it to a random string of letters and numbers. This is called a hash. There are a number of different encryption methods. SHA1 and MD5 have been used for years to authenticate files downloaded from the internet. An MD5 hash of 'fluffythecat2401' looks like 69d618735161f4d32c2ebc55bb7cc93e. So, if your password is fluffythecat2401 and someone steals your account information that uses MD5 encryption, they will only see 69d618735161f4d32c2ebc55bb7cc93e. This is called obfuscation.
Now, you cannot use 69d618735161f4d32c2ebc55bb7cc93e in a login form for the password 'fluffythecat2401'. The program will hash the login value to see if it matches what is stored on the server. The only way to get a match to 69d618735161f4d32c2ebc55bb7cc93e is to put 'fluffythecat2401' through the same encryption hash as was used originally. Encryption renders the passwords unreadable.
Modifying the Database
The process for encrypting passwords is fairly straight forward. Get the password from the user entered form field and hash it. Then store it in the database. PHP developer docs recommends using a varchar(255) datatype for the database. I would recommend using PHPMyAdmin to modify the database. There are tools in PHPMyAdmin for changing the database column values or you can copy and paste the code below into the SQL window of the membership table.
ALTER TABLE `membership` MODIFY `password` varchar(255);
Encrypting Passwords
Below is an example of how to do this using a stronger encryption hash compatible with crypt(). You can find an explanation in the PHP.net manual. It is designed to be updated to ever changing improvements in encryption methods and can vary in length. The example below is for updating the original password. Saving the password from the user registration is similar, but the database command would be INSERT instead of UPDATE.
Password Hashing Example
// encrypt the password
$password = password_hash($password, PASSWORD_DEFAULT);
// write information to database
$stmt = $conn->stmt_init();
if ($stmt->prepare("Update `membership` SET `password` = ? WHERE `memberID` = ?")) {
$stmt->bind_param("si", $password, $memberID);
$stmt->execute();
$stmt->close();
}
Form Process Example
You might see some new looking code here. Since we are improving the security of the data, I'm using the newer PHP filter methods, filter_has_var and filter_input, to extract form data. You will also see I'm using prepared statements to interact with the database. These methods are not necessary for including encryption, but are another level of security.
if (filter_has_var(INPUT_POST, 'login')) {
//Get the information from form
$username = strip_tags(filter_input(INPUT_POST, 'username'));
$passwordSubmit = trim(filter_input(INPUT_POST, 'password'));
$valid = TRUE;
if ($username == NULL) {
//If a field is empty, set an error message for that field and load the form
$invalid_user = '<span class="error">Required field</span>';
$valid = FALSE;
}
if ($passwordSubmit == NULL) {
//If a field is empty, set an error message for that field and load the form
$invalid_password = '<span class="error">Required field</span>';
$valid = FALSE;
}
}
Password Authentication
There is a lot going on in the next code example. It is processing the user input for username and password. It first looks up the username in the database to pull the stored encrypted password. Then it checks the user entered password against the encrypted password to see if it is a match. If the passwords match, the record is pulled from the database and assigned to $_SESSION variables. You may want to also create a $_COOKIE or redirect the user at this time.
if ($valid) { // if the form data are valid
$stmt = $conn->stmt_init(); // create the database connection
if ($stmt->prepare("SELECT `memberID`, `password` FROM `membership` WHERE `username` = ?")) { // prepare the db query
$stmt->bind_param("s", $username); // lookup this user
$stmt->execute();
$stmt->store_result();
$stmt->bind_result($memberID, $password); // bind the stored password from the db record to a variable
$stmt->fetch();
$stmt->free_result();
$stmt->close();
} else {
$msg = <<<HERE
<h3 class="error">We could not find you in the system.
New users must register before gaining access to the site.
If you forgot your login, please use the Password Recover tool.</h3>
HERE;
}
if (password_verify($passwordSubmit, $password)) { // checks submitted password against stored password for a match
$stmt = $conn->stmt_init();
if ($stmt->prepare("SELECT `firstname`, `lastname`, `email` FROM `membership` WHERE `memberID` = ?")) {
$stmt->bind_param("i", $memberID);
$stmt->execute();
$stmt->store_result();
$stmt->bind_result($firstname, $lastname, $email); // get authenticated member record
if($stmt->num_rows == 1){
$stmt->fetch();
// $_SESSION['memberID'] = $memberID;
// setcookie("firstname", $firstname, time()+(3600*3));
header("Location: profile.php?memberID=$memberID&msg=You are logged in.");
exit;
} else {
$msg = <<<HERE
<h3 class="error">We could not access the login records.</h3>
HERE;
}
$stmt->close();
} else {
$msg = <<<HERE
<h3 class="error">We could not find your information.</h3>
HERE;
}
} else {
$msg = <<<HERE
<h3 class="error">We could not find you in the system.
New users must register before gaining access to the site.
If you forgot your login, please use the Password Recover tool.</h3>
HERE;
}
}
Password Strength
One of the biggest security vulnerabilities in database-driven dynamic web sites is the user. Users have a tendency to create the easiest passwords they can think of. I've seen users literally use 'password' as their password. Not hard to guess that one. It is important for you to have a strategy for dealing with stengthening user passwords.
Strong Password Pattern Matching
While PHP can employ regular expressions to test passwords to match a desired pattern, it can't do so until the user submits the password to the server for processing. Like checking form fields, this pattern matching can be done in the browser as well as on the server. Form controls can now incorporate various filters for user input and can include pattern matching, too. Including this pattern matching in the form, as well as on the server, might keep your user's from becoming frustrated with a form that just keeps coming back due to password policy violations.
Let's say we want our users to have a password with at least 1 uppercase letter, 1 lowercase letter, 1 number and a minimum size of 8 characters. We can create the pattern matching we want to accomplish using a regular expression and then incorporate it into our form field and the PHP process.
Example
<!-- Add Pattern Matching to the password field in the HTML form -->
<label><span class="error">*</span> Password: </label>
<input type="password" name="Password" pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}"
title="Must contain at least one number and
one uppercase and lowercase letter, and at least 8 or more characters"
placeholder="Password" required />
/* PHP process form input */
if (!preg_match('/(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}/',$password)){
$password_error = "Must contain at least one number and
one uppercase and lowercase letter, and at least 8 or more characters";
$valid = FALSE;
}
Using JavaScript for Validating and Matching Passwords
You can use the same pattern for matching with PHP on the server. While both of these serve to give you twice the chance of catching weak passwords, you can also incorporate JavaScript to do the pattern matching and compare two passwords for a match. This is done because password fields are hidden from view. In order to ensure the user typed the password they wanted, you have them type it twice and then compare them.
HTML Form
<!-- Form input field -->
<label><span class="error">*</span> Password:</label>
<input type="password" id="regPassword1" name="Password" pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}"
title="Must contain at least one number and one uppercase and lowercase letter, and at least 8 or more characters"
placeholder="Password" onkeyup="strongPW()" required />
<span class="error">$invalid_Password</span>
<label class="registrationLabel"><span class="error">*</span> Re-type Password: </label>
<input type="password" id="regPassword2" name="Password2" placeholder="Confirm Password" onkeyup="checkPW()" required />
<span id="match"></span>
<p>Password must contain these characters:</p>
<p id="capital" class="invalid">An <b>uppercase</b> letter</p>
<p id="letter" class="invalid">A <b>lowercase</b> letter</p>
<p id="number" class="invalid">A <b>number</b></p>
<p id="length" class="invalid">Minimum <b>8 characters</b></p>
CSS
/* CSS to style form control */
/* Add a green text color and a checkmark when the requirements are right */
.valid {
color: green;
}
.valid:before {
position: relative;
left: -35px;
content: url(/templates/css/images/checkmark_24.png);
}
/* Add a red text color and an "x" icon when the requirements are wrong */
.invalid {
color: red;
}
.invalid:before {
position: relative;
left: -35px;
content: url(/templates/css/images/redx_24.png);
}
JavaScript Code
// validate original password
// When the user starts to type something inside the password field
function strongPW() {
var myInput = document.getElementById("regPassword1");
var letter = document.getElementById("letter");
var capital = document.getElementById("capital");
var number = document.getElementById("number");
var length = document.getElementById("length");
// Validate lowercase letters
var lowerCaseLetters = /[a-z]/g;
if(myInput.value.match(lowerCaseLetters)) {
letter.classList.remove("invalid");
letter.classList.add("valid");
} else {
letter.classList.remove("valid");
letter.classList.add("invalid");
}
// Validate capital letters
var upperCaseLetters = /[A-Z]/g;
if(myInput.value.match(upperCaseLetters)) {
capital.classList.remove("invalid");
capital.classList.add("valid");
} else {
capital.classList.remove("valid");
capital.classList.add("invalid");
}
// Validate numbers
var numbers = /[0-9]/g;
if(myInput.value.match(numbers)) {
number.classList.remove("invalid");
number.classList.add("valid");
} else {
number.classList.remove("valid");
number.classList.add("invalid");
}
// Validate length
if(myInput.value.length >= 8) {
length.classList.remove("invalid");
length.classList.add("valid");
} else {
length.classList.remove("valid");
length.classList.add("invalid");
}
}
//validate password match
function checkPW() {
var regPassword1 = document.getElementById("regPassword1");
var regPassword2 = document.getElementById("regPassword2");
if(regPassword1.value != regPassword2.value) {
match.style.background = "red";
match.style.color = "white";
document.getElementById("match").innerHTML = "No Match";
} else {
match.style.background = "green";
match.style.color = "white";
document.getElementById("match").innerHTML = "Match";
}
}