1209

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

Community
  • 1
  • 1
luiscubal
  • 23,581
  • 8
  • 51
  • 82
  • 13
    http://www.openwall.com/phpass/ is also very good library – Alfred Jul 02 '11 at 01:02
  • 54
    Md5 is now completely unsafe – JqueryToAddNumbers Jul 06 '12 at 05:14
  • 3
    @NSAwesomeGuy That depends on what you're using it for. It's trivial to rainbow-match or just brute force unsalted MD5 passwords, sure, but with decent salting it's still exceedingly impractical to build a rainbow table for fast cracking of sets of passwords, and brute force is a no-hoper. – Craig Ringer Aug 24 '12 at 06:54
  • 13
    PHP 5.5+ has a secure password hash built in http://php.net/manual/en/function.password-hash.php – Terence Johnson Jan 01 '13 at 16:09
  • http://php.net/faq.password – hakre Jun 27 '13 at 00:58
  • [How to use the PHP 5.5 password hashing functions](http://www.dev-metal.com/use-php-5-5-password-hashing-functions/) – Sliq Sep 13 '13 at 23:56
  • Also see Openwall's [Portable PHP password hashing framework](http://www.openwall.com/phpass/) (PHPass). Its hardened against a number of common attacks on user passwords. – jww Oct 11 '14 at 23:26
  • Built-in since v5.6 https://www.php.net/manual/en/function.password-hash.php – NVRM Apr 09 '21 at 11:25

14 Answers14

1006

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.

Aurelio
  • 20,064
  • 8
  • 54
  • 61
Robert K
  • 28,868
  • 12
  • 59
  • 77
  • I see. But in my case, the person may have multiple emails. Should I use the ID(BIGINT primary key) instead? – luiscubal Dec 30 '08 at 22:19
  • @luiscubal: as long as the values for your salt come from a sufficiently large space, it will be a good salt. Your ID value will probably suffice, especially if the number of records is large (the number of IDs will be large). – rmeador Dec 30 '08 at 22:26
  • you could use anything that the user only has one of, but ideally something that doesn't change. – Tom Haigh Dec 30 '08 at 22:26
  • I'd include something secret as well. The point is, you want the hash function's input to be consistent for a correct password, but you also want it to be nonguessable so someone can't brute-force guess the password offline. (vs. having to do it through your webpage which is much slower) – Jason S Dec 30 '08 at 23:02
  • 9
    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. – frankodwyer Dec 30 '08 at 23:07
  • 2
    note, it's not really true that 'the computational power to decrypt' doesn't exist yet. since most passwords are dictionary words or dictionary derived, a dictionary based attack is usually very effective (hence the use of password policies and iteration counts). – frankodwyer Dec 31 '08 at 00:51
  • Frank, while what you say is true, the algorithmic cycles it takes to generate the words doesn't make it feasible. Don't forget the inclusion of the salt. Brute force will always work if they know your hash algorithm and salt, otherwise it's _virtually_ impossible. – Robert K Dec 31 '08 at 13:52
  • But that doesn't make insecure passwords any safer. It just makes the database rows safer if compromised, at which point you should change your salt immediately and inform your users to change their passwords. – Robert K Dec 31 '08 at 13:54
  • Even when the salt and algorithm are known, finding a password by brute-force search will take many years if the proper method is used. Passwords are cracked when they are predicted by following a pattern, such as transforming dictionary words and incorporating other cribs. An essential part of the method is some sort of computationally intensive key stretching technique, like using thousands of iterations of the hash function. – erickson May 10 '09 at 22:02
  • 42, if I were designing a truly secure system, I'd suggest a military grade hash algorithm anyway with a ~16bit randomly generated number stored in the database. In general use, a salt with a password is better than none--unless either is compromised. I'm not claiming to be an expert, nothing is better than enforced password rotation plus a military-grade algorithm, but for basic non-critical software this is enough. – Robert K May 26 '09 at 00:11
  • 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 :) – m42 May 26 '09 at 00:16
  • Absolutely! I've just shared what I've found. I found a number of things from Shneier on Security and a very long (convoluted) discussion on a news site (don't remember which now). – Robert K May 26 '09 at 00:28
  • 2
    *"Even MD5 is still safe, as aside from a brute-force attack the computational power needed to decrypt doesn't exist yet."* OMG, I don't believe it. MD5 can't be "decrypted", period. But it is definitly *not* safe, as it is. **Ever heard of rainbow tables?** – o0'. May 05 '10 at 10:16
  • 2
    Look, if you're using MD5 on software that requires high security (like a store) then YOU'RE the fool, not me. I know that MD5 is extremely fast, but if you're designing forum software with good practices it's still generally secure. – Robert K May 20 '10 at 14:14
  • 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 07 '10 at 20:07
  • Excellent post. Answered many of my own questions. However, I am uncertain of what it means to save the site_key in the file system. Could you please explain? For me, even a .php file in a folder on the server is the file system. – Francisc Apr 05 '11 at 14:08
  • The article that **42** linked 404s—here's a copy http://chargen.matasano.com/chargen/2007/9/7/enough-with-the-rainbow-tables-what-you-need-to-know-about-s.html – Drew Stephens Jun 08 '11 at 16:32
  • 1
    I'll update it about bcrypt sometime. I've been busy and the scheme I suggested is at least moderately effective. Better that than someone just throwing a plain md5/sha1 hashed password in their DB. – Robert K Aug 16 '11 at 12:17
  • no other way to save random number of $nonce except save it to db? If the hacker can get hashed_password from db, sure he able to get nonce from db too – zac1987 Jan 02 '12 at 18:18
  • Great revision. I've been using bcrypt for some time now, and I guess I'll start looking into scrypt for future projects. – luiscubal May 25 '12 at 17:50
  • 2
    I think even emailing a new password in case of a lost one is a bad idea. Why not email a link to a secure site that allows the user to change his password? That's much more comfortable (no need to change the password after logging in with the random one) and more secure since no password went over the wire unencrypted - remember, users are lazy and chances are good that they do not change the random password. – ThiefMaster Jun 11 '12 at 02:03
  • 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. – Tim Gautier Aug 02 '12 at 21:10
  • 1
    Regarding your most recent edit: "require at least 1 upper case letter, 1 lower case letter, a number, and a symbol" is in direct contradiction with the xkcd comic in the link. It shows an example of a password with *no symbols*, but *high entropy*. – luiscubal Jan 05 '13 at 23:03
  • @luiscubal I'm not sure Randall is right, so I don't 100% endorse his suggestion though I've tried it. With lowercase/spaces your entropy is ~4.75/character, which for 28 characters is about 131-133 bits of entropy. But there's only four words there, with ~1 million words in the English language that's 1e+24 combinations versus upper/lower/symbols at 28 characters is 3.18855e+55 or so. Anyway, I'm not 100% convinced. – Robert K Jan 07 '13 at 14:08
  • 2
    @RobertK Humans are imperfect random number generators, which greatly lowers the entropy in both cases. They won't pick a perfectly random `[a-zA-Z0-9_$%#]{28}` sequence - they are more likely to just pick something like `$justinBieber28`. – luiscubal Jan 07 '13 at 14:26
  • @luiscubal You are correct. However a password manager that can do just that is *invaluable*. Which is the other problem with Randall's suggestion: his method produces pseudorandom combinations less random than our pseudorandom gibberish passwords. That's why it was only a suggestion of something to do. One's probably as secure as the other (for now). – Robert K Jan 07 '13 at 16:04
  • @RobertK Note that he never truly checks the entropy of "complete gibberish", because people tend not to use complete gibberish for passwords. Computer-generated passwords can be much better than human-generated passwords, but how many people would just hit the "regenerate" button until they got something easy to remember? Or even tweak the result to be simpler? All that lowers entropy. – luiscubal Jan 07 '13 at 16:12
  • @luiscubal I said a password *manager* not just a generator. I personally use LastPass to generate/remember passwords, and just make the best password I can to lock that service down. That keeps entropy high for other sites. As a user it's the best solution I've come across so far. – Robert K Jan 07 '13 at 17:52
  • Just read the update, and it looks much better. Removing my -1. Perhaps it would be worth mentioning the new 5.5 APIs: http://php.net/manual/en/ref.password.php and the compatibility project: https://github.com/ircmaxell/password_compat And thanks for updating the answer!!! – ircmaxell Jan 14 '13 at 20:17
  • 1
    requiring at least 1 upper case letter, 1 lower case letter, a number, and a symbol will not improve the entropy of the password, it will just make it harder for people to remember. – Popnoodles Jan 19 '13 at 16:19
  • @popnoodles By expanding the character set and the number of used characters it expands the bits of entropy per character, it does NOT safeguard against non-random passwords by users. – Robert K Jan 21 '13 at 19:28
  • 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. – Popnoodles Jan 25 '13 at 00:17
  • @popnoodles That's an excellent point that I totally missed: I gave the suggestion as placing a hard-requirement that would limit the number of possible plaintexts. Thanks for pointing that out! – Robert K Jan 25 '13 at 15:52
  • "This will improve the entropy of the password" - if you force somebody to use at least one number then you've reduced the entropy of the password as there are only 10 digits to choose from – PP. Sep 12 '13 at 15:28
  • @RobertK not sure why you personally attacked me with sarcasm there - "cliff-notes version". I'll ask the moderators to give you a temporary suspension because it was out of order. – PP. Sep 13 '13 at 11:29
  • @PP. I don't understand what you read as sarcasm. The TL;DR section at the top of my answer is the "cliff-notes version" of the long part of my answer. All I asked was that you respond to specific points in the relevant section, rather than the TL;DR summary which doesn't contain any of the debate about the subject. – Robert K Sep 13 '13 at 15:19
  • 1
    Also consider hashing it on client side, this way even if the server got corrupted in any way, the original passwords cannot be retrieved! –  Nov 07 '13 at 11:01
  • @derylius Unless your log in code is delivered in a plugin, you cannot verify that the javascript code is your original code with no compromises. Also, sending a hash to be compared against a hash in the DB is just as dangerous (it takes the place of the password). This isn't a very good, or safe, idea. It's better to rely upon connection security than it is javascript security. – Robert K Nov 07 '13 at 13:58
  • @RobertK I guess you misunderstood me. I didn't say do this instead of setting up a secure connection, what I suggested is to do this in addition, cause even if the connection is secure, if the server get compromised, then every single request sent to that server potentially shows plain passwords to the attackers. So my point is, that as a user I don't want to trust any server, but that doesn't mean I want to remember a 64 character long hash string instead of the color of my favorite fruit. (Further explanation here: http://xkcd.com/936/) –  Nov 07 '13 at 14:52
  • @derylius Yeah, I probably misunderstood. However, sending the hash to the server is just as bad. Though I think [SQRL](https://www.grc.com/sqrl/sqrl.htm) by Steve Gibson shows really good promise. – Robert K Nov 07 '13 at 18:11
  • @RobertK its really just a small addition that protects the password itself, not the environment. But since the passwords must be hashed anyway to store it, hashing it on client side instead of server side is not so much extra code adding some to the security without taking out any of it. –  Nov 08 '13 at 09:50
  • 2
    I'm also of the opinion that passwords should not expire. Forcing users to create new passwords and disallowing them from using previously used passwords results in users choosing worse and worse combinations as they can never remember them. Sure they shouldn't be remembering anyway, but I'm talking reality not ideal world. – MrYellow Nov 05 '14 at 01:34
  • @MrYellow In general I dislike password expiry too. But preventing the use of previous passwords is a good idea for high security applications. After all, you wouldn't want somebody alternating passwords on something highly sensitive. But I left those out because they felt like subjective conclusions. Those are harder to prove than "X algorithm can be calculated in Y M-hashes/sec". – Robert K Nov 05 '14 at 14:08
  • @RobertK I've heard strategies from people who will just reset their password 15 times to expire the "previously used" list and get back to a password they can remember... yeah agree the numbers aren't cut and dry on this part of the question. It's debatable and the correct choice is different depending on systems purpose. – MrYellow Nov 05 '14 at 20:11
  • @MrYellow Yeah, I'm not surprised. About the only way the password expiry scheme would work is if past passwords were stored, then levenshtein distance was used to validate a new password. It's tyrannical, but it's about the only way to ensure users don't pick "password" then "password1", then "password2", etc. (Not that I advise doing this...) – Robert K Nov 06 '14 at 14:05
  • Regarding forcing users to implement a password with every type of character: first it will piss them off and make them go away, two, it is not even necessary/useful. Instead just start locking out the account after too many attempts/too quick of attempts Even if you just restrict it to 1 password attempt per second, you've crippled a brute force cracker's options, no? – Andrew Oct 28 '15 at 16:47
  • How to recover password when users forgot it? – Iman Marashi Nov 08 '15 at 20:30
  • @ImanMarashi the way I would do it is(and please do correct me if I am a dumbass:P): make a forgot password page and let him fill in a field with his email address. When the user clicks on send you generate a random password [maybe this will help](http://stackoverflow.com/questions/6101956/generating-a-random-password-in-php) (which ofcourse should be hashed again) and send an email to him with the random password inside and a link the page of his account and tell him to change his password. That's how I would do it maybe I am totally wrong but then I'm interested to see how it should be done – BRoebie Dec 29 '15 at 10:00
  • @BRoebie I don't advise changing the user's password, because someone fishing for users/emails could then lock users out. Instead, generate a token with a short expiry time (like < 15 minutes) and use that token in the email's link to a reset page. That's more secure. But reseting passwords via email is never particularly secure (IMO). – Robert K Dec 30 '15 at 17:54
  • Thank you for your reply. I will definitely use that method. I will look around the internet for some examples. Can you give me a timestamp I should use before it expires. For example would an hour be acceptable too? I know that it would be less secure but some mail services are quite slow – BRoebie Dec 30 '15 at 18:05
  • 2
    I have to disagree with requiring an uppercase letter, lowercase letter, a number, and a symbol all at once. All that's going to do, is make people enter "Password1!" instead of "password". Or worse, they'd write it down on post-its. Such restrictions are irrelevant for password manager users, but even then, don't implement such harsh restrictions unless you're a bank or payment service. – Hugo Zink Jan 12 '16 at 09:40
  • 1
    In place of scrypt and bcrypt you should nowadays probably be looking at [argon2](https://password-hashing.net/), which won the password hashing competition. – Thom Wiggers Feb 02 '16 at 21:05
  • Limiting a password to <200 characters is fine IMHO; if the user only entered `0`s and `1`s they'd still have basically unbreakable entropy, or for the sake of argument, 1000 chars. There's no point to going way, *way* over 256 bits of entropy just to say there's "no limit". Are you happy to hash a multi-GB password I give you? An alternative workaround: make the user hash their 50 GB password with SHA-2 or whatever and pretend that's the password. Bam, all 'passwords' you receive are 32 bytes long (still need to use a password hash to actually store it though). – Nick T Oct 01 '18 at 19:43
  • @NickT Most web servers would reject a 50GB password due to exceeding upload limits and timeout on input, so I feel that that point is moot. So long as your limit is 64+, it'd be find in my opinion. (You might want to check the age of this answer, though.) – Robert K Oct 03 '18 at 21:02
143

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).

For example, you can 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. That was in 2012, as of 2018 you could use fewer GPUs, or crack faster with 25 GPUs.

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 - 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.
RichVel
  • 3,992
  • 4
  • 24
  • 38
  • 2
    but these systems are better known and maybe already compromised. but it beats making your own when you don't know what your doing. – JqueryToAddNumbers Feb 11 '12 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 '12 at 12:24
  • Given the recent password hash dumps from LinkedIn, Last.fm and others, this is quite topical. You are in good company in not knowing how to write your own password mechanism! – RichVel Jul 05 '12 at 14:11
  • 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 '13 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 '13 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. – CubicleSoft Feb 08 '15 at 12:57
  • @CubicleSoft - can you recommend another PHP library for password hashing? Unless there's a better library, phpass will still be better than the vast majority of custom code that uses unsalted and unstretched MD5. – RichVel Feb 09 '15 at 10:16
  • 1
    @RichVel - The password_hash() function. As mentioned earlier, it's built into PHP core (aka /ext/standard). – CubicleSoft Mar 15 '15 at 12:10
  • @CubicleSoft - password_hash() defaults to bcrypt and has a cost function for stretching, so it seems good for people on PHP 5.5 or higher - http://php.net/manual/en/function.password-hash.php. phpass supports earlier PHP versions where it is certainly better than _most people's_ custom code built on MD5 etc. – RichVel Mar 19 '15 at 08:55
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.

Tom Haigh
  • 54,886
  • 20
  • 107
  • 138
  • not for password hashing. the attacker only needs to break one hash to retrieve the password. the point is moot anyway as neither MD5 nor SHA1 have any practical breaks available in the password scenario. – frankodwyer Dec 30 '08 at 23:05
  • 3
    sorry, i misread your answer as recommending using two hashes...you are in fact correct. Using two hashes weakens the system in the password case, as they only need to break the weaker hash. – frankodwyer Dec 30 '08 at 23:37
43

As of PHP 5.5, PHP has simple, secure functions for hashing and verifying passwords, password_hash() and password_verify()

$password = 'anna';
$hash = password_hash($password, PASSWORD_DEFAULT);
$expensiveHash = password_hash($password, PASSWORD_DEFAULT, array('cost' => 20));

password_verify('anna', $hash); //Returns true
password_verify('anna', $expensiveHash); //Also returns true
password_verify('elsa', $hash); //Returns false

When password_hash() is used, it generates a random salt and includes it in the outputted hash (along with the the cost and algorithm used.) password_verify() then reads that hash and determines the salt and encryption method used, and verifies it against the provided plaintext password.

Providing the PASSWORD_DEFAULT instructs PHP to use the default hashing algorithm of the installed version of PHP. Exactly which algorithm that means is intended to change over time in future versions, so that it will always be one of the strongest available algorithms.

Increasing cost (which defaults to 10) makes the hash harder to brute-force but also means generating hashes and verifying passwords against them will be more work for your server's CPU.

Note that even though the default hashing algorithm may change, old hashes will continue to verify just fine because the algorithm used is stored in the hash and password_verify() picks up on it.

AlliterativeAlice
  • 9,983
  • 8
  • 41
  • 65
33

Though the question has been answered, I just want to reiterate that salts used for hashing should be random and not like email address as suggested in first answer.

More explanation is available at- http://www.pivotalsecurity.com/blog/password-hashing-salt-should-it-be-random/

Recently I had a discussion whether password hashes salted with random bits are more secure than the one salted with guessable or known salts. Let’s see: If the system storing password is compromised as well as the system which stores the random salt, the attacker will have access to hash as well as salt, so whether the salt is random or not, doesn’t matter. The attacker will can generate pre-computed rainbow tables to crack the hash. Here comes the interesting part- it is not so trivial to generate pre-computed tables. Let us take example of WPA security model. Your WPA password is actually never sent to Wireless Access Point. Instead, it is hashed with your SSID (the network name- like Linksys, Dlink etc). A very good explanation of how this works is here. In order to retrieve password from hash, you will need to know the password as well as salt (network name). Church of Wifi has already pre-computed hash tables which has top 1000 SSIDs and about 1 million passwords. The size is of all tables is about 40 GB. As you can read on their site, someone used 15 FGPA arrays for 3 days to generate these tables. Assuming victim is using the SSID as “a387csf3″ and password as “123456″, will it be cracked by those tables? No! .. it cannot. Even if the password is weak, the tables don’t have hashes for SSID a387csf3. This is the beauty of having random salt. It will deter crackers who thrive upon pre-computed tables. Can it stop a determined hacker? Probably not. But using random salts does provide additional layer of defense. While we are on this topic, let us discuss additional advantage of storing random salts on a separate system. Scenario #1 : Password hashes are stored on system X and salt values used for hashing are stored on system Y. These salt values are guessable or known (e.g. username) Scenario#2 : Password hashes are stored on system X and salt values used for hashing are stored on system Y. These salt values are random. In case system X has been compromised, as you can guess, there is a huge advantage of using random salt on a separate system (Scenario #2) . The attacker will need to guess addition values to be able to crack hashes. If a 32 bit salt is used, 2^32= 4,294,967,296 (about 4.2 billion) iterations will can be required for each password guessed.

infojolt
  • 4,786
  • 2
  • 28
  • 72
Gaurav Kumar
  • 341
  • 3
  • 2
  • 7
    Even if the attacker gets the salt, a "sitesalt:usersalt:password" string is still resistant to precomputed tables, since the attacker needs to generate the tables for every user(so the attack becomes far slower), unless of course a specific user is being targeted... – luiscubal Feb 12 '10 at 16:01
  • Regarding "Even if the attacker gets the salt, a "sitesalt:usersalt:password" string is still resistant to precomputed tables" , Totally agree. My point is that sitesalt if made random and long, will make system more secure than it (sitesalt) being predictable. I've seen some people recommending use of email id etc as salt, and I discourage that. – Gaurav Kumar Feb 13 '10 at 01:20
  • You missed what I originally wrote. I said to use a random nonce, stored with the record, PLUS the email address. The addition of the email address makes for extra entropy for the hacker to work on. I've since rewritten my answer in favor of bcrypt. – Robert K Mar 23 '12 at 01:15
28

I just want to point out that PHP 5.5 includes a password hashing API that provides a wrapper around crypt(). This API significantly simplifies the task of hashing, verifying and rehashing password hashes. The author has also released a compatibility pack (in the form of a single password.php file that you simply require to use), for those using PHP 5.3.7 and later and want to use this right now.

It only supports BCRYPT for now, but it aims to be easily extended to include other password hashing techniques and because the technique and cost is stored as part of the hash, changes to your prefered hashing technique/cost will not invalidate current hashes, the framework will automagically, use the correct technique/cost when validating. It also handles generating a "secure" salt if you do not explicitly define your own.

The API exposes four functions:

  • password_get_info() - returns information about the given hash
  • password_hash() - creates a password hash
  • password_needs_rehash() - checks if the given hash matches the given options. Useful to check if the hash conforms to your current technique/cost scheme allowing you to rehash if necessary
  • password_verify() - verifies that a password matches a hash

At the moment these functions accept the PASSWORD_BCRYPT and PASSWORD_DEFAULT password constants, which are synonymous at the moment, the difference being that PASSWORD_DEFAULT "may change in newer PHP releases when newer, stronger hashing algorithms are supported." Using PASSWORD_DEFAULT and password_needs_rehash() on login (and rehashing if necessary) should ensure that your hashes are reasonably resilient to brute-force attacks with little to no work for you.

EDIT: I just realised that this is mentioned briefly in Robert K's answer. I'll leave this answer here since I think it provides a bit more information about how it works and the ease of use it provides for those who don't know security.

halfer
  • 18,701
  • 13
  • 79
  • 158
JonoCoetzee
  • 959
  • 2
  • 14
  • 29
19

I'm using Phpass which is a simple one-file PHP class that could be implemented very easily in nearly every PHP project. See also The H.

By default it used strongest available encryption that is implemented in Phpass, which is bcrypt and falls back to other encryptions down to MD5 to provide backward compatibility to frameworks like Wordpress.

The returned hash could be stored in database as it is. Sample use for generating hash is:

$t_hasher = new PasswordHash(8, FALSE);
$hash = $t_hasher->HashPassword($password);

To verify password, one can use:

$t_hasher = new PasswordHash(8, FALSE);
$check = $t_hasher->CheckPassword($password, $hash);
rabudde
  • 6,888
  • 5
  • 43
  • 83
14

THINGS TO REMEMBER

A lot has been said about Password encryption for PHP, most of which is very good advice, but before you even start the process of using PHP for password encryption make sure you have the following implemented or ready to be implemented.

SERVER

PORTS

No matter how good your encryption is if you don't properly secure the server that runs the PHP and DB all your efforts are worthless. Most servers function relatively the same way, they have ports assigned to allow you to access them remotely either through ftp or shell. Make sure that you change the default port of which ever remote connection you have active. By not doing this you in effect have made the attacker do one less step in accessing your system.

USERNAME

For all that is good in the world do not use the username admin, root or something similar. Also if you are on a unix based system DO NOT make the root account login accessible, it should always be sudo only.

PASSWORD

You tell your users to make good passwords to avoid getting hacked, do the same. What is the point in going through all the effort of locking your front door when you have the backdoor wide open.

DATABASE

SERVER

Ideally you want your DB and APPLICATION on separate servers. This is not always possible due to cost, but it does allow for some safety as the attacker will have to go through two steps to fully access the system.

USER

Always have your application have its own account to access the DB, and only give it the privileges it will need.

Then have a separate user account for you that is not stored anywhere on the server, not even in the application.

Like always DO NOT make this root or something similar.

PASSWORD

Follow the same guidelines as with all good passwords. Also don't reuse the same password on any SERVER or DB accounts on the same system.

PHP

PASSWORD

NEVER EVER store a password in your DB, instead store the hash and unique salt, I will explain why later.

HASHING

ONE WAY HASHING!!!!!!!, Never hash a password in a way that it can be reversed, Hashes should be one way, meaning you don't reverse them and compare them to the password, you instead hash the entered password the same way and compare the two hashes. This means that even if an attacker gets access to the DB he doesn't know what the actually password is, just its resulting hash. Which means more security for your users in the worst possible scenario.

There are a lot of good hashing functions out there (password_hash, hash, etc...) but you need to select a good algorithm for the hash to be effective. (bcrypt and ones similar to it are decent algorithms.)

When hashing speed is the key, the slower the more resistant to Brute Force attacks.

One of the most common mistakes in hashing is that hashes are not unique to the users. This is mainly because salts are not uniquely generated.

SALTING

Passwords should always be salted before hashed. Salting adds a random string to the password so similar passwords don't appear the same in the DB. However if the salt is not unique to each user (ie: you use a hard coded salt) than you pretty much have made your salt worthless. Because once an attacker figures out one password salt he has the salt for all of them.

When you create a salt make sure it is unique to the password it is salting, then store both the completed hash and salt in your DB. What this will do is make it so that an attacker will have to individually crack each salt and hash before they can gain access. This means a lot more work and time for the attacker.

USERS CREATING PASSWORDS

If the user is creating a password through the frontend that means it has to be sent to the server. This opens up a security issue because that means the unencrypted password is being sent to the server and if a attacker is able to listen and access that all your security in PHP is worthless. ALWAYS transmit the data SECURELY, this is done through SSL, but be weary even SSL is not flawless (OpenSSL's Heartbleed flaw is an example of this).

Also make the user create a secure password, it is simple and should always be done, the user will be grateful for it in the end.

Finally, no matter the security measures you take nothing is 100% secure, the more advanced the technology to protect becomes the more advanced the attacks become. But following these steps will make your site more secure and far less desirable for attackers to go after.

Here is a PHP class that creates a hash and salt for a password easily

http://git.io/mSJqpw

wmfrancia
  • 1,186
  • 2
  • 9
  • 24
  • 1
    You should strike SHA512 from your list of decent hash algorithms, because it is too fast. Use it only in combination with PBKDF2. While BCrypt is based on blowfish, blowfish itself is an algorithm for encryption, not for hashing. – martinstoeckli Apr 09 '14 at 15:24
  • 1
    How do you store the random salt in the DB? I think you don't hash it (cannot be used for verification) nor store in clear (no real benefits if the attacker can read the DB). So, how you do it? – Iazel Jun 12 '14 at 23:00
  • wmfrancia wrote: "Salting adds a random string to the password so similar passwords don't appear the same in the DB". This does not make sense to me. Hashes in the DB will already appear dissimilar because that is a property of hash functions. – H2ONaCl Feb 26 '15 at 03:30
  • wmfancia wrote in regard to a constant salt: "once an attacker figures out one password salt he has the salt for all of them". The same can be said that if the hacker figures out which DB field is the salt, he has the salts for all of them. Since a constant salt would probably not be in the DB, that is a good thing about a constant salt. – H2ONaCl Feb 26 '15 at 03:39
  • Of course, these comments are not to suggest a random salt per user is not better than one salt per application. It is better. – H2ONaCl Feb 26 '15 at 07:35
12

Google says SHA256 is available to PHP.

You should definitely use a salt. I'd recommend using random bytes (and not restrict yourself to characters and numbers). As usually, the longer you choose, the safer, slower it gets. 64 bytes ought to be fine, i guess.

AticusFinch
  • 2,071
  • 3
  • 24
  • 31
  • 13
    64 bits ought to be enough for anyone? – Konerak Jun 21 '11 at 12:32
  • @Konerak, I'd get back to this after 20 years. :) But yep SHA256 is indeed available. If you want to know how secure SHA256 is, you might want to check this out: http://security.stackexchange.com/questions/90064/how-secure-are-sha256-salt-hashes-for-password-storage – Vincent Edward Gedaria Binua Dec 03 '15 at 22:18
8

I found perfect topic on this matter here: https://crackstation.net/hashing-security.htm, I wanted you to get benefit from it, here is source code also that provided prevention against time-based attack also.

<?php
/*
 * Password hashing with PBKDF2.
 * Author: havoc AT defuse.ca
 * www: https://defuse.ca/php-pbkdf2.htm
 */

// These constants may be changed without breaking existing hashes.
define("PBKDF2_HASH_ALGORITHM", "sha256");
define("PBKDF2_ITERATIONS", 1000);
define("PBKDF2_SALT_BYTES", 24);
define("PBKDF2_HASH_BYTES", 24);

define("HASH_SECTIONS", 4);
define("HASH_ALGORITHM_INDEX", 0);
define("HASH_ITERATION_INDEX", 1);
define("HASH_SALT_INDEX", 2);
define("HASH_PBKDF2_INDEX", 3);

function create_hash($password)
{
    // format: algorithm:iterations:salt:hash
    $salt = base64_encode(mcrypt_create_iv(PBKDF2_SALT_BYTES, MCRYPT_DEV_URANDOM));
    return PBKDF2_HASH_ALGORITHM . ":" . PBKDF2_ITERATIONS . ":" .  $salt . ":" . 
        base64_encode(pbkdf2(
            PBKDF2_HASH_ALGORITHM,
            $password,
            $salt,
            PBKDF2_ITERATIONS,
            PBKDF2_HASH_BYTES,
            true
        ));
}

function validate_password($password, $good_hash)
{
    $params = explode(":", $good_hash);
    if(count($params) < HASH_SECTIONS)
       return false; 
    $pbkdf2 = base64_decode($params[HASH_PBKDF2_INDEX]);
    return slow_equals(
        $pbkdf2,
        pbkdf2(
            $params[HASH_ALGORITHM_INDEX],
            $password,
            $params[HASH_SALT_INDEX],
            (int)$params[HASH_ITERATION_INDEX],
            strlen($pbkdf2),
            true
        )
    );
}

// Compares two strings $a and $b in length-constant time.
function slow_equals($a, $b)
{
    $diff = strlen($a) ^ strlen($b);
    for($i = 0; $i < strlen($a) && $i < strlen($b); $i++)
    {
        $diff |= ord($a[$i]) ^ ord($b[$i]);
    }
    return $diff === 0; 
}

/*
 * PBKDF2 key derivation function as defined by RSA's PKCS #5: https://www.ietf.org/rfc/rfc2898.txt
 * $algorithm - The hash algorithm to use. Recommended: SHA256
 * $password - The password.
 * $salt - A salt that is unique to the password.
 * $count - Iteration count. Higher is better, but slower. Recommended: At least 1000.
 * $key_length - The length of the derived key in bytes.
 * $raw_output - If true, the key is returned in raw binary format. Hex encoded otherwise.
 * Returns: A $key_length-byte key derived from the password and salt.
 *
 * Test vectors can be found here: https://www.ietf.org/rfc/rfc6070.txt
 *
 * This implementation of PBKDF2 was originally created by https://defuse.ca
 * With improvements by http://www.variations-of-shadow.com
 */
function pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output = false)
{
    $algorithm = strtolower($algorithm);
    if(!in_array($algorithm, hash_algos(), true))
        die('PBKDF2 ERROR: Invalid hash algorithm.');
    if($count <= 0 || $key_length <= 0)
        die('PBKDF2 ERROR: Invalid parameters.');

    $hash_length = strlen(hash($algorithm, "", true));
    $block_count = ceil($key_length / $hash_length);

    $output = "";
    for($i = 1; $i <= $block_count; $i++) {
        // $i encoded as 4 bytes, big endian.
        $last = $salt . pack("N", $i);
        // first iteration
        $last = $xorsum = hash_hmac($algorithm, $last, $password, true);
        // perform the other $count - 1 iterations
        for ($j = 1; $j < $count; $j++) {
            $xorsum ^= ($last = hash_hmac($algorithm, $last, $password, true));
        }
        $output .= $xorsum;
    }

    if($raw_output)
        return substr($output, 0, $key_length);
    else
        return bin2hex(substr($output, 0, $key_length));
}
?>
Jason OOO
  • 3,447
  • 2
  • 21
  • 28
8

In the end, double-hashing, mathematically, provides no benefit. In practice, however, it is useful for preventing rainbow table-based attacks. In other words, it is of no more benefit than hashing with a salt, which takes far less processor time in your application or on your server.

Max
  • 1,024
  • 10
  • 19
  • 2
    multiple hashing also protects against dictionary and brute force attacks - i.e. it simply makes them take longer to compute. – frankodwyer Dec 30 '08 at 23:00
  • 6
    double hashing won't give you a significant advantage but multi round hashing iterations are still a feasible defense against dictionary and bruce force attacks. Industrial strength password hashes use 1000+ rounds. PKCS#5's PBKDF1 suggests 1000 rounds minimum. – Berk D. Demir Jan 01 '09 at 22:17
6

I usually use SHA1 and salt with the user ID (or some other user-specific piece of information), and sometimes I additionally use a constant salt (so I have 2 parts to the salt).

SHA1 is now also considered somewhat compromised, but to a far lesser degree than MD5. By using a salt (any salt), you're preventing the use of a generic rainbow table to attack your hashes (some people have even had success using Google as a sort of rainbow table by searching for the hash). An attacker could conceivably generate a rainbow table using your salt, so that's why you should include a user-specific salt. That way, they will have to generate a rainbow table for each and every record in your system, not just one for your entire system! With that type of salting, even MD5 is decently secure.

rmeador
  • 24,922
  • 16
  • 59
  • 99
4

SHA1 and a salt should suffice (depending, naturally, on whether you are coding something for Fort Knox or a login system for your shopping list) for the foreseeable future. If SHA1 isn't good enough for you, use SHA256.

The idea of a salt is to throw the hashing results off balance, so to say. It is known, for example, that the MD5-hash of an empty string is d41d8cd98f00b204e9800998ecf8427e. So, if someone with good enough a memory would see that hash and know that it's the hash of an empty string. But if the string is salted (say, with the string "MY_PERSONAL_SALT"), the hash for the 'empty string' (i.e. "MY_PERSONAL_SALT") becomes aeac2612626724592271634fb14d3ea6, hence non-obvious to backtrace. What I'm trying to say, that it's better to use any salt, than not to. Therefore, it's not too much of an importance to know which salt to use.

There are actually websites that do just this - you can feed it a (md5) hash, and it spits out a known plaintext that generates that particular hash. If you would get access to a database that stores plain md5-hashes, it would be trivial for you to enter the hash for the admin to such a service, and log in. But, if the passwords were salted, such a service would become ineffective.

Also, double-hashing is generally regarded as bad method, because it diminishes the result space. All popular hashes are fixed-length. Thus, you can have only a finite values of this fixed length, and the results become less varied. This could be regarded as another form of salting, but I wouldn't recommend it.

Henrik Paul
  • 63,711
  • 30
  • 82
  • 93
  • The target site shouldn't contain anything too sensitive(it's not a bank), but still I'd rather have it secured. – luiscubal Dec 30 '08 at 22:24
  • 1
    double hashing does not reduce the result space. iterative hashing is a common control against dictionary and brute force attacks (it slows them down much more than it slows down your password checking). – frankodwyer Dec 30 '08 at 23:10
  • 2
    @frankodwyer: yes, it is bad. `sha1(sha1($foo))` effectively reduces the output space, because any collision of the inner function will automatically become a collision of the outer one. The degradation is linear, but it's still a concern. The iterative hashing methods feed data back in on each round, such as `$hash = sha1(sha1($salt . $password) . $salt)`. But that's still not good... Stick with PBKDF2 or Bcrypt... – ircmaxell Sep 13 '12 at 14:40
-7

ok in the fitsy we need salt salt must be unique so let generate it

   /**
     * Generating string
     * @param $size
     * @return string
     */
    function Uniwur_string($size){
        $text = md5(uniqid(rand(), TRUE));
        RETURN substr($text, 0, $size);
    }

also we need the hash I`m using sha512 it is the best and it is in php

   /**
     * Hashing string
     * @param $string
     * @return string
     */
    function hash($string){
        return hash('sha512', $string);
    }

so now we can use this functions to generate safe password

// generating unique password
$password = Uniwur_string(20); // or you can add manual password
// generating 32 character salt
$salt = Uniwur_string(32);
// now we can manipulate this informations

// hashin salt for safe
$hash_salt = hash($salt);
// hashing password
$hash_psw = hash($password.$hash_salt);

now we need to save in database our $hash_psw variable value and $salt variable

and for authorize we will use same steps...

it is the best way to safe our clients passwords...

P.s. for last 2 steps you can use your own algorithm... but be sure that you can generate this hashed password in the future when you need to authorize user...

shalvasoft
  • 91
  • 1
  • 9
  • 4
    This question was about hashes for passwords. 1 execution of `sha512` (even if salted) is widely considered to not be good enough for password protection. (also that RNG is not cryptographically secure, so using it for password generation is risky). – luiscubal Oct 08 '15 at 13:53
  • 2
    You have no idea what you're doing. Read the top answers in this post and you can see why your code is not just insecure, but makes no sense. – cryptic ツ Oct 09 '15 at 18:12
  • ok. my code is not secure. so let me know why are you using in your algorithms ony sha256??? I know that sha512 is the best why not use it??? – shalvasoft Oct 10 '15 at 08:59
  • 1
    @shalvasoft sha512 is pretty good for general purpose hashing, but password protection requires hashes with very specific properties ("being slow" is strangely a *good thing*, for instance, and sha512 is pretty fast). Some people have used sha512 as a building block to create password hashing functions, but nowadays the recommended approach is "use bcrypt and keep an eye on scrypt". – luiscubal Oct 11 '15 at 18:37