Viewed   63 times

I have used unsalted md5/sha1 for long time, but as this method isn't really secure (and is getting even less secure as time goes by) I decided to switch to a salted sha512. Furthermore I want to slow the generation of the hash down by using many iterations (e.g. 100).

My question is whether I should append the salt on every iteration or only once at the beginning. Here are the two possible codes:

Append every time:

// some nice big salt
$salt = hash($algorithm, $salt);

// apply $algorithm $runs times for slowdown
while ($runs--) {
    $string = hash($algorithm, $string . $salt, $raw);
}

return $string;

Append once:

// add some nice big salt
$string .= hash($algorithm, $salt);

// apply $algorithm $runs times for slowdown
while ($runs--) {
    $string = hash($algorithm, $string, $raw);
}

return $string;

I first wanted to use the second version (append once) but then found some scripts appending the salt every time.

So, I wonder whether adding it every time adds some strength to the hash. For example, would it be possible that an attacker found some clever way to create a 100timesSha512 function which were way faster than simply executing sha512 100 times?

 Answers

4

In short: Yes. Go with the first example... The hash function can lose entropy if feed back to itself without adding the original data (I can't seem to find a reference now, I'll keep looking).

And for the record, I am in support of hashing multiple times.

A hash that takes 500 ms to generate is not too slow for your server (considering that generating hashes are typically not done the vast majority of requests). However a hash that takes that long will significantly increase the time it will take to generate a rainbow table...

Yes, it does expose a DOS vulnerability, but it also prevents brute force attacks (or at least makes them prohibitively slow). There is absolutely a tradeoff, but to some the benefits exceed the risks...

A reference (more like an overview) to the entire process: Key Strengthening

As for the degenerating collisions, the only source I could find so far is this discussion...

And some more discussion on the topic:

  1. HEKS Proposal
  2. SecurityFocus blog on hashing
  3. A paper on Oracle's Password Hashing Algorithms

And a few more links:

  1. PBKDF2 on WikiPedia
  2. PBKDF2 Standard
  3. A email thread that's applicable
  4. Just Hashing Is Far From Enough Blog Post

There are tons of results. If you want more, Google hash stretching... There's tons of good information out there...

Sunday, October 2, 2022
2

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

Monday, October 3, 2022
5

So let's take it one part at a time

but it returns a different hash every time

That's the idea. password_hash is designed to generate a random salt every time. This means you have to break each hash individually instead of guessing one salt used for everything and having a huge leg up.

There's no need to MD5 or do any other hashing. If you want to raise the security of password_hash you pass a higher cost (default cost is 10)

$password = password_hash($password4, PASSWORD_DEFAULT, ['cost' => 15]);

As to verify

if(password_verify($password4, $dbpassword))

So $password4 should be your unhashed password and $dbpassword should be the hash you've stored in your database

Thursday, November 10, 2022
 
2

ASPNET_DB says this - can't go wrong.

Password nvarchar(128) NOT NULL,
PasswordSalt nvarchar(128) NOT NULL,

while 128 may seem like a lot, various types of encryption can result in larger strings than you started out with. There is absolutely no reason not to follow the lead of the very smart people who have spend thousands of man hours developing the asp.net membership system.

Friday, December 23, 2022
3

The code that you have given is a port of PHPASS, specifically the "portable" algorithm. Note the qualification of portable. This will only apply to the phpass library if you pass true as the second constructor parameter. From here on out in this answer, phpass refers ONLY to the portable algorithm, and not the library itself. The library will do bcrypt by default if you do not explicitly specify portable...

The PHPBB team did not develop this themselves (a very good thing), but ported it from phpass directly (arguable).

There are a few questions we should ask here:

Is It Bad?

The short answer is no, it's not bad. It offers pretty good security. If you have code on this right now, I wouldn't be in a rush to get off it. It's adequate for most usages. But with that said, there are far better alternatives if you were starting a new project that I wouldn't pick it.

What are some weaknesses?

  • Relative To pbkdf2: The phpass algorithm uses hash() where pbkdf2() uses hash_hmac(). Now, a HMAC runs 2 hashes for every call internally, but the PHP implementation only takes approximately 1.6 times the execution of a single call to hash() (isn't C wonderful?). So we get 2 hashes from hash_hmac in 62% of the time it would take hash() to execute 2 hashes.

    What does that mean? Well, for a given runtime, pbkdf2 will run approximately 37.5% more hashes than the phpass algorithm. More hashes in a given time == good, because it results in more computation being performed.

    So pbkdf2 is approximately 37.5% stronger than phpass when using the same primitive (md5 in this case). But pbkdf2 can also take stronger primitives. So we can use pbkdf2 with sha512 to gain a very significant advantage over the phpass algorithm (mainly because sha512 is a harder algorithm with more computation than md5).

    This means that not only is pbkdf2 able to generate more computations in the same amount of time, it's able to generate harder computations.

    With that said, the difference isn't overly significant. It's very much measurable, and pbkdf2 is definitely "stronger" than phpass.

  • Relative To bcrypt: This is a lot harder of a comparison to make. But let's look at the surface of it. phpass uses md5, and a loop in PHP. pbkdf2 uses any primitive (in C) and a loop in PHP. bcrypt uses a custom algorithm all in C (meaning it's a different algorithm from any available hash). So right of the bat, bcrypt has a significant advantage just do to the fact that the algorithm is all in C. This allows for more "computation" per unit time. Thereby making it a more efficient slow algorithm (more computations in the given runtime).

    But just as important as how many computations it does is the quality of the computations. This could an entire research paper, but in short it comes down to the fact that the computations that bcrypt uses internally are much harder to perform than a normal hash function.

    One example of the stronger nature of bcrypt is the fact that bcrypt uses a far larger internal state than a normal hash function. SHA512 uses a 512 bit internal state to compute against a block of 1024 bits. bcrypt uses instead about 32kb of internal state to compute against a single block of 576 bits. The fact that bcrypt's internal state is so much bigger than SHA512 (and md5 and phpass) partially accounts for the stronger nature of bcrypt.

Should It Be Avoided

For new projects, absolutely. It's not that it's bad. It isn't. It's that there are demonstrably stronger algorithms out there (by orders of magnitude). So why not use them?

For further proof of how bcrypt is stronger, check out the Slides from Password13 (PDF) which launched a 25 GPU cluster for cracking password hashes. Here are the relevant results:

  • md5($password)
    • 180 BILLION guesses per second
    • 9.4 Hours - All possible 8 character passwords
  • sha1($password)
    • 61 BILLION guesses per second
    • 27 Hours - All possible 8 character passwords
  • md5crypt (which is very similar to phpass with a cost of 10):
    • 77 Million guesses per second
    • 2.5 Years - All possible 8 character passwords
  • bcrypt with a cost of 5
    • 71 Thousand guesses per second
    • 2700 Years - All possible 8 character passwords

Note: all possible 8 character passwords are using a 94 character set:

a-zA-Z0-9~`!@#$%^&*()_+-={}|[]:";'<>,.?/

The Bottom Line

So if you're writing new code, without a doubt use bcrypt. If you have phpass or pbkdf2 in production now, you may want to upgrade, but it's not a clear cut "you're significantly vulnerable".

Wednesday, September 21, 2022
Only authorized users can answer the search term. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :