How To Salt, Hash and Store Passwords Securely?

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.

RELATED: PHP Login Script with Session Tutorial – Step by Step Guide!

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.

You see, even if someone steal your database, they will never read your actual or cleartext password.

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.

php+hash+password+database+with+a+record

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" . $_POST['email'];
            $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.
Empty database.
Registration form.
Registration form.
Just a sample HTML5 validation.
Just a sample HTML5 validation.
After successful registration.
After successful registration.
Our database will have the record.  Notice the password field, it was hashed.
Our database will have the record.
Notice the password field, it was hashed.
Our login page.
Our login page.
Just an example HTML5 validation during login.
Just an example HTML5 validation during login.
Login with wrong credentials.
Login with wrong credentials.
After login with correct username and password.
After login with correct username and password.

Some references

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!

You can download all the code used in this tutorial for only $9.99 $5.55!
[purchase_link id="12455" text="Download Now" style="button" color="green"]

Thank you for learning from our post about: How To Salt, Hash and Store Passwords Securely?

RELATED: PHP Login Script with Session Tutorial – Step by Step Guide!

Hi! I'm Mike Dalisay, the co-founder of codeofaninja.com, a site that helps you build web applications with PHP and JavaScript. Need support? Comment below or contact [email protected]

I'm also passionate about technology and enjoy sharing my experience and learnings online. Connect with me on LinkedIn, Twitter, Facebook, and Instagram.