client --- server
salt? -> -> generate salt
hash(pass+salt) <- <- salt
-"- -> -> verify hash
done <- <- auth client --- server
salt? -> -> generate salt
hash(pass+salt) <- <- salt
-"- -> -> verify hash
done <- <- authThe point is that the server doesn't store what's used to verify directly, it needs to provide something that can generate it. The way you're doing it, someone can dump your DB and login as anyone. If you want a secure way to do this maybe look into SRP or similar PAKE protocols.
In a more secure auth flow you instead have:
Request 1:
-> Can I get a login CSRF token for `crdrost`?
- generate random string and store on the users table with an exp date
<- Sure here is a login token
Request 2:
-> Here is my token and my plaintext password. (!)
- Users table: select CSRF token, salt, KDF(salt, pass) previously computed
- compute KDF(salt, pass), compare with previous computation
- generate session token
<- Sure here is your session token
In terms of KDFs you might use Blowfish or PBKDF2 or Scrypt or more recently Argon2, you can also HMAC or hash-concat if you have strong enough complexity requirements that you are not worried about brute force attacks, e.g. “when you sign up I will generate a random password for you.”The remaining security vulnerability is typically that you accidentally auto-log plaintext passwords so this requires vigilance around that.
But the point is that at no time does the database store anything which can be used to complete a login. Suppose I do the KDF on the client side, then its output is “password-like” and if I know that output by reading a row accidentally from the users table, I can log in as that user. Similarly by overhearing an HTTP login over WiFi, I can log in as that user.
Similarly you have a “challenge/response” system because you don't want plaintext passwords to even make it to the API, this is a noble aim but it absolutely requires public key crypto to be done correctly: you store a public key derived from the private key; login page uses password to generate private key and then signs a request to sign in, you confirm the signature with the public key. A full scheme, so that you are not rolling your own crypto, is described by the Secure Remote Password protocol.
The security risks there are just that the process has become very complex and complex things are easier to get wrong.
It actually doesn't, have a look at how the Firefox Account Login protocol works, essentially using only hashing and KDFs:
https://blog.mozilla.org/warner/2014/05/23/the-new-sync-prot...
It doesn't really achieve challenge-response without it, but it does mean at least that the server never sees the plaintext password.
While CR does not need the full force of public key crypto it has properties which require a distinctive subset of those features in this context—I want to be able to take a password and derive two values (K, V) such that g(V, f(K, s)) = h(V, s) for some large family of s'es, but f(K, s) cannot be computed as j(V, s) efficiently. Public key crypto is more or less the case where h(V, s) = s, giving an option to maybe do something interesting when h is nontrivial.
Even if you used a pre-hashed (well, preferably key derived using scrypt, argon2, etc) password, the problem is that if an attacker dumped your database they can still login to every account on your system without the original password.
To perform a full challenge response system, you'd need to get some public key crypto going, be that deriving ed25519 keys on the client or going to a full PAKE algorithm like SRP. Anything short of that means your challenge response is going to prove almost as bad as clear text if an attacker steals the secrets.