可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I want to implement a salt into my login system but am a bit confused on how this is supposed to work. I can't understand the logic behind it. I understand md5 is a one-way algorithm and all of the functions that I have come across seem to hash everything together. If this is the case, how does one get the password back out for comparison? My biggest question is, how is salting a users' password safer than just hashing the password? If a database was ever to be compromised, the hash along with the salt is in the database. Isn't this all that a hacker would need?
I also found another post here on SO where another developer said :
"Ensure your salt and algorithm are
stored separately from the database"
I would like to store the salt in the database. Is this really a problem if I do?
I'm looking for some help on understanding how this works and also what the best practice might be. Any help is greatly appreciated.
EDIT:
I want to thank everyone for their responses and ideas. Even though I may be more confused now, it has certainly been a learning experience for me. Thanks again guys.
回答1:
An hash function always return the same value for the same input string. Let's say my user (Alice) has the password secret
. Hashing secret
using md5()
leads to the following hash
5ebe2294ecd0e0f08eab7690d2a6ee69
Using a dictionary (a list of common words and password) or one of the various sites that offer you that service, the attacker (Mallory) can easily find out the password is secret when he sees in his dictionary that 5ebe2294ecd0e0f08eab7690d2a6ee69 = secret
.
The process of salting before hashing makes it harder to use a dictionary attack without knowing your salt. Consider the following:
<?php
$salt = '@!#%$@#$@SADLkwod,sdaDwqksjaoidjwq@#@!';
$hash = md5($salt . 'secret');
The resulting hash is now b58ad809eece17322de5024d79299f8a
but Alice's password is still secret
. Now if Mallory gets her hands on the salted hash, chances are she will not find the answer in her dictionary. If she does, the dictionary will give her the wrong answer.
Never store a static salt in your database. Preferably store it with your application's configuration (which by the way should not be available from the web).
If you are going to use a dynamic salt, you are going to need to use the database. Use a non-null column of existing valid data to build your salt on (blowfish-encrypted string of username based on a secret encryption key is usually cryptographically secure). Do not use a separate column for the salt. If you cannot use an existing column, incorporate your salt in the same column than your hash. For example, use the first 32 characters for your 128-bits salt and then the last 40 for your 160-bits hash. The following function will generate such an hash:
function seeded_sha1($string, $seed_bits) {
if(($seed_bits % 8) != 0) {
throw new Exception('bits must be divisible by 8');
}
$salt = '';
for($i = 0; $i < $seed_bits; $i+=8) {
$salt .= pack('c', mt_rand());
}
$hexsalt = unpack('h*hex', $salt);
return $hexsalt['hex'] . sha1($salt . $string);
}
function compare_seeded_sha1($plain, $hash) {
$sha1 = substr($hash, -40);
$salt = pack('h*', substr($hash, 0, -40));
$plain_hash = sha1($salt . $plain);
return ($plain_hash == $sha1);
}
If an attacker gets in your database using SQL injection, at least the hashes he/she retrieves won't be useful since he/she won't have access to your application configuration. If your server gets rooted, it's pretty much game-over no matter what you do.
Note: There are other types of attack possible on md5()
which is why you use more secure hashing algorithm, sha1()
for example. Or, even better, use the Portable PHP password hashing framework, which has been designed with security in mind and is backwards compatible with pretty much any PHP version.
require('PasswordHash.php');
$pwdHasher = new PasswordHash(8, FALSE);
// $hash is what you would store in your database
$hash = $pwdHasher->HashPassword( $password );
// $hash would be the $hashed stored in your database for this user
$checked = $pwdHasher->CheckPassword($password, $hash);
if ($checked) {
echo 'password correct';
} else {
echo 'wrong credentials';
}
回答2:
The point of a salt is to prevent attackers from amortizing the cost of a brute force attack across sites (or better yet, when using a different salt for each user: all users of a site) through precomputed rainbow tables.
With plain hashing, an attacker can compute such a table once (a very long, costly operation) and then use it to quickly find passwords for any site. When a site uses one fixed salt, the attacker has to compute a new table specifically for that site. When a site uses a different salt for each user, the attacker can stop bothering with rainbow tables - he'll have to brute-force each single password separately.
Storing the salts separately is not necessary to gain this advantage. In theory it would be even more secure because it would neutralize the weakness of dictionary or short passwords. In practice, it's not worth bothering with because at the end of the day, you need access to the salts somewhere to check passwords. Also, trying to separate them would lead to more complex systems - and the more complex a system is, the more opportunities for security holes there are.
Edit: My concrete recommendations:
- Generate long pseudorandom salt for each user and store in in the DB
- Use a bcrypt-based hash
- ideally, don't implement it yourself, use an existing library instead
回答3:
Forget about using salts (partly for the reason you mention), use bcrypt instead:
For a good explanation see: http://codahale.com/how-to-safely-store-a-password/
回答4:
The other answers are good, so I'll just throw in a minor point that nobody else has mentioned. You don't want to use the same salt for every password because then if two people have the same password, they'll have the same hash. That's exposing information that an attacker can exploit.
You could use the same salt for every user along with Juraj's good idea to combine the password with other non-changing database fields (unique to a user). But watch out because this information gets tied to the password. If you were to hash the username + password together to guarantee a unique hash, you wouldn't be able to change the username without creating a new user and requiring them to set a new password.
As an example of having a unique salt per user and storing it alongside the password hash, I'll point out /etc/shadow on your typical Linux system.
root@linux:/root# cat /etc/shadow | grep root
root:$1$oL5TTZxL$RhfGUZSbFwQN6jnX5D.Ck/:12139:0:99999:7:::
Here, the oL5TTZxL is the salt and RhfGUZSbFwQN6jnX5D.Ck/ is the hash. The plain-text password is root in this case, and the hash algorithm my system uses is the MD5-based BSD password algorithm. (newer systems than mine have better hash algorithms)
回答5:
You don't get the password out for comparison. You encrypt the password when they attempt a login and compare the stored value with the newly encrypted value.
回答6:
As you mentioned, hashing algorithms work only one-way (or only if they are strong enough :-D)
For your question about salting I would recommend to hash a password with a static salt string and some dynamic data from database, which should not change after once created
This is a very secure way of storing passwords, as even if database is compromised, hackers/crackers still need to get your static string hash and need to guess how you applied all the salting..
For example let's say you have a users
table with these columns:
id
username
password
created_at
columns id and created_at after once filled should never be changed..
so when you are hashing user's password you can do as simple as:
<?php
$staticSalt = '!241@kadl;ap][';
$userPass = 'my new pass';
// assuming $user variable is already populated with DB data
// we will generate new hash from columns and static salt:
$genPass = sha1($user['id'] . $userPass . $user['created_at'] . $staticSalt);
?>
I hope this one helps :) cheers
回答7:
Hashing passwords is meant to keep those passwords secret from your own administrator(s).
1) Keeping plain text passwords in your database would be fine except your passwords may be used by the administrator to gain access to some other system.
2) You can use a single global salt, which is combined with the passwords (by prepending or XORing them) and then hashing for storage in the database. But that is vulnerable to a malicious administrator AND a rainbow table designed for that one salt.
3) You can have a separate salt for each user: The database will be used to store the salt, and the hash derived from the password/salt combination. This will prevent a rainbow attack, but brute force attacks will still be possible.
4) Finally, you can keep your hash function a secret by using a velocity-limited hardware hashing solution.
That is as good as you can do. Because of human nature, passwords have a limited domain and are vulnerable to brute force attacks. We are trying to prevent administrators getting a hold of user passwords, and then using them on other systems they should not have access to.
Some other notes:
a) You can use bcrypt on the password/salt combination to slow down the attacker’s brute force attack. But since we are assuming administrators, they can be patient.
b) Keeping the salt separate from the password hash is not an effective defense, we are assuming administrators after all.
c) Using existing data as a salt is a little better, but I doubt existing data has as much entropy a random salt has.
回答8:
Salting a user's password is possibly safer than just hashing the password because it can protect against precomputation attacks.
For instance, if a hacker gets access to your database, and the passwords are not salted, then he can look up the hashes in his database of hashes (see http://en.wikipedia.org/wiki/Rainbow_table) to get the original passwords.