Categories
hash passwords php security

Secure hash and salt for PHP passwords

1235

It is currently said that MD5 is partially unsafe. Taking this into consideration, I’d like to know which mechanism to use for password protection.

This question, Is “double hashing” a password less secure than just hashing it once?
suggests that hashing multiple times may be a good idea, whereas How to implement password protection for individual files? suggests using salt.

I’m using PHP. I want a safe and fast password encryption system. Hashing a password a million times may be safer, but also slower. How to achieve a good balance between speed and safety? Also, I’d prefer the result to have a constant number of characters.

  1. The hashing mechanism must be available in PHP
  2. It must be safe
  3. It can use salt (in this case, are all salts equally good? Is there any way to generate good salts?)

Also, should I store two fields in the database (one using MD5 and another one using SHA, for example)? Would it make it safer or unsafer?

In case I wasn’t clear enough, I want to know which hashing function(s) to use and how to pick a good salt in order to have a safe and fast password protection mechanism.

Related questions that don’t quite cover my question:

What’s the difference between SHA and MD5 in PHP
Simple Password Encryption
Secure methods of storing keys, passwords for asp.net
How would you implement salted passwords in Tomcat 5.5

8

1017

DISCLAIMER: This answer was written in 2008.

Since then, PHP has given us password_hash and password_verify and, since their introduction, they are the recommended password hashing & checking method.

The theory of the answer is still a good read though.

TL;DR

Don’ts

  • Don’t limit what characters users can enter for passwords. Only idiots do this.
  • Don’t limit the length of a password. If your users want a sentence with supercalifragilisticexpialidocious in it, don’t prevent them from using it.
  • Don’t strip or escape HTML and special characters in the password.
  • Never store your user’s password in plain-text.
  • Never email a password to your user except when they have lost theirs, and you sent a temporary one.
  • Never, ever log passwords in any manner.
  • Never hash passwords with SHA1 or MD5 or even SHA256! Modern crackers can exceed 60 and 180 billion hashes/second (respectively).
  • Don’t mix bcrypt and with the raw output of hash(), either use hex output or base64_encode it. (This applies to any input that may have a rogue \0 in it, which can seriously weaken security.)

Dos

  • Use scrypt when you can; bcrypt if you cannot.
  • Use PBKDF2 if you cannot use either bcrypt or scrypt, with SHA2 hashes.
  • Reset everyone’s passwords when the database is compromised.
  • Implement a reasonable 8-10 character minimum length, plus require at least 1 upper case letter, 1 lower case letter, a number, and a symbol. This will improve the entropy of the password, in turn making it harder to crack. (See the “What makes a good password?” section for some debate.)

Why hash passwords anyway?

The objective behind hashing passwords is simple: preventing malicious access to user accounts by compromising the database. So the goal of password hashing is to deter a hacker or cracker by costing them too much time or money to calculate the plain-text passwords. And time/cost are the best deterrents in your arsenal.

Another reason that you want a good, robust hash on a user accounts is to give you enough time to change all the passwords in the system. If your database is compromised you will need enough time to at least lock the system down, if not change every password in the database.

Jeremiah Grossman, CTO of Whitehat Security, stated on White Hat Security blog after a recent password recovery that required brute-force breaking of his password protection:

Interestingly, in living out this nightmare, I learned A LOT I didn’t know about password cracking, storage, and complexity. I’ve come to appreciate why password storage is ever so much more important than password complexity. If you don’t know how your password is stored, then all you really can depend upon is complexity. This might be common knowledge to password and crypto pros, but for the average InfoSec or Web Security expert, I highly doubt it.

(Emphasis mine.)

What makes a good password anyway?

Entropy. (Not that I fully subscribe to Randall’s viewpoint.)

In short, entropy is how much variation is within the password. When a password is only lowercase roman letters, that’s only 26 characters. That isn’t much variation. Alpha-numeric passwords are better, with 36 characters. But allowing upper and lower case, with symbols, is roughly 96 characters. That’s a lot better than just letters. One problem is, to make our passwords memorable we insert patterns—which reduces entropy. Oops!

Password entropy is approximated easily. Using the full range of ascii characters (roughly 96 typeable characters) yields an entropy of 6.6 per character, which at 8 characters for a password is still too low (52.679 bits of entropy) for future security. But the good news is: longer passwords, and passwords with unicode characters, really increase the entropy of a password and make it harder to crack.

There’s a longer discussion of password entropy on the Crypto StackExchange site. A good Google search will also turn up a lot of results.

In the comments I talked with @popnoodles, who pointed out that enforcing a password policy of X length with X many letters, numbers, symbols, etc, can actually reduce entropy by making the password scheme more predictable. I do agree. Randomess, as truly random as possible, is always the safest but least memorable solution.

So far as I’ve been able to tell, making the world’s best password is a Catch-22. Either its not memorable, too predictable, too short, too many unicode characters (hard to type on a Windows/Mobile device), too long, etc. No password is truly good enough for our purposes, so we must protect them as though they were in Fort Knox.

Best practices

Bcrypt and scrypt are the current best practices. Scrypt will be better than bcrypt in time, but it hasn’t seen adoption as a standard by Linux/Unix or by webservers, and hasn’t had in-depth reviews of its algorithm posted yet. But still, the future of the algorithm does look promising. If you are working with Ruby there is an scrypt gem that will help you out, and Node.js now has its own scrypt package. You can use Scrypt in PHP either via the Scrypt extension or the Libsodium extension (both are available in PECL).

I highly suggest reading the documentation for the crypt function if you want to understand how to use bcrypt, or finding yourself a good wrapper or use something like PHPASS for a more legacy implementation. I recommend a minimum of 12 rounds of bcrypt, if not 15 to 18.

I changed my mind about using bcrypt when I learned that bcrypt only uses blowfish’s key schedule, with a variable cost mechanism. The latter lets you increase the cost to brute-force a password by increasing blowfish’s already expensive key schedule.

Average practices

I almost can’t imagine this situation anymore. PHPASS supports PHP 3.0.18 through 5.3, so it is usable on almost every installation imaginable—and should be used if you don’t know for certain that your environment supports bcrypt.

But suppose that you cannot use bcrypt or PHPASS at all. What then?

Try an implementation of PDKBF2 with the maximum number of rounds that your environment/application/user-perception can tolerate. The lowest number I’d recommend is 2500 rounds. Also, make sure to use hash_hmac() if it is available to make the operation harder to reproduce.

Future Practices

Coming in PHP 5.5 is a full password protection library that abstracts away any pains of working with bcrypt. While most of us are stuck with PHP 5.2 and 5.3 in most common environments, especially shared hosts, @ircmaxell has built a compatibility layer for the coming API that is backward compatible to PHP 5.3.7.

Cryptography Recap & Disclaimer

The computational power required to actually crack a hashed password doesn’t exist. The only way for computers to “crack” a password is to recreate it and simulate the hashing algorithm used to secure it. The speed of the hash is linearly related to its ability to be brute-forced. Worse still, most hash algorithms can be easily parallelized to perform even faster. This is why costly schemes like bcrypt and scrypt are so important.

You cannot possibly foresee all threats or avenues of attack, and so you must make your best effort to protect your users up front. If you do not, then you might even miss the fact that you were attacked until it’s too late… and you’re liable. To avoid that situation, act paranoid to begin with. Attack your own software (internally) and attempt to steal user credentials, or modify other user’s accounts or access their data. If you don’t test the security of your system, then you cannot blame anyone but yourself.

Lastly: I am not a cryptographer. Whatever I’ve said is my opinion, but I happen to think it’s based on good ol’ common sense … and lots of reading. Remember, be as paranoid as possible, make things as hard to intrude as possible, and then, if you are still worried, contact a white-hat hacker or cryptographer to see what they say about your code/system.

49

  • 10

    a secret doesn’t help as your password DB is supposed to be secret anyway – if they can get hold of that DB, they can also find whatever secret you’re using. it is however important that the salt is random.

    Dec 30, 2008 at 23:07

  • 6

    @wicked flea, I’m not arguing with you. Just pointing out how convoluted and complex this area of our work is. I keep hoping to get schooled by the be-all, end-all smartest, best practice for setting up a small web site’s content management system. I’m still learning here. …every time I read something that makes sense, I soon notice 5 other posts that contradict it. that round-and-round gets dizzying quickly 🙂

    May 26, 2009 at 0:16

  • 4

    Interesting revision. Is the user ID(say, an auto increment BIGINT) a good nonce? Or since it’s not random it isn’t good? Also, I’ll have to store the nonce for each user in the database… Does the site key + nonce + HMAC provide significant improved security over a salted(with user ID) hash iterated multiple times? Similarly, is iterating HMAC multiple times good for security?

    – luiscubal

    Jul 7, 2010 at 20:07

  • 4

    Sending a temporary password through email that requires the user to change it the first time they use it and sending a “secure” link over email that allows them to set their password are equally risky. In either case anyone who intercepts the email can access the account as long as they use the link or password before the intended recipient does.

    Aug 2, 2012 at 21:10

  • 3

    @RobertK By expanding the character set yes it increases, BUT by forcing all passwords to to follow rules decreases the amount of possible options. Let’s say someone was to get a password by brute force. By telling them the user’s password has 1 upper case letter, 1 lower case letter, a number, and a symbol, it means the number of tries they need is significantly less. By allowing the user to decide what they want, the hacker has to try harder.

    Jan 25, 2013 at 0:17

144

A much shorter and safer answer – don’t write your own password mechanism at all, use a tried and tested mechanism.

  • PHP 5.5 or higher: password_hash() is good quality and part of PHP core.
  • PHP 4.x (obsolete): OpenWall’s phpass library is much better than most custom code – used in WordPress, Drupal, etc.

Most programmers just don’t have the expertise to write crypto related code safely without introducing vulnerabilities.

Quick self-test: what is password stretching and how many iterations should you use? If you don’t know the answer, you should use password_hash(), as password stretching is now a critical feature of password mechanisms due to much faster CPUs and the use of GPUs and FPGAs to crack passwords at rates of billions of guesses per second (with GPUs).

As of 2012, you could crack all 8-character Windows passwords in 6 hours using 25 GPUs installed in 5 desktop PCs. This is brute-forcing i.e. enumerating and checking every 8-character Windows password, including special characters, and is not a dictionary attack. With modern GPUs, you can of course crack more passwords or use fewer GPUs – or rent the GPUs in the cloud for a few hours at reasonable cost.

There are also many rainbow table attacks on Windows passwords that run on ordinary CPUs and are very fast.

All this is because Windows still doesn’t salt or stretch its passwords, even in Windows 10. This is still true in 2021. Don’t make the same mistake as Microsoft did!

See also:

  • excellent answer with more about why password_hash() or phpass are the best way to go.
  • good blog article giving recommmended ‘work factors’ (number of iterations) for main algorithms including bcrypt, scrypt and PBKDF2.

9

  • 3

    but these systems are better known and maybe already compromised. but it beats making your own when you don’t know what your doing.

    Feb 11, 2012 at 22:53

  • 16

    Re “these systems are better known and maybe already compromised” – there is no reason why a well designed system for authentication should become “already compromised” just because it is better known. Libraries such as phpass are written by experts and reviewed by many people in detail – the fact they are well known goes along with detailed review by different people and is more likely to mean they are secure.

    – RichVel

    Feb 16, 2012 at 12:24

  • 1

    “don’t write your own password mechanism at all” – but the truly paranoid will want to write their own to minimise probability the NSA have a backdoor.

    – PP.

    Sep 12, 2013 at 15:29

  • 3

    @PP – the chances of a peer-reviewed password hashing algorithm having an NSA backdoor are very low, in my view. The chances of someone who is not a real crypto expert writing a new password hashing mechanism without other vulnerabilities is much lower. And the typical webapp uses just MD5 or SHA-1 hashing, which is terrible – even Chris Shiflett’s otherwise great Essential PHP Security book recommends MD5 …

    – RichVel

    Sep 14, 2013 at 10:55

  • 1

    phpass is NOT the best way to go. Never has been and likely never will be. I reviewed the code several years ago and it is NOT secure on Windows or any platform where /dev/urandom is not available. It does NOT follow best-practices when it comes to security, using a combination of md5() and microtime() when it should be terminating the application instead of making false claims about security. It hasn’t seen any updates since I reviewed the code despite PHP itself moving ahead in the security space with bcrypt in core. Stay FAR away from phpass.

    Feb 8, 2015 at 12:57


45

I would not store the password hashed in two different ways, because then the system is at least as weak as the weakest of the hash algorithms in use.

0