Secure hash and salt for PHP passwords

    |
  • Added:
  • |
  • In: Basic PHP

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

This Question Has 14 Answeres | Orginal Question | luiscubal

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.
  • 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 his 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.

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.

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

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

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.

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.

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.

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.

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.

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

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.

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)); } ?> 

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.
  • Older PHP versions: 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 2016 you could use fewer 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 - 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.

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.


Search
I am...

Sajjad Hossain

I have five years of experience in web development sector. I love to do amazing projects and share my knowledge with all.

NEED HOSTING?
Are you searching for Good Quality hosting?
You can Try It!
Connect Social With PHPAns
Top