4

(BTW, this is all coded in PHP as I'm sure you'll be able to tell from the function below)

When saving passwords, I am aware that you have to hash your passwords, in fact here's my code (tell me if it's secure enough) :

function hash($password)
{
    return password_hash($password, PASSWORD_BCRYPT, array(
        'cost' => 12,
        'salt' => bin2hex(openssl_random_pseudo_bytes(16))
    ));
}

However, my actual question is whether I am using an appropriate login system, or not. In the next paragraph I'm going to explain how my current system works and can someone please tell me if it's OK or needs to be changed. I've heard that the proper way you're suppose to do it is with a token that the client then saves in the cookies or something.

When the user first arrives on the site, and their account is registered, they're prompted to login. With the password they provided, I hash it and check if it matches, with password_verify(), the password in the database. Once validated they have the correct login information and I can go ahead and authorise them I set in the $_SESSION the username and password (which I understand is stored on the server side), and if they ticked the remember me button I save the username and password (unhashed) in the cookies to expire in 1 week.

Every time they attempt to do a new action, for example load a page like account information, it runs a function at the top of the code which returns if they're authenticated or not, to check if they're authenticated or not, it checks the $_SESSION for the username and password and authenticates them again. If they've ticked remember me and the $_SESSION has expired it grabs the username and password from there.

My concern is that I'm sure most websites use a token system, so that when you login it saves a token in your cookies and then instead of signing in every time, it uses the token. And also on forums for example you can check who's online whereas with mine, you can not.

Can someone please respond whether what I've done is correct, or it must be changed, or it's not the best but technically it's secure. The session part I'm pretty sure is secure, and even more secure than some videos I've watched, some say that you can just store the user's ID in the session which means if they change their password they're still logged in. But the cookies part, it's tripping me up with cookie stealing scares saving it unhashed in their cookies.

3
  • You need to save the salt as well as the hashed password in order to verify the password - your code will not work.
    – symcbean
    Aug 7, 2016 at 15:40
  • Cost should be set as high as your production server allows, taking into account its performance in busy traffic and the patience of your non malicious users. e.g. aim for login to take around a second. Aug 10, 2016 at 17:40
  • So that's what Remember Me does. Never used it; never will.
    – Jennifer
    Oct 21, 2018 at 3:47

4 Answers 4

1

I am aware that you have to hash your passwords, infact here's my code (tell me if it's secure enough)

Yes, it's secure. I wouldn't pass the salt though, password_hash manages salts automatically.

I set in the $_SESSION the username and password (which I understand is stored on the server side)

Yes, it's stored server-side. Still, you shouldn't store sensitive information in the session if it can be avoided - which in this case it can be (you don't need the password in the session at all).

This is a security issue as it may lead to the leakage of the plaintext password, as session data may be read out on shared hosts, via file inclusions, and so on.

if they ticked the remember me button I save the username and password (unhashed) in the cookies

Again, this is not good. You really don't want to store the password unhashed anywhere, especially the cookie.

Anyone with temporary access to the victims browser would now have access to the plaintext password, it may be accessed via XSS (at least if your cookie isn't httpOnly, which it should be), and so on.

What you want to use instead is some random identifier that you generate and store in the database (ideally hashed).

The session part I'm pretty sure is secure, and even more secure than some videos I've watched, some say that you can just store the user's ID in the session which means if they change their password they're still logged in. But the cookies part, it's tripping me up with cookie stealing scares saving it unhashed in their cookies.

You seem to know that you don't follow best-practices. So why build your own here? Your approach doesn't seem to offer any advantages, but does have some rather serious downsides.

0
1

Quick reminder:

  • Don't define the salt, let the password_hash() function manage that for you. Note that it's deprecated in PHP7.
  • bcrypt truncates password longer than 72 characters and on NUL byte (\0). This is by design, not because of PHP implementation. For these reasons, is it recommended to pass the base64_encode() result of the hash('SHA-384') to password_hash() instead of the original password directly:
function safePasswordHash($password) {
    return password_hash(
        base64_encode(
            hash('sha384', $password, false)
        ), PASSWORD_BCRYPT, array('cost' => 12)
    );
}

Try it: https://3v4l.org/R1ccr

Based on this article, you could also use a random selector and token to associate each user with an authentication state in a separate table (e.g. "token_auth").

So first generate two 12 characters random strings:

  • A "selector", used as an identifier;
  • A "validator", stored in plain text in the cookie and in hashed value in the database ("token" field). Use SHA-256 here.

Then you set the cookie as "selector:validator" e.g. "FhpyQLjXYCc9:qXE6Bh417rYg".

To ensure the cookie is valid, you first fetch the "token" that matches the "selector" in your database and then compare it with the SHA-256 value of the "validator" present in the cookie.

Use the hash_equals() function to avoid potential timing attacks.

1
  • Thankyou, I will look more into this hash with BCRYPT and sha384
    – user120698
    Aug 7, 2016 at 14:56
0

Your intuition about storing the passwords in plaintext in a cookie is correct - it's not a good idea to do so, and there are more secure alternatives. You're salting and hashing your passwords, but storing them in plaintext in the $_SESSION variable sort of counteracts that - it means that if an attacker gained access to your server, they'd be able to get the passwords of all currently signed-in users.

As far as I know, the standard method is exactly like you mention - using a token. This is far more secure - you never have to store your users' passwords in plaintext anywhere. The general idea is this:

  • When a user POSTs their username & password to you (over HTTPS), you verify it with password_verify().
  • If the authentication is successful, you save a cookie to the user's computer with the following: username=xxxx&expiry=xxxx&digest=xxxx. How long the token should last is up to you. You can also add other variables, like admin=true and such.
  • The digest is an HMAC - a Hash-based Message Authentication Code. It proves that only you (the server) could have created the cookie, and that it was not tampered with. You can use PHP's hash_hmac() for this. Note that you'll need to save a secret key on your server.
  • Now, when a user tries to access another page, you just check that they have your cookie and (crucially) that the digest= matches the hash_hmac() of the other cookie data. If a user tries to tamper with their cookie, e.g. by changing it to a different username, the HMAC won't validate.

There's a very good paper about this that I recommend reading: Do's and Dont's of Client Authentication on the Web (PDF link).

5
  • Thankyou very much! I will definately read that PDF and immediately add this approach!
    – user120698
    Aug 6, 2016 at 13:26
  • Can you please explain how to do this? That PDF is 16 pages long and I have no idea how.
    – user120698
    Aug 6, 2016 at 14:08
  • I'd recommend reading the whole thing - if you're writing an authentication system, it's best to fully understand the ins and outs! That being said, section 4 presents their design more briefly.
    – tao_oat
    Aug 6, 2016 at 14:21
  • Thanks, I just researched JWTs, is it the same thing as what you were talking about?
    – user120698
    Aug 6, 2016 at 14:30
  • I'm not familiar with JWT, but it looks like it could be used for the above - though it isn't necessary for it.
    – tao_oat
    Aug 6, 2016 at 14:43
0

I have found an answer, for this I am using JWT(s). Json Web Tokens. They are two base64 encoded strings of json data and a valid signature at the end validating that the information has not been changed. It contains when the login session expires, and their username. It's especially good if you want to easily employ new servers as it doesn't need to synchronise with one database for login.

Anyone that wants to do authentication, I definately reccomend it. So easy to use, here's a lib too: https://gist.github.com/anonymous/e223abe8066b66c40332649404553385 <-- It's a copied class of one I found on github except I've edited it to remove errors and make it simpler.

$key="1682da5b920f4ceb74a542922f43b2a4"/* You need to keep this to decode */
$a = new JWT;

$jwt = $a->encode(json_encode(array(
            "typ" => "JWT",
            "alg" => "HS256"
        )), json_encode(array(
            "iss" => "http://yourwebsite.com/",
            "iat" => time(),
            "exp" => time() + (20 * 60),
            "username" => "authenticated Username"
        )), $key);

        setcookie('jwt_token', $jwt, 0, '/');

Remember, the data set in JWTs can not be changed, as the signature that the key will provide later on keeps it this way.

When a user is attempting to sign in, run the $a->decode() function, it will return false if the data has been tampered with, for example them changing the username and attempting to sign in as "administrator".

$jwt = $this->jwt->decode($_COOKIE['jwt_token'], $key);
        if($jwt == false) {
            echo 'Invalid JWT signature, tampered with!'
        } else {
            $json = json_decode($jwt, true);

            if(time() > $json['exp']) { /* expired */ }
            if(time() < $json['iat']) { /* invalid */ }

            /* 
             VALIDATED, let them through as being the user:
             $json['username']
            */
        }

if it returns false, as stated above, the signature isn't valid as it was tampered with. You can then use the json_decoded array to check information like the "exp", if time() > exp then it's expired. If time() < iat then they're also spoofing it. Once all of it has been validated you can check the username and bee 100% sure that they have permission to be in that user's account.

You must log in to answer this question.