4

Apparently, hashing the username and password and saving them as cookies and logging in with sanitized cookie data is not good enough for you people (or my site's safety).

Is this approach good enough?

Registration procedure:

$salt= date('U');
$username= hash('sha256', $salt. $_POST['username']);
$password= hash('sha256', $salt. $_POST['password']);
$token = hash('sha256', $salt. (rand(1,10000000000000000000000000000000000000000000)));

To login manually, users type in their username and password, it is passed through the hash and matched.

Upon successful login, two cookes are created for the user. One holding the unhashed username, the other holding the hashed token.

When the user visits, if the cookies are set, the site hashes the username and then uses these two values to login.

The advantage of this approach is that it avoids storing the user's password anywhere on their computer. Someone could crack the token and gain access to their account but at least I wouldn't have jeopardized the user's password...

The people who answered MySQL real_escape string and cookies say it is wrong to store user data in cookies. Does this divert the problem?

Community
  • 1
  • 1
user187680
  • 663
  • 1
  • 6
  • 20
  • You need to show the login procedure, too. – ryandenki Jul 03 '12 at 01:17
  • 5
    I would also be a little worried how PHP handles numbers in the tredicillion range. – ryandenki Jul 03 '12 at 01:18
  • You should re-use an existing authentication framework whenever possible, because, really, it's complex. For example, take a look at https://github.com/delight-im/PHP-Auth – caw Sep 21 '16 at 04:29

4 Answers4

5

Why do you need to store the password at all, even an encrypted version? Is your site accessing a 3rd-party API in the backend that does HTTP Basic auth or something?

Unfortunately there's no definitive answer to your question. "Appropriate" means different things to different people. And with security questions, I'm not sure it's ever possible to be "appropriate enough" or "secure enough." That said, here's how I handle logins and password security. In my database's users table, I have 3 login-related columns:

  1. username
  2. salt
  3. passwordHash

The username column is in plain text. The salt column is a 64-character string of randomly chosen alphanumeric characters. The passwordHash column is the user's password concatenated with their salt value, and then irreversibly hashed. I use sha256 for my hashes. The salt is 64 characters because that's what sha256 produces. It's good to have a salt value at least as long as the hash in order to produce enough variability in the hashed string.

When the user submits the login form, I do a database query for the username. If the username isn't found, I show an "Invalid username and/or password" error to the user. If the username is found, I concatenate the salt with the password, hash it, and see if it equals the passwordHash value. If not, the user is shown the exact same error.

It's good to show the exact same error message regardless of whether the username was wrong, or the password was wrong, or both. The fewer clues you give to a hacker, the better. Also, whenever a user changes their password, I give them a new salt too. It's really easy to do this at that point in time, and it keeps the salt values a little fresher.

This system of having a different salt per user is called dynamic salting. This greatly complicates a hacker's job if they try to use rainbow tables to reverse-engineer your users' passwords. Not to mention that storing passwords in irreversibly hashed form goes a very long way toward keeping anyone from determining a user's password, even if they have access to the database and the PHP code.

This also means if your user forgets their password, there's no way to retrive it. Instead, you write your system to just reset it to a new randomly-determined value that is sent to them along with strong encouragement to change their password as soon as they log in again. You can even write your system to force this upon the next successful login.

I require passwords to be at least 8 characters. Ideally, it should also include numbers and special characters, but I haven't decided I should require this yet. Maybe I should!

To protect against brute-force attacks, I keep track of all failed logins during the previous 10 minutes. I track them on a per IP address basis. After 3 failed login attempts, the system uses the sleep() function to delay responding to further login attempts. I use a block of code like this:

$delay = ($failedAttempts - 3);
if ($delay > 0) {
    sleep($delay);
}

IMHO this is much better than locking users out of their accounts after a hard number of failures. It reduces the number of customer support inquiries you'll get, and it's more graceful for legitimate users who simply can't remember their own passwords. Brute-force attacks need to do many attempts per second in order to have any sort of efficiency, so delaying on an n = x basis keeps them from getting very far at all.

Login sessions are tracked with PHP sessions. Call session_start() when every page on your site is loaded. (This is really easy if you have a common header.php file.) This makes the $_SESSION variable available. When a user successfully logs in, you can use this to store whatever info your site needs in order to know the user is logged in. I typically use their User ID, Username, and maybe some other details specific to the site. But I don't include the password or a hash of it here. If somehow a hacker got into the user's session data, which is stored on your server, they still wouldn't have a chance at finding the user's password this way.

Logging out happens when one of 2 things occur: Either 1) The user's session cookie is deleted, such as by clearing the browser cache or sometimes just by closing the browser window, or 2) Your server deletes their session data. You can force the latter to happen via calling session_destroy() when the user presses your "Log out" button on your site. Otherwise you could make sessions automatically expire after a certain period of time. This may involve tweaking your session.gc_* params in php.ini.

If you absolutely must know the user's password after the initial login phase, you can store it in $_SESSION. Do this IF AND ONLY IF your site requires an SSL connection and you've made it so the site won't work without one. This way the password is encrypted and so is protected against packet sniffing. But know that it's a security risk if a hacker gets access to your server's session data.

curtisdf
  • 4,130
  • 4
  • 33
  • 42
  • 1
    I want you to know you made my day. i'm not exaggerating. I love the internet. I love that people will take the time to give such detailed, clearly written and informative responses. If I had money, Id give you a pile. – user187680 Jul 03 '12 at 03:54
  • Glad to know my time was well spent. :-) I wish I had been told these things many years ago. – curtisdf Jul 03 '12 at 04:37
  • only 1 upvote?! Beautifully written. Of course, my favorite post will always remain: http://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags – Kasapo Jul 11 '12 at 20:27
1

Your salt is not random enough.

The problem with this is a replay attack. Suppose the bad guy collects the user's cookies on the way to your site. What stops him (the bad guy) from sending the same values and getting in as if they were the genuine user?

It is bad to store the user data in the cookies. What you might get away with is storing a single random 64-bit number in the cookie on the user's browser. In your application's authentication database, you record the random number and salient properties such as the user name, time when the cookie was first valid, when it expires, and maybe browser information and IP address information.

When the cookie is sent, you validate that it seems to come from the same browser as when you sent the cookie.

I have not checked this against best practices, or what it takes to 'reliably' identify the user again. But the random data contains nothing meaningful, so it is not the end of the world if a bad guy captures it. The validity of the captured cookie is limited, and the bad guy has to simulate the user's environment fairly exactly if you capture appropriate details.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • 2
    If someone can snoop the authentication cookies, they can get in anyway. And if they can snoop the login request, they'll have the password too. The only way to prevent those attacks is to use HTTPS (which is a good idea anyway, if you care about security). – Ilmari Karonen Jul 03 '12 at 01:25
0

Why hash the username? It's not secret, and you're going to need the unhashed (or at least unsalted) username to look up the login credentials anyway.

Also, I think you'll want to use a crypto-safe RNG, such as openssl_random_pseudo_bytes() instead of rand(). Other than that, though, the basic idea looks OK.

Ilmari Karonen
  • 49,047
  • 9
  • 93
  • 153
0

If I were a malicious user on an open network, like a wi-fi hotspot, and wanted to gain control of one of your user's accounts, I might try the following approaches. Defend yourself.

  • Knowing (from your post on SO) that you use date('U') as your salt, I would attempt to crack the password using a dictionary, appending the known salt before each test.
  • If you're allowing unencrypted traffic, I would sniff the traffic and hijack the token. Hopefully your site would provide a way to change my e-mail address and then e-mail me the password.
  • If I can gain file access, I would extract the token from the file and use it directly.

Another approach you might consider is to serialize the fields, encrypt that string, then store a base64-encoded message of that in your cookie. Also, append some large messy string to your existing salt.

phatfingers
  • 9,770
  • 3
  • 30
  • 44