Picture a state of the art implementation of a website registration and login system.
I'm interested in analyzing what a defender gains and loses by feeding the user password to a key-stretching KDF function (e.g. argon2).
Let's start from the basis.
Given a hash function H and a password Pwd, performing h0 = H(Pwd) and sending h0 to the server is pretty useless. However, if Ks is a per-site unique value (some use the hostname but a random unique value is more portable) then h1 = H(Ks || Pwd) turns a possible shared secret (Pwd) into a per-site unique secret (h1).
An attacker knowing h1 from site A can still impersonate the user on the specific site but it cannot use h1 on, say, site B. Even if both sites use the same client-side processing of Pwd.
As standing the scheme is weak, H is prone to many attacks, a quick analysis turn H into full-blown KDF with key-stretching with the server sending all the necessary entropy (or just set minimum requirements for the UAs).
So, by sending h2 = KDF(Ks, Pwd, costs_params, salt) to the server, we make sure the effective password used to register/login on this website is unique and resistant to usual attacks on stored passwords.
An unrelated but useful reminder: server-side h2
is still "hashed" (i.e. KDF with KS) to prevent turning a leak into an easy impersonation of the users.
I've not seen this scheme implemented in any framework or even raccomended as far as I know. Why? Should I start implementing/requiring it?
I tried to write down a list of cons and pros.
Cons
- The server cannot enforce a password policy. My opinion is that most password policies are harmful but the server should at least do a few checks. In particular, check the length and if the password is in the n-most-used-passwords list. However, the length check can be done client-side (at the cost of possibly letting "smart" users bypass the check).
- This extra processing prevents credential stuffing only in the case the attack can solely intercept TLS traffic passively. If the attacker can control the user device, or the server, or tamper with TLS traffic this is all worthless. It's worth noting that if an attacker can read the TLS traffic they already have access to the user's sensitive information (including authentication tokens). This reduces the usefulness of this client-side processing.
- It consumes (variable) client-side power. JavaScript implementations are sufficiently fast but they have great variability and the user may not be happy with the increased power consumed. It's not much about time as much as about battery life nowadays.
- It's an extra moving wheel. I have the feeling (under discussion) that all this processing is doing is adding an extra "protection" on top of TLS but without adding too much (see point 2). Less moving parts are better. We do something similar server-side though, but in that case, the weak link is the server, which is a single-person business, while in this case, the weak link is TLS which is all people business. Plus unique password generation can be handled by encouraging the use of password managers. All this gives me a feeling of "reinventing the wheel".
Pros
- It prevents credential stuffing even if somebody is sniffing TLS. This is the only bullet but it is very appealing.
There already is a very similar question: Secure authentication: partial client-side key stretching... please review/criticize my idea but the answer focuses only on con #3 which, 8 years later, don't really hold anymore.
Personally, I have the feeling that theoretically it is useful but in practice the attack surface removed is very small and if one considers TLS compromised then all bets are off (just as if the standard crypto is broken).
Yet maybe I'm just old and not receptive as I should be.
Reiterating the question: Should we start requiring client-side key-stretching KDF processing of the user password as we require server-side?