How To Salt, Hash and Store Passwords Securely?

[adinserter block=”34″]

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!


Comments

15 responses to “How To Salt, Hash and Store Passwords Securely?”

  1. If you wouldn’t mind adding it, please put some validation on that email address like:

    filter_var($_POST[’email’], FILTER_VALIDATE_EMAIL);

    Using the bound parameters is good, but you want to be sure the data’s good too 🙂 Input filtering ftw!

    1. Sure, thanks for sharing @twitter-8854032! Using filter_var is a good technique! 😀

  2. dienast Avatar
    dienast

    Always use a random salt per user registration instead of using a static one in your code.

    1. Thanks for the tip @1fb9141a5eb3333aaddb662f1da47976! That will help for sure!

  3. TROWA07 Avatar
    TROWA07

    Is this hashed with MD5 + salt? I dont see in your code where you put the hash. i only saw your salt value

  4. Jonathan Bernardi Avatar
    Jonathan Bernardi

    I prefer to use the password hashing functions built into php.
    http://us3.php.net/manual/en/ref.password.php

    They are available on php 5.5+ and there is a library that will backport them to php 5.3.7+
    https://github.com/ircmaxell/password_compat

    It will also let you future proof your password hashing. Currently the best password hashing method is bcrypt. It is reasonable to assume that someday a better method will exist. If you use the password_hash function it will automatically start using the newer one as soon as it is available without having to change your code.

    Also bcrypt (which both phpass and password_hash use) adds a salt to the password by itself so there is no need to add one yourself.
    Double salting does not add any extra security. Using the same salt for every password likely even reduces security.

  5. Dear readers, this article is from 03/2013 (!) and outdated. For secure password hashing please only use PHP 5.5’s native password hashing API, not phpass!

    1. ninjazhai Avatar
      ninjazhai

      Hey guys @jonathanbernardi and @devmetal, thanks for the useful comments!

      But do you think my current blog post above will still help those users in PHP version < 5.3? I think they currently have significant numbers, according to this http://wordpress.org/about/stats/ and this http://w3techs.com/technologies/details/pl-php/5/all . I'm going to update this post soon.

      1. @ninjazhai Hmm, should we really support people who are still using 5.2, which is dead for 5 years (!) now? People with 5.2 are doing EVERYTHING wrong and are should not get any support from the community in my opinion.

  6. Gregory Smith Avatar
    Gregory Smith

    I find it is best to use some data from the user as the salt. that can be part of a string or it can be their login username or something. that way the salt is always random.

    1. Hi @disqus_8QO3F20Vmo , yes, that’s another way to do it. We can concatenate a user data in the $salt variable above.

      1. Gregory Smith Avatar
        Gregory Smith

        well the problem with concatenating onto a static string is that it will be possible to locate that static string. a salt should only be 1 string and it doesn’t have to be complex. it can be simple.

        1. Would you tell us how? Please show us a sample code, we’ll appreciate it! Many software like WordPress uses a ‘static string’ like what we used above, I’m sure they concatenate something on it too.

  7. Stephane Avatar
    Stephane

    Hello
    If I am not mistaken, your code runs on the server side, so you are sending the password over internet in clear….
    Stephane.

    1. Hi @Stephane, yes but the password sent by the server is hashed and not the actual password.

Leave a Reply

Your email address will not be published. Required fields are marked *