3

Assume we have a password and add a unique salt to prevent various precomputation attacks.

Why don't we do this:

SHA256(SHA256(SHA256 ...(Password || Salt)))   

So, we have n iterations (like in PBKDF2) and because of the iterations, it should take much longer to compute the hash for a password, which is key stretching.

Can someone explain to a newbie like me why we don't use specific algorithms like scrypt, PBKDF2, or Argon2 and do this?

iPherian
  • 103
  • 2
Aliquis
  • 593
  • 1
  • 4
  • 8

2 Answers2

7

What's proposed, and PBKDF2 alike, give significantly less protection than scrypt or Argon2. That's because adversaries trying many passwords on parallel hardware can do so at lesser cost, for a given time or CPU × time on the legitimate user's side. With scrypt or Argon2, but not the question's construct or PBKDF2:

  • A significant amount of memory is required to compute the function. This implies that the adversary has to invest in that memory for each instance of password searcher, and can't use GPUs efficiently. Password cracking for PBKDF2 (or worse) entropy stretching is common practice, using GPUS, FPGAs, I guess ASICs. I conjecture government agencies do this routinely. Bitcoin mining ASICs are close to being usable to attack the question's construct at high rate (but are not AFAIK), and PBKDF2 has comparable complexity.
  • Multiple CPUs on the legitimate user's side are leveraged. This allows to perform more work on the legitimate side for a given user's wait (raising attack cost correspondingly). That's useful when the limiting factor for parametrization of the effort per password is the user's waiting time.

The question's construct is close to PBKDF1 and comparable to PBKDF2. The main operational differences are that

  • PBKDF1 and PBKDF2 have variable size output; PBKDF1 has built-in truncation, and PBKDF2 additionally allows large output. That makes them usable in more situations. But large output is immaterial for password storage, and PBKDF2's way of generating large output is poor, as noted by CodesInChaos in comment.
  • The question's construct (and PBKDF1) has the dubious property that for all $\mathsf{password}$, $\mathsf{salt}$, and bytestring $b$, it holds $F(\mathsf{password}\mathbin\|b,\mathsf{salt})=F(\mathsf{password},b\mathbin\|\mathsf{salt})$, which PBKDF2 avoids. This is typically immaterial for password storage, but conceivably could be an issue in some other uses of PBKDF2. However PBKDF2 used with HMAC (as it almost always is) has another potentially undesirable regularity, as noted in said comment: a long $\mathsf{password}$ and its hash are equivalent.
  • For 1 iteration of SHA-256 and uniform random input, the (likely) entropy in the output is only about $256-0.827\dots$ bit (see this), and that keeps on decreasing as the number of iterations grows (see this and this). That contrasts with PBKDF2-HMAC-SHA-256 for 256-bit output (probably) next to fully and uniformly covering it's output space, with no marked decrease as the number of rounds increases, for near 256-bit entropy. However that effect in the question's construct and PBKDF1 remains beyond experimentally observable as far as we know (that would imply an attack on SHA-256), nearly disappears anyway for significantly truncated output, and has no practical significance.
fgrieu
  • 149,326
  • 13
  • 324
  • 622
1

In your construction it seems the password and salt are only mixed at the first iteration. We prefer to mix them in again on every iteration. This makes sure we don't end up loosing any of the entropy in the password originally.

But fundamentally this is a good construction for password hashing. not great but good. I would prefer bcrypt as it was designed to be slow and for this purpose while sha256 was designed to fast.

Argon and scrypt give an extra layer of security requiring memory reaources and not only computational resources.

Meir Maor
  • 12,053
  • 1
  • 24
  • 55