0

I don't like my server having access to plaintext passwords. Instead of SRP, OPAQUE or another PAKE that needs several rounds, I was thinking I could take the username, server url and password, run a hardened hash like scrypt and generate a private/public key pair.

When registering the account, the client send the public key to the server, which stores it in lieu of the salted hashed hardened password verifier.

When doing a new (HTTPS) request, the client takes the current time and signs it with the private key it can regenerate. The server checks that the signed time in the request is close to its current time, similar to aws checking HMACs.

Maybe the generated key pair could even work as a client certificate.

Am I missing something?

  • 2
    Potential duplicate to [Does a password-derived public key authentication improve security over pure password-based authentication?](https://security.stackexchange.com/questions/234161/does-a-password-derived-public-key-authentication-improve-security-over-pure-pas). And if you don't want to server to see the clients original password - just do client side hashing in addition to server side hashing (of the client side hashed password). – Steffen Ullrich Mar 22 '21 at 06:06
  • 1
    The client-side hashed password would be password equivalent, and incorrectly logging it would be as bad as logging the password, that I'm trying to avoid. – Bruno Martinez Mar 22 '21 at 17:01

1 Answers1

3

In theory, this is certainly possible - see this answer about doing the key generation. However, bear in mind that you're going to have to do all of this (hashing and key generation) on the client, which has several undesirable characteristics:

  • It's done in JS, which is slower than native code even on modern JIT-compiled engines, and you're doing two different things (password hashing and key pair generation) that are already slow.
  • It needs to work exactly the same on each client, and there's no standard JS API for generating a key pair from a given high-entropy seed (or for executing scrypt, for that matter), so you're going to have to supply all the relevant JS code yourself (and ensure it works correctly on all browsers that you support, with polyfills if needed for any library functions) rather than just calling a built-in API.
  • Since the hashing is done pre-auth, there's no secure way for the server to supply a salt (that an attacker couldn't get), so you're stuck with salts that might be unique (like username/email address) but are not secret and thus the password hashes can be pre-computed for users of interest. (On the plus side, the extra step of generating a key pair from this hash output provides an additional time cost against brute-forcing attacks).

So, all of this is possible. It is arguably a bit of a hack; there's no inherent browser support for what you're trying (even when it was recommended, the <keygen> element didn't allow supplying the seed entropy, and the built-in JS crypto API doesn't support that either) and you're kind of re-inventing public-key auth in a slightly weird way (using passwords for client-side key re-derivation, rather than just moving around a client certificate+private key). It doesn't add a lot of security either, as the scripts that do all this hashing and deriving and signing are going to be served by the server so an attacker with access to the server could slightly modify the scripts to steal the passwords/keys anyhow. But it could be done.

EDIT: You tagged this question "web-application" and referred to HTTPS, so I assumed your client was running in a browser. For a thick client, this is much less of an issue; not only do you have a much richer (and usually more performant and better-defined) API to draw upon, it's harder to surreptitiously serve the user maliciously modified code.

CBHacking
  • 42,359
  • 3
  • 76
  • 107
  • 1
    +1. OP, most (if not all) of this can be done in javascript using the WebCrypto API. See https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto. A private key can be derived from a password via PBKDF2 using deriveKey(), then the public key can be exported using exportKey(). getRandomValues() can be used as a CSPRNG. But, @CBHacking makes a great point in the last sentence of his answer about the infamous browser crypto chicken and egg problem. See https://security.stackexchange.com/questions/238441/solution-to-the-browser-crypto-chicken-and-egg-problem for more info on this. – mti2935 Mar 22 '21 at 14:16
  • The client is .net, so the chicken and egg problem moves to it being auto updated or not... Salting is lost or severely nerfed, yes, as in SRP. – Bruno Martinez Mar 22 '21 at 17:30
  • @mti2935 `crypto.subtle.deriveKey` doesn't support deriving private keys / key pairs, at least not according to Mozilla's documentation. It'll only derive *secret* keys for HMAC or AES, which are symmetric keys. SubtleCrypto can generate RSA, ECDSA, or ECDH key pairs, but you can't provide your own RNG or entropy to the `generateKey` function. I would have mentioned SubtleCrypto if it supported this use case. – CBHacking Mar 23 '21 at 05:12
  • @CBHacking right, I was thinking web api, like aws. Juste edited the tags. Thanks. – Bruno Martinez Mar 23 '21 at 22:47
  • 1
    @CBHacking, yes I agree, I've not been able to find a direct way to do this either using the Web Crypto API. The only way that I've been able to find is by deriving a 256 bit AES key using deriveKey(), then exporting the key using exportKey(), then importing the key as a ECDH CryptoKey oobject using importKey(). Similar can be done for a 256 bit value generated by getRandomValues(). Far from ideal, I know, and probably not a solution to OP's problem. But thought I would add it here in case anyone trying to do something like this stumbles upon this question. – mti2935 Mar 24 '21 at 10:44