1

Context

We have a browser-based client-server application. The client registers with email and password. The password is enforced to some guidelines.

Problem

The User needs an encrypted vault on the server to store cryptographic keys in such a way that only the user on the client device can decrypt and recover the keys for temporary usage.
So not the server or anybody else should have access to those cryptographic keys. Only the user on the client-side should be able to recover it.

Solution ??

  1. Client: User registers with email and password
  2. Client: Derive Master-Secret (MS) by using bcrypt(password, salt)
  3. Client: From MS derive Login-Secret (LS) by using HKDF(MS, salt)
  4. Client: From MS derive Vault-Secret (VS) by using HKDF(MS, salt)
  5. Client: Sends Email + LS to server.
  6. Server: Stores LS by bcrypt(LS, random_salt) in DB (as with any other passwords too).
  7. Client: Can use VS to decrypt crypto-keys and send them to server.

The 3 salts used in steps 2-4 could be derived from the email by using SHA3-512 and take the necessary length of bytes needed for those functions?

I was thinking it is important that the server has no information how to decrypt the vault. Thus should never know in any point in time the secret key for the vault. By just using one password for login and encryption sounds not reasonable? How would someone approach this problem?

Dachstein
  • 111
  • 4

2 Answers2

2

I think the duplicate I proposed should tell you "yes, you're going about this in a reasonable way", but there are a couple points that I think are worth addressing separately.

The 3 salts used in steps 2-4 could be derived from the email by using SHA3-512 and take the necessary length of bytes needed for those functions?

Salts should not be reused, and they should not be predictable. By using a hash of the username, the same salt will be used whenever a user changes their password. It also allows an attacker to trivially predict the salt that will be used and start generating a rainbow table in the hope that a future leak will allow them to make use of it.

In the case of the login key salt, since it has to be used client-side prior to logging in, anyone who knows the username must be able to retrieve the salt from the server anyway, so generating the salt from the username may not be a deal breaker here, however it would still be best to avoid using the same salt when a user changes their password. The encryption key salt should be randomly generated in order to prevent pre-computation of rainbow tables, which means it needs to be stored on the server (unless you're ok with users not being able to change browsers/computers).

If you compare this to a normal app that just sends the password to the server, this isn't really any less secure to outside attackers. For a rainbow table using the login-key salt to be useful, someone would have to obtain the user's login-key, which in other apps would be the password itself.

If the encryption key salt is only sent to authenticated users, then the only rainbow table that could be generated would be for the user's login key. If there is a database leak, then the server-side bcrypt hash of the login key will have a separate random salt that the rainbow table won't be useful for, and the encrypted data will be encrypted using the encryption key salt that the attacker wouldn't have prior access to, so allowing anyone to access the login key salt shouldn't pose too much of a threat.

Of course, if the server itself is malicious, nothing prevents it from trying to crack the password, but this isn't really avoidable.

I was thinking it is important that the server has no information how to decrypt the vault.

While that certainly sounds nice, unless your users take extreme measures (ie auditing the code and verifying it hasn't changed every time they load the page) they'll still have to trust the server to some extent. It's the server that sends them the JavaScript every time they load the page, and it would be easy for someone malicious in control of the server to modify the code to have it send them the encryption keys.

Client-side encryption with a key known only to the user is useful to mitigate the damage done if the server is compromised in the future, but if a user connects to the server while it's compromised/malicious they're probably going to be out of luck.

Thus should never know in any point in time the secret key for the vault. By just using one password for login and encryption sounds not reasonable?

Running the password through bcrypt and then HKDF client-side with separate salts for the encryption key and authentication should be sufficient to solve this problem. Just be aware that, historically, JavaScript hasn't been a good choice for password hashing due to its speed. If a native implementation is 10x faster than a JavaScript implementation, the JavaScript will have to run 10x longer for the same security. A WebAssembly implementation may work better, but benchmarking is recommended.

AndrolGenhald
  • 15,436
  • 5
  • 45
  • 50
  • What random salt values we can choose for 1x Bcrypt and 2x HKDF ? – Dachstein Mar 14 '19 at 16:33
  • @Dachstein I'm not sure what you mean, are you asking how to generate the salts? If you want to generate them client-side you'd want to use [`window.crypto.getRandomValues()`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues). – AndrolGenhald Mar 14 '19 at 16:36
  • Then we store them on the server? – Dachstein Mar 14 '19 at 16:37
  • You'd have to yes, if you want the account to be accessible from more than one browser/computer. The obvious downside is that the client-side bcrypt and login salts will have to be exposed to anyone who knows the username, but I'm not sure that there's a way around this. You could wait till the user is authenticated to send the encryption key salt, though I'm not sure this really gains you much. – AndrolGenhald Mar 14 '19 at 16:41
  • Got your point. But still scratching my head about this salts. I really dont want to expose the salts to anyone who just knows the username (email). – Dachstein Mar 14 '19 at 16:43
  • Indeed it's not ideal, but if you want to minimize trust in the server the only other option is to have the client remember the salt, at which point they're essentially just lengthening their password. If it makes you feel any better, the random salt for the server-side bcrypt is never exposed, so rainbow tables couldn't be pre-generated for those hashes, only for the login-key that's sent to the server. Educating users that their data is only as secure as their password seems like the only option here. – AndrolGenhald Mar 14 '19 at 16:46
  • Look at it this way: you're trying to minimize trust in the server, which means the server is considered a potential enemy. Given that, your salts are all _necessarily_ exposed if they're stored on the server. – AndrolGenhald Mar 14 '19 at 16:51
  • How about the password in the very beginning runs through a HKDF function for each salt needed? – Dachstein Mar 14 '19 at 16:54
  • You mean [deriving the salt from the password](https://security.stackexchange.com/a/36882)? That sort of defeats the purpose of the salt :P Also, HKDF _requires_ a salt, so now you have a chicken and egg problem. – AndrolGenhald Mar 14 '19 at 16:57
  • Yes thats right, face-palm :) – Dachstein Mar 14 '19 at 16:59
  • So the email would not be good enought to act as a the pseudo-random salt? – Dachstein Mar 14 '19 at 17:03
  • Let us [continue this discussion in chat](https://chat.stackexchange.com/rooms/91043/discussion-between-androlgenhald-and-dachstein). – AndrolGenhald Mar 14 '19 at 17:05
2

This is exactly the problem we faced when designing 1Password (for which I work). If you want the gory details, take a look at the 1Password Security Design document. (Note that there are things that we would do differently if designing that KDF today. Use it for inspiration, but don't copy it entirely.)

Although many of the details are different the overall approach we took is similar. We use distinct salt and distinct HDKF info for deriving the "master unlock key" than is used for deriving the long term client authentication secret.

Don't transmit your authentication secret

Note that we, like you, never want to receive user secrets. So what you are calling the LS is not ever transmitted. Instead we use a Password Authenticated Key Exchange mechanism, in particular SRP. For what it's worth, we've open sourced our SRP implementation. We do make the salt available from the server, but in future versions we may blind the salt, which would prevent some precomputation attacks.

Slow hashing

Your choice of bcrypt is a good choice. You may wish to consider memory-hard KDFs such as Argon2 or scrypt. In our case, we were stuck with PBKDF2 because we needed something for which there were well-optimized implementations in web-browsers. But if you are not looking at having a web-client, definitely look toward bcrypt at a minimum or one of the memory-hard KDFs.

Another note about PBKDF2 is do not use it for key stretching. You are correctly using HKDF for key stretching, but I wanted to put in that warning for others reading this. There is a design error in PBKDF2 which under some circumstances can bite you in the rear when it is used for key stretching.

Limits of slow hashing and toward true uncrackability

Although bcrypt or the like are necessary, there is a point at which all of these slow hashes produce diminishing returns. And people will use bas passwords. So we add into the key derivation function a high entropy secret that is stored only on the client. This gets combined with the Master Password client side. Without that client-held secret, the data that we hold onto cannot be used for password cracking.

Misc

I recommend using random salts instead of deriving them from usernames. It does mean that your service will need to transmit the salt to the user when they set up a new client, but you really should be using some challenge/response between client and server so as not to transmit your login secret.

SHA3-512 is overkill for salt creation if you still do want to derive salt from the username. You simply don't need salts to be that long. The purpose of a salt is to avoid collisions of keys with identical passwords. Sixteen bytes is going to be more than enough for a salt.

Jeffrey Goldberg
  • 5,839
  • 13
  • 18
  • I have read about SRP a bit. Does the SRP protocol forces the browser (client) to remember something from login to login? What I mean is it stateless on the client? – Dachstein Mar 15 '19 at 08:38
  • 1
    You say in future versions you want to blind the salt. Having this in mind? OPAQUE: An Asymmetric PAKE Protocol Secure Against Pre-Computation Attacks: https://eprint.iacr.org/2018/163.pdf – Dachstein Mar 15 '19 at 08:41
  • Well spotted. Yes, the idea of salt blinding is derivative from OPAQUE, but it is very very new, and hasn't been thoroughly reviewed. Normal SRP does not require any persistence from session to session. Our think with the Secret Key does, but SRP itself doesn't. – Jeffrey Goldberg Mar 15 '19 at 17:39