Subject: Re: [kitten] Comments on draft-ietf-kitten-password-storage-02

From: "Sam Whited" <[email-masked]>
Date: Tue, 08 Dec 2020 14:33:22 +0000

I was just re-reading parts of the argon2 I-D and its variable length
hash function uses the first 32bytes of the inner hash function, so
minimum 32 seems like a reasonable number. Just throwing that out in
case anyone wants to disagree or explain to me why I'm misreading the
argon2 I-D (which is quite likely).

—Sam

On Mon, Nov 23, 2020, at 23:22, [email-masked] wrote:
> Hmm I guess just pick one as it doesn't really matter. Yeah I suggest
> for Argon2 and scrypt to say "default or 24 bytes" and PBKDF2
> "min(hLen, 24 bytes)". You can change 24 to whatever I won't bike shed
> you on that. I like 24 bytes as it's multiple of 3 which is nice with
> base 64 encoding. Which is kind of a dumb reason but I will back any
> number bytes 16 to 32.

-- 
Sam Whited

---

From: "Jim Fenton" <[email-masked]>
Date: Tue, 08 Dec 2020 10:10:34 -0800

> argon2 I-D (which is quite likely).

Somewhat ignoring the details of argon2, an ideal hash function would 
have no attack that is more efficient than taking random guesses of the 
input and comparing the output. NIST SP 800-131A considers (in the 
context of choosing key sizes) a minimum of 112 bits to be sufficiently 
free from brute-force attacks.

I’d suggest that this not be larger than it needs to be. I won’t 
quibble over the difference between 112 bits and 128 bits, but twice 
that seems a bit excessive as a lower bound, unless argon2 is a lot 
further from ideal than I expect.

-Jim

---

From: [email-masked]
Date: Tue, 8 Dec 2020 16:25:01 -0600 (CST)

> On 12/08/2020 8:33 AM Sam Whited <[email-masked]> wrote:
> 
>  

I do not agree with "minimum 32 bytes", but if you state "default or 32 bytes" that's fine.

---

From: [email-masked]
Date: Tue, 8 Dec 2020 16:47:01 -0600 (CST)

> On 12/08/2020 12:10 PM Jim Fenton <[email-masked]> wrote:
> 
>  

So this made me think, I'm saying 128 bit hash output because brute forcing "128 bits" is a lot of work. But this is already stretched by 10 to 30 bits. So technically "128-log2(amount of work)" bits is what I was thinking of. It looks like NIST is saying 2^112 amount of work is "sufficiently free from brute-force attacks". Using that the minimum for storing a password hash* is "112-log2(amount of work)" bits.

* Note if this is actually a key used in something like SCRAM or a PAKE where the key can be directly guessed then the minimum is 112 bits.

> further from ideal than I expect.

If NIST says 112 bits is fine then OK. Also for this if the key stretching is at least 16 bits then it's at 128 bit strength, so eh. Also I have my password manager generate ~80 bit strength passwords and I feel that's safe enough even if it's an unsalted, fast hash. But one reason I go with a minimum hash length of 128 bits is to avoid people saying it's too small. As I said before 16 to 32 bytes I won't argue and I guess if you're NIST you can go down to 14 bytes if you want to.

P.S. Argon2 uses Blake2b for the final hash:

block = zeroBlock
foreach lane in lanes
{
    block = xorBlocks(block, lane[lastBlock])
}
out = ""
x = blake2b(block)
while (outlen - len(out) > 64)
{
    out = out || substr(x, 0, 32)
    x = blake2b(x)
}
return substr(out || x, 0, outlen)

---

From: [email-masked]
Date: Tue, 8 Dec 2020 20:13:32 -0600 (CST)

> On 12/08/2020 4:47 PM [email-masked] wrote:
> 

Slight correction I forgot Blake2 uses the output length as an input for hashing. The last hash block is not truncated but of said length. Note Argon2 also uses the output length as an input which is why truncating an Argon2 hash won't match... I forget if I mentioned that. I feel like I did in a draft but removed it as it was too much in the weeds of Argon2. Anyway, even if Argon2 didn't do that, then the use of Blake2b at the end makes it so. Although, if Argon2 didn't use the output length as an input, then you can kind of do truncation compares like "substr(argon2(..., outlen:64 bytes, rawKey:true), 0, 32) == substr(argon2(..., outlen:128 bytes, rawKey:true), 0, 32)" (ie when both outlen are at least 64 bytes and truncated to at most 32 bytes).

Anyway the correct Argon2 finalization:

x = zeroBlock
foreach lane in lanes
{
    x = xorBlocks(x, lane[lastBlock])
}
out = ""
while (outlen - len(out) > 64)
{
    x = blake2b(x, hashLen:64)
    out = out || substr(x, 0, 32)
}
return out || blake2b(x, hashLen:(outlen - len(out)))