Why does PHP crypt() prepend the salt to the hash?
Asked Answered
M

6

11

I am looking into building a login system and after reading the php manual when you pass a 2 digit salt to the crypt() function it returns a hash string, and the first 2 digits of the string are the salt that you used.

example:

$salt = "kr";
echo crypt("mysecret",$salt); //returns "kreOI.F7eOQMY"

My first thought was, wouldn't this help someone who is trying to reverse your hash?

I looked up salt on wikipedia which said:

For best security, the salt value is kept secret.

So I do not understand why then would the crypt function return all hashes prepended with the salt value used?

Is there a reason for this? Should this be a security concern?

Mildew answered 26/1, 2011 at 17:22 Comment(3)
That's why I recommend a two part salt. One in a config file which is secret but the same for all users, and one part saved together with the password which is different for each user/randomly generated.Thieve
@meager, actually I am trying to upgrade one that I inherited.Mildew
Wikipedia should not be taken as gospel. Salts are not secret.Clearheaded
E
1

The crypt() function is obsolete. It was used to hash passwords for old-style Unix systems, before shadow password support came along. The salt was there to increase the complexity of brute forcing the password. However, since the salt was randomly generated by the password subsystem, it had to be stored in the clear so any future password actions would work. If the salt had been embedded into the password before crypting, there would be no practical way to verify a password - you'd have to try every single possible salt whenever a password check was done - highly impractical. So, the salt was prepended to the crypted password, so you could pull it out again for future use.

crypted password: xxabcdefghijklmn
                  ^^- salt
                    ^^^^^^^^^^^^^^-- crypted pw

if ('xx' + crypt('xx' + password) == 'crypted string') then
     password is ok
endif

These days, crypt() is the security equivalent of a cereal box decoder ring. There for historical purposes, and low-security "who cares if it's cracked into" storage. For any modern password usage, you'd be better off with more modern hashes, such as sha1/sha256/md5. And even md5 is considered broken these days, sha1 has cracks around the edges, and (last I checked) sha256 is still secure.

Exophthalmos answered 26/1, 2011 at 17:30 Comment(7)
This makes sense, so if I were to already have a data-base full of users with hashed passwords stored. Is there anyway for me to convert them to something else like sha256, or should I "convert" them the next time the user logs in? since obviously I would have the raw password at that time. Thanks.Mildew
If all you've got is the hashed password, then the original password is gone - that's why it's a hash. A one-way operation from plaintext->hashtext. Converting upon authentication against the old hash is one way to go, unless you want to invalidate everyone's password, generate new ones with the new hash, and force them to change it "back" to their old PW and store that with the new hashing algorithm as well.Exophthalmos
The salt is not there to increase brute-force complexity. It's there to prevent the time-space trade-off of a rainbow table.Puissance
I'd consider a rainbow table a brute force method. Either way, adding a salt increases the computation time. Whether it's onetime for a rainbow table, or N times on a per-pw check basis, it's still increasing entropy.Exophthalmos
crypt() is not "obsolete" - certainly the older DES-based hashes are of dubious value at this point, but the MD5, Blowfish and (in newer PHPs) SHA-based hashes work just fine for modern password hashing.Incorrect
@John - The way I've handled the problem of conversion is merely to hash the hash. It adds a step to the password verification process but that's negligable. I've read maths that suggest doing so might increase the chance of a collision but not significantly so and even so, collisions aren't my real worry in an authentication mechanism. and @Incorrect - Why defend crypt()? Everybody can just move-on and use bcrypt and we'll all be better for it.Skillet
SHA* and salts are apples and bananas. SHA* without a salt is worse than any old algorithm with salt. As such, this does not really answer the question at all.Formalize
P
31

The author of the Wikipedia article is conflating salt with the idea of search space, implying salt is a way to deter brute force attacks. Security is not improved by confusing these ideas; someone who can't recognize and delineate these two issues is not a credible guide.

The purpose of salt is to thwart pre-computed lookup tables (like a Rainbow table). Salt prevents an attacker from trading "space" for "time." Every bit of salt doubles the storage requirements for a table; a two byte salt makes a big (65536 times) difference, but eight bytes would require non-existent "yottabyte" storage devices for lookup tables.

Assuming that the salt cannot be kept secret encourages better key-strengthening and password selection, and this leads to more secure system.

However, recent recommendations from NIST encourage the use of an additional, secret "salt" (I've seen others call this additional secret "pepper"). One additional iteration of the key derivation can be performed using this secret as a salt. Rather than increasing strength against a pre-computed lookup attack, this round protects against live dictionary attacks. In this way, it's more like the large number of iterations in a good key derivation function.

This secret serves no purpose if stored with the hashed password; it must be managed as a secret, and that could be difficult in a large user database.

Brute force attacks are best prevented by key-strengthening (applying the hash function thousands of times), and password selection rules (require longer passwords, reject blacklisted entries, etc.), but a "pepper" provides an additional layer of defense.

Puissance answered 26/1, 2011 at 18:49 Comment(1)
See a related answer here.Puissance
F
9

I should comment that Crypt is not as bad as Marc B makes it sound, and may in fact be the easiest way to good hashes, as long as you don't rely on its weaker schemes like MD5.

See:

How do you use bcrypt for hashing passwords in PHP?

http://uk.php.net/manual/en/function.crypt.php

http://www.openwall.com/phpass/

Frankly answered 6/4, 2012 at 20:39 Comment(0)
K
2

Yes, the salt is supposed to be kept secret, but then so is the password hash. It's perfectly acceptable for them to be kept equally secret in the same place. To check a password against the hash, you have to combine the salt with the password and then check it against the hash. So, any user or process with the right to see the password hash should also have the right to see the salt, since the password hash by itself is not useful for checking passwords (unless you're going to brute-force the salt).

The purpose of the salt is so that if two different users have the same password, they'll hash to different things. This also means that dictionary attacks are much more complex because you can't just hash all likely passwords and then check them against a list of user password hashes to find multiple user's passwords. Instead you have to try passwords for an individual salt to find one user's password or try all combinations of likely passwords with multiple salts in order to find hits. But knowledge of the salt, by itself, doesn't mean you can reverse the password hash. It just means that you can do a dictionary attack on the password hash.

If you can find a way to keep the salt more secure than the hash value, it certainly wouldn't be a bad thing, but it's hard to see how this is feasible when any program which needs access to one needs access to both.

Keller answered 26/1, 2011 at 17:29 Comment(0)
E
1

The crypt() function is obsolete. It was used to hash passwords for old-style Unix systems, before shadow password support came along. The salt was there to increase the complexity of brute forcing the password. However, since the salt was randomly generated by the password subsystem, it had to be stored in the clear so any future password actions would work. If the salt had been embedded into the password before crypting, there would be no practical way to verify a password - you'd have to try every single possible salt whenever a password check was done - highly impractical. So, the salt was prepended to the crypted password, so you could pull it out again for future use.

crypted password: xxabcdefghijklmn
                  ^^- salt
                    ^^^^^^^^^^^^^^-- crypted pw

if ('xx' + crypt('xx' + password) == 'crypted string') then
     password is ok
endif

These days, crypt() is the security equivalent of a cereal box decoder ring. There for historical purposes, and low-security "who cares if it's cracked into" storage. For any modern password usage, you'd be better off with more modern hashes, such as sha1/sha256/md5. And even md5 is considered broken these days, sha1 has cracks around the edges, and (last I checked) sha256 is still secure.

Exophthalmos answered 26/1, 2011 at 17:30 Comment(7)
This makes sense, so if I were to already have a data-base full of users with hashed passwords stored. Is there anyway for me to convert them to something else like sha256, or should I "convert" them the next time the user logs in? since obviously I would have the raw password at that time. Thanks.Mildew
If all you've got is the hashed password, then the original password is gone - that's why it's a hash. A one-way operation from plaintext->hashtext. Converting upon authentication against the old hash is one way to go, unless you want to invalidate everyone's password, generate new ones with the new hash, and force them to change it "back" to their old PW and store that with the new hashing algorithm as well.Exophthalmos
The salt is not there to increase brute-force complexity. It's there to prevent the time-space trade-off of a rainbow table.Puissance
I'd consider a rainbow table a brute force method. Either way, adding a salt increases the computation time. Whether it's onetime for a rainbow table, or N times on a per-pw check basis, it's still increasing entropy.Exophthalmos
crypt() is not "obsolete" - certainly the older DES-based hashes are of dubious value at this point, but the MD5, Blowfish and (in newer PHPs) SHA-based hashes work just fine for modern password hashing.Incorrect
@John - The way I've handled the problem of conversion is merely to hash the hash. It adds a step to the password verification process but that's negligable. I've read maths that suggest doing so might increase the chance of a collision but not significantly so and even so, collisions aren't my real worry in an authentication mechanism. and @Incorrect - Why defend crypt()? Everybody can just move-on and use bcrypt and we'll all be better for it.Skillet
SHA* and salts are apples and bananas. SHA* without a salt is worse than any old algorithm with salt. As such, this does not really answer the question at all.Formalize
C
1

The salt is appended to the has so that you will know which salt to use when you get the password and want to see if it matches the hash. The idea here is to use a different salt for every password so that someone cannot precompute a hash table.

You could also append a second salt to every password (the same for all) and not tell anyone what it is.

Cowardice answered 26/1, 2011 at 17:31 Comment(0)
A
1

PHP crypt inherits this behaviour from the UNIX crypt() function, which was used for generating password hashes in the UNIX passwd file. It's necessary to store the salt somewhere, or you can't verify later that the password is correct. For the passwd file, the simple behaviour was just to prepend the salt (always two characters) to the start of the crypted password, which makes it simple to store it in a single field.

The statement that the salt value should be kept secret is open to misinterpretation. For best practice you should not publish your salts, in the same way that you should not publish your password hashes. Giving an attacker the hashes and salts makes it easy for them to run a brute-force attack without generating suspicious traffic to your system. However, the system should still be secure even if an attacker can see both salt and hashed password.

In fact, there's nowhere you can store the hash that couldn't, in principle, be compromised by a hacker in exactly the same way as the hashed passwords. If the password-checking code can access it, then you have to assume that someone who's compromised the system could get access to it as well.

Anagoge answered 26/1, 2011 at 17:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.