What is password hashing?
It turns a string (of any length) to a fixed length "fingerprint" that cannot be reversed. For example, my password is "i1love2coding3", when hashed, it can be converted to a 60 character "ytwqwxpbx1oxbfvmpoaafckmat2zkdsjaxs..." which will be stored to the database.
Why do we have to hash passwords?
I think the main reason why we have to hash passwords is to prevent passwords from being stolen or compromised.
I know that some PHP frameworks or CMS already provide this functionality, but I believe that it is important for us to know how its implementation can be made.
Saved password was salted and hashed. |
We are going to use a Portable PHP Password Hashing Framework called phpass (pronounced "pH pass") recommended by a lot of forums and is used by some famous Web applications like phpBB3, WordPress, Drupal, Vanilla, etc.
This post will focus and provide you a quick grasp and basic idea on how to salt, hash and store passwords in a MySQL database. This is essential to your PHP login script.
Let's Code
Our SQL table structure looks like this:CREATE TABLE IF NOT EXISTS `users` ( `id` int(11) NOT NULL AUTO_INCREMENT, `email` varchar(32) NOT NULL, `password` char(60) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
libs/PasswordHash.php - our password framework file, yes, it is just this one file. You can download it here.
libs/DbConnect.php - configuration to be connected to database.
register.php - The user registration page, this is where we are going to save the user's password. On this example web app, we require these two fields only during registration.
<html> <head> <title>registration page - php salt and hash password - www.codeofaninja.com</title> <link type="text/css" rel="stylesheet" href="css/style.css" /> </head> <body> <div id="loginForm"> <?php // save the username and password if($_POST){ try{ // load database connection and password hasher library require 'libs/DbConnect.php'; require 'libs/PasswordHash.php'; /* * -prepare password to be saved * -concatinate the salt and entered password */ $salt = "ipDaloveyBuohgGTZwcodeRJ1avofZ7HbZjzJbanDS8gtoninjaYj48CW";
$password = $salt . $_POST['password']; /* * '8' - base-2 logarithm of the iteration count used for password stretching * 'false' - do we require the hashes to be portable to older systems (less secure)? */ $hasher = new PasswordHash(8,false); $password = $hasher->HashPassword($password); // insert command $query = "INSERT INTO users SET email = ?, password = ?"; $stmt = $con->prepare($query); $stmt->bindParam(1, $_POST['email']); $stmt->bindParam(2, $password); // execute the query if($stmt->execute()){ echo "<div>Successful registration.</div>"; }else{ echo "<div>Unable to register. <a href='register.php'>Please try again.</a></div>"; } } //to handle error catch(PDOException $exception){ echo "Error: " . $exception->getMessage(); } } // show the registration form else{ ?> <!-- -where the user will enter his email and password -required during registration -we are using HTML5 'email' type, 'required' keyword for a some validation, and a 'placeholder' for better UI --> <form action="register.php" method="post"> <div id="formHeader">Registration Form</div> <div id="formBody"> <div class="formField"> <input type="email" name="email" required placeholder="Email" /> </div> <div class="formField"> <input type="password" name="password" required placeholder="Password" /> </div> <div> <input type="submit" value="Register" class="customButton" /> </div> <div id='userNotes'> Already have an account? <a href='login.php'>Login</a> </div> </div> </form> <?php } ?> </div> </body> </html>
login.php - the user login page, we are going to check if the users's password is valid or not .
<html> <head> <title>login page - php salt and hash password - www.codeofaninja.com</title> <link type="text/css" rel="stylesheet" href="css/style.css" /> </head> <body> <div id="loginForm"> <?php // form is submitted, check if acess will be granted if($_POST){ try{ // load database connection and password hasher library require 'libs/DbConnect.php'; require 'libs/PasswordHash.php'; // prepare query $query = "select email, password from users where email = ? limit 0,1"; $stmt = $con->prepare( $query ); // this will represent the first question mark $stmt->bindParam(1, $_POST['email']); // execute our query $stmt->execute(); // count the rows returned $num = $stmt->rowCount(); if($num==1){ //store retrieved row to a 'row' variable $row = $stmt->fetch(PDO::FETCH_ASSOC); // hashed password saved in the database $storedPassword = $row['password']; // salt and entered password by the user $salt = "ipDaloveyBuohgGTZwcodeRJ1avofZ7HbZjzJbanDS8gtoninjaYj48CW"; $postedPassword = $_POST['password']; $saltedPostedPassword = $salt . $postedPassword; // instantiate PasswordHash to check if it is a valid password $hasher = new PasswordHash(8,false); $check = $hasher->CheckPassword($saltedPostedPassword, $storedPassword); /* * access granted, for the next steps, * you may use my php login script with php sessions tutorial :) */ if($check){ echo "<div>Access granted.</div>"; } // $check variable is false, access denied. else{ echo "<div>Access denied. <a href='login.php'>Back.</a></div>"; } } // no rows returned, access denied else{ echo "<div>Access denied. <a href='login.php'>Back.</a></div>"; } } //to handle error catch(PDOException $exception){ echo "Error: " . $exception->getMessage(); } } // show the registration form else{ ?> <!-- -where the user will enter his email and password -required during login -we are using HTML5 'email' type, 'required' keyword for a some validation, and a 'placeholder' for better UI --> <form action="login.php" method="post"> <div id="formHeader">Website Login</div> <div id="formBody"> <div class="formField"> <input type="email" name="email" required placeholder="Email" /> </div> <div class="formField"> <input type="password" name="password" required placeholder="Password" /> </div> <div> <input type="submit" value="Login" class="customButton" /> </div> </div> <div id='userNotes'> New here? <a href='register.php'>Register for free</a> </div> </form> <?php } ?> </div> </body> </html>
css/style.css - just for some styling.
body{ font: 20px "Lucida Grande", Tahoma, Verdana, sans-serif; color: #404040; } input[type=text], input[type=password], input[type=email]{ padding:10px; width:100%; } #userNotes{ font-size:0.7em; text-align:left; padding:10px; } #actions{ padding:10px; } #infoMesssage{ padding:10px; background-color:#BDE5F8; color:black; font-size:0.8em; } #successMessage{ padding:10px; background-color:green; color:white; } #failedMessage{ padding:10px; background-color:red; color:white; font-size:15px; } #formBody{ padding:5px; } #loginForm{ text-align:center; border:thin solid #000; width:300px; margin:7em auto 0 auto; } #formHeader{ border-bottom:thin solid gray; padding:10px; background:#f3f3f3; } #loginForm{ } .customButton { padding:5px; width:100%; -moz-box-shadow:inset 0px 1px 0px 0px #bbdaf7; -webkit-box-shadow:inset 0px 1px 0px 0px #bbdaf7; box-shadow:inset 0px 1px 0px 0px #bbdaf7; background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #79bbff), color-stop(1, #378de5) ); background:-moz-linear-gradient( center top, #79bbff 5%, #378de5 100% ); filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#79bbff', endColorstr='#378de5'); background-color:#79bbff; -moz-border-radius:6px; -webkit-border-radius:6px; border-radius:6px; border:1px solid #84bbf3; display:inline-block; color:#ffffff; font-family:arial; font-size:15px; font-weight:bold; text-decoration:none; text-shadow:1px 1px 0px #528ecc; cursor:pointer; } .customButton:hover { background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #378de5), color-stop(1, #79bbff) ); background:-moz-linear-gradient( center top, #378de5 5%, #79bbff 100% ); filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#378de5', endColorstr='#79bbff'); background-color:#378de5; } .customButton:active { position:relative; top:1px; } /* This imageless css button was generated by CSSButtonGenerator.com */
Demo Screenshots
Empty database. |
Registration form. |
Just a sample HTML5 validation. |
After successful registration. |
Our database will have the record. Notice the password field, it was hashed. |
Our login page. |
Just an example HTML5 validation during login. |
Login with wrong credentials. |
After login with correct username and password. |
Some references
- We should never store passwords as plain text.
- Add a long, unique random salt to each password you store so that brute force attacks will be a waste of time.
- If you want to have a deeper understanding and learn more techniques, I highly recommend reading the documentation, it's kinda long, but it's worth your time!
- Salted Password Hashing - Doing it Right
- How to store salt?
- Use bcrypt.
Please note that password hashing is often wrongly referred to as "password encryption". Hashing is a more appropriate term since encryption is something that is supposed to be easily reversible. ~ phpass
If there's something you want to add, something wrong, or any questions, please let me know in the comments. Thanks a lot!
The Code of a Ninja Resources
For FREE programming tutorials, click the red button below and subscribe! :)
website