DEV Community

Cover image for Good Practices: PHP Security, How to manage password
anastasionico
anastasionico

Posted on

Good Practices: PHP Security, How to manage password

Facebook-Cambridge Analytica, the 2016 US Democratic email leak, the 2018 Google data breach, the 2012 Yahoo Voice hack are only some of the data breaches that have affected us during the last decade or two.

The world’s information has been available to us like never before and, if we haven’t been careful, our information (even the more private) has travelled the opposite way.

Whether you are developing an open-source game for children or working for a Fortune 500 business, as a web developer is your responsibility to ensure security to all your platforms.

I don’t blame you.

 

Security is hard,

A good point to start is by using good practices when working with passwords.

Luckily PHP provides several tools and functions that we can use to make our application safe.

In this post, you are going to discover all you need to know to make your applications airtight.

 

 

3 rules for password security

Never know user passwords

I still remember my first days as a PHP developer, one of the first application I created was a game to manage a fantasy football team with my friends.

Each of us could log to our accounts, add players, deploy the men every week for the new game.

I remember also that I created the basic accounts, 

 

Yes, I added username and password for all of them and emailed them the credentials that needed to use.

Only months after I learned and realized how silly, and worse, dangerous that was.

As a general rule:

Not only you should never know the password of your users, but you also should never be able to know them

This is a serious matter, that can imply lots and lots of legal liabilities.

As a rule of thumb, you do not want to store passwords in plain text or use decryptable passwords.

 

Do not restrict passwords

Let’s play a game, 

guess the following password:

**********

Hard isn’t it?

Let’s try again:

P*r***e***

Now you know that there are a capital letter and few more lowercase letters in the middle

How about numbers?

P*r***e911

Now that you know that the word includes a capital letter, lowercase letters and number it is so much easier to guess.

The same happens when restricting the choices to our users have upon their passwords.

 

If one of your application’s requirement is to follow a definite pattern you are providing several hints that malicious users can use against you.

It is ok to use a minimum number of characters because the length of a password affect the time needed to be spent to find it out, but it is much better to learn how algorithms and hashing works

By the way, the solution of the riddle was “Porsche911”.

 

Never email passwords in clear

As I wrote in my little story above, one of my first error as a web developer was not to learn how to manage password earlier.

This was before I came across good practice, I was (and still am) figuring this programming thing out.

In Modern PHP, Josh Lockhart wrote about this issue and illustrate how the trust on a developer can be lost in a matter of seconds.

Imagine you are a client and hire a web developer for creating a nice and shining e-commerce website for your business,

Now, 

imagine you receive an email from your developer and the email contains a password for your website. 

A simple example but keep following along…

You now learned 3 things about your pal, 

He knows your password;

He is storing your password in clear with little to no encryption at all;

He cannot care less about sending passwords over the internet

The next letter you should send him should be a Notice of Termination.

Here is how you as a web developer should do:

Create a page on your web application that allows inserting the user’s email when he/she forgot its password in order to request a new one

Your application now generates a unique token and associate the toker to the user that made the request (I personally use UUID in this occasion)

Your application then send an email to the user with a link that includes the token

Once the user visits the URL, your application validates the token and allow the user to change its password

After the password in change, the token is deleted

How much has the security of the application increased with these simple steps?

This is what its called good practice.

We can even higher the level of security by adding a time limit between the request of the new password and the submission of the new one if we want.

 

 

How to hash users’ password

Let's start with an affirmation.

You should hash the password of your web application, you do not encrypt them.

That made me struggling for a while,

The difference between the two is that encryption is a 2-way algorithm, you encrypt a string than later on you can decrypt and get your string back, this is largely used in intelligence when sending or receiving messages by allied.

Hashing is something different, a hashed string cannot go back to plain text, or at least this is the final goal.

There are many algorithms designed for a different purpose, some need to be fast, others very secure.

This is an always-changing technology, with a lot of improvement just in the last few years.

We are going to have a look at three of the most popular in chronological order.

 

Sha-1

This is the first hashing function

SHA1 stands for Secure Hashing Algorithm, and it was developed by the NSA.

It was very famous and one of the most used in the PHP world in produces a 20-byte hexadecimal string, 40 digits long.

The SSL industry has chosen SHA1 for digital signatures for several years until, after several weaknesses,  Google decided that was time to upgrade to SHA-2

The first version of this algorithm has been “deprecated” in 2005 and other new versions have been designed (SHA-2, SHA-3, and SHA-256) and are now used.

 

 

Bcrypt

Bcrypt is not the natural evolution from the SHAs but is the one that picked up more audience for his security.

It has been designed to be really slow and produce the most secure hashing string possible.

It does several iterations that hashing the data, in technical term that’s called work factor,

The higher is the work factor the more expensive would be for a hacker to get the password.

The good news is that in the future when we are going to have more powerful machines we can just do more iterations.

 

I will leave an amazing link regarding Bcrypt by the guy at auth0.

 

 

Argon2 

This is the brand-new shining thing in the world of hashing, it was designed by Alex Biryukov, Daniel Dinu, and Dmitry Khovratovich at the University of Luxembourg, and it won the Password Hashing Competition in 2015.

Argon2 is divided into 3 versions:

  • Argon2d accesses the memory array it reduces the possibility of time-memory trade-off but it has a possible side-channel attack;
  • Argon2i is quite the opposite, it is optimized for side-channel attack and access the memory in a password independent order;
  • Argon2id consist of a mix of the two versions,

By itself, this function has 6 parameters the string of the password, the salt, the memory cost, the time cost, the parallelism factor which is the maximum number of parallel threads allowed, and the length of the hash.

You will see below how to use this hash in PHP leveraging built-in functions.

 

Also, I have already written about this hashing almost a year ago when PHP 7.3 was released.

 

 

Hashing and Verify a password in PHP 7

Hashing the password with password_hast

This function creates a password hash according to the parameters we decide to pass to it.

It uses a one-way algorithm.

We can decide which type of algorithm we want to use by inserting the constant of our choice, the constant available are:

  • PASSWORD_DEFAULT from PHP 5.5 uses the Bcrypt algorithm as a default algorithm, it, however, changes over time according to the discovering of the new and more secure algorithm, or the other factors.
  • PASSWORD_BCRYPT produces a crypt() hash, it usually is 60 characters long and you can recognize it by its identifier that looks like this “$2y$”
  • PASSWORD-ARGON2I Argon2 is one of the most secure hashes available at the moment, this algorithm is available only if your PHP has been compiled with Argon2
  • PASSWORD_ARGON2ID Another hashing algorithm that belongs to the family of the Argon2, it uses the Argon2ID version rather than the I. it also needs to PHP be compiled with Argon2 in order to work.

This function has also an optional parameter consisting of an associative array that accepts several keys according to the algorithm we choose to use.

In case you prefer to use Bcrypt the key of this array will be the cost (an integer representing an expansion iteration count as a power of two).

If instead you choose to use the algorithm that uses Argon2 the keys of the associative array are going to be: the memory_cost (an integer that indicated the maximum memory needed to be used to compute the hash), the time_cost (an integer that indicate the maximum time is going to be needed to compute the hash) and the thread (another integer that specifies the number of threads to use when computing the hash).

Do not provide the salt in PHP 7.0, doing so are going to produce a deprecation warning. 

Now that we know all the elements required to use the password_hash() function let’s see how to write it,

echo password_hash("ThisIsAPassword", PASSWORD_DEFAULT);
$2y$10$.vGA1O9wmRjrwAVjuoshdyenNpDczlqm3Jq7KnEd1rVAGv3Fykk1a

$options = [
    'cost' => 12,
];
echo password_hash("ThisIsAPassword", PASSWORD_BCRYPT, $options);
$2y$12$QjSH496pcT5CEbzjD/vtVeH03tfHKFy36d4J0Ltp3lRtee9HDxY3K

echo password_hash('rasmuslerdorf', PASSWORD_ARGON2I);
$argon2i$v=19$m=1024,t=2,p=2$YzJBSzV4TUhkMzc3d3laeg$zqU/1IN0/AogfP4cmSJI1vc8lpXRW9/S0sYY2i2jHT0
Enter fullscreen mode Exit fullscreen mode

It is recommended that you test this function on your servers, and adjust the cost parameter so that execution of the function takes less than 100 milliseconds on interactive systems. 

The script in the above example will help you choose a good cost value for your hardware.

Also, have a look at what the official manual has to say about password_hash 

 

 

Verify the users’ password

You have allowed users to register to your brand new application, they can insert their password and you handle them astonishingly,

Hashing them according to the last security trends, you do not store anything in clear and your server is hidden inside a vault 10 meters (32 feet for English readers) underground.

 

Now what?

Now you actually need to allow the user to log-in to your application.

To do that PHP provides a built-in function that verifies that the password matches the hashed string.

This function is called password_verify().

It has two parameters both must be of type string, the first parameter is the password the user has typed into the login form, it has to be a plain text password, the second parameter is the actual hash we need to verify against.

The result is a boolean value that we can use inside the conditional operation and log the user in or tell him the something when wrong

This function works because in the previous step when we hashed the password, the value returned from password_hast included the algorithm, the cost and the salt that we used.

Thus, all information the password_verify() needed are already available. 

Here is an example:

// See the password_hash() example to see where this came from.
$hash = '$2y$07$BCryptRequires22Chrcte/VlQH0piJtjXl.0t1XkA8pw9dMXTpOq';

if (password_verify('rasmuslerdorf', $hash)) {
    echo 'Password is valid!';
} else {
    echo 'Invalid password.';
}
Enter fullscreen mode Exit fullscreen mode

Something to notice is that you cannot use double quotes when using the $hash string, as you can see there are several dollar signs inside the variable and therefore they will be evaluated rather than parsed.

The official manual has a page about password_verify as well.

 

 

How to Create a User Registration system in PHP

I hope you now understand how PHP developers manage security practices when dealing with passwords (If you still have any doubt ask your questions on our community).

We have seen how these built-in functions work in theory, now we are going to see how to actually use them when creating a basic signup and login system.

 

Step 1: Creating a database table using a SQL language

CREATE TABLE IF NOT EXISTS `users` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `username` varchar(50) NOT NULL,
    `email` varchar(50) NOT NULL,
    `password` varchar(50) NOT NULL,
    PRIMARY KEY (`id`)
 );
Enter fullscreen mode Exit fullscreen mode

This code above is not PHP but MySql and will create a table called users in which we are going to store the users’ data we need.

Database language and queries are out of the scope of this tutorial, if what you have just seen confused you have a look at this tutorial about mysql

 

Step 2; Creating a Signup or Registration form signup.php

< form action="signup.php" method="POST" >
    < input name="username" required="" type="text" /> 
    < input name="email" required="" type="text" />
    < input name="password" required="" type="password" /> 
    < input name="submit" type="submit" value="Register" />
< /form >
Enter fullscreen mode Exit fullscreen mode

The code above represents a very basic HTML form that sends a post request to the page signup.php all the field within the post are required and the goal is to send and elaborate the information provided within the form to register and log the user in.

 

Step 3; Register the user

if(isset($_POST[submit])){
    $username=$_POST['username'];
    $email=$_POST['email'];
    $password=password_hash($_POST['password'], PASSWORD_DEFAULT);

    $checkQuery="SELECT * FROM users where (username=:username ||  email=:email)";
    $check = $dbh -> prepare($checkQuery);
    $check->bindParam(':uemail',$email,PDO::PARAM_STR);
    $check->bindParam(':uname',$username,PDO::PARAM_STR);
    $check->execute();
    $results = $check->fetchAll(PDO::FETCH_OBJ);
    
    if($check->rowCount() == 0) {
        $sql="INSERT INTO users (username, email, password) VALUES(:username, :email, :password)";
        $query = $dbh->prepare($sql);
        $query->bindParam(':username',$username,PDO::PARAM_STR);
        $query->bindParam(':email',$email,PDO::PARAM_STR);
        $query->bindParam(':password',$password,PDO::PARAM_STR);
        $query->execute();
        $lastInsertId = $dbh->lastInsertId();
        If ($lastInsertId) {
            $msg="You have signup Successfully";
        } else {
            $error="Something wrong here. Try again";
        }
    } else {
        $error="User already exist. Try again";
    }
}
Enter fullscreen mode Exit fullscreen mode

After checking that the form has been updated the snipped above gathers all the information submitted by the user and hash the password using PASSWORD_DEFAULT as algorithm of choice ( BCrypt is the built-in in PHP7 ) we prepare and execute a statement in which we check if the user already exists in our database and if not we insert his data.

There are several expedients we can use to make this code much more secure, have a look at the first episode of this series about Good Practices - Input validation

 

Step 4: Create the login form

< form action="login.php" method="POST">
    < input name="username" required="" type="text" />
    < input name="password" required="" type="password" />
    < input name="login" type="submit" value="Login" />
< /form>
Enter fullscreen mode Exit fullscreen mode

Another very simple form, After the user has filled in the field and submitted the form will send a post request to the login.php page the will evaluate the data and grant access to the user.

Step 5: Allow the user to access the web application

session_start();

if(isset($_POST["login"])) {
    $username=$_POST['username'];
    $password=password_hash($_POST['password'], PASSWORD_DEFAULT);

    $sql ="SELECT username, email, password FROM users WHERE (username=:username) and (password=:password)";
    $query= $dbh -> prepare($sql);
    $query-> bindParam(':username', $username, PDO::PARAM_STR);
    $query-> bindParam(':password', $password, PDO::PARAM_STR);
    $query-> execute();
    $results=$query->fetchAll(PDO::FETCH_OBJ);


    if($query->rowCount() > 0) {
        $result = $query>fetch();
        if (password_verify($_POST["password"], $result['password'])) {
            $_SESSION["username"] = $_POST["username"];
            exit(header("location:home.php"));
        } else {
            echo "'Invalid Details'";
        } 
    } else {  
        echo "'Invalid Details'";
    }  
}
Enter fullscreen mode Exit fullscreen mode

Here you see the function password_verify() in action.

The first thing we check if the page has a post request, then select and count the number of users that match the input submitted.

If everything was fine, this number should be 1, then we verify the password and redirect the user to the home page otherwise we need a little bit of help from Javascript to show an alert box with an error message.

Click the image below to be ther fist to get all new posts
subscribe-Medium.jpg

 

 

Conclusion

Another section of the “Good Practices for PHP developer” series gone,

After seeing how to manage input, how to sanitize, validate and escape in PHP you now have learned how to keep your web application safe and manage your password properly.

Remember that follow good practices is not a list of a standard that you need to follow, it a path that you should enjoy.

Doing it on a daily basis learn new techniques like you just did with the code above, add some additional functionality and experiment with your code will eventually make you a black belt in web development either you are using PHP or prefer to discover what the other languages have to offer.

Top comments (4)

Collapse
 
v461m profile image
v461m

Hi anastasionico, good article, but i think that is very important for people to think in security mode ON when they write code, so in the various forms you need to include the csrf token... what do you think?
Thanks a lot!

Collapse
 
anastasionico profile image
anastasionico

one of my teacher always used to say "think at the user as the most dumb or evil person possible and code accordingly"

Collapse
 
jfstassen profile image
Jfsen

Hi. Pretty good read. I just don't understand why you declare $results and never uses it nor why you fetch all.
I think there is a typo at step 5: $query>fetch() -- missing dash ?

Collapse
 
anastasionico profile image
anastasionico

Hi Jfsen, Thank you so much for taking the time to comment on my post.
About the code snippets please consider them as pseudo-code, not to be copy-pasted into an actual application.
regarding the fetch() 's typo it was just google translate being funny before I posted it.
You can find an updated version of this post and the whole series on my website anastasionico.uk/blog/good-practic...