1

I'm working on making an end-to-end encrypted app that will store sensitive mental health information. The goal is to make it completely impossible for someone with access to the server to see the users raw data. The server acts only as a storage vessel for the users account. It should be completely impossible to access the users data without their password.

Currently what my app does when you sign up is the client generates a DEK and uses that to encrypt the data. The app also generates a KEK derived from the users password plus a random salt and uses it to encrypt the DEK and sends that along with the salt to the server. When the user signs in on another device, their encrypted records, KEK salt, and encrypted DEK are sent to the client. The client then uses the KEK salt along with the password to re-generate the KEK and decrypt the DEK, which can then be used to decrypt the data.

The problem is, currently I'm using asp.net identity on the backend to handle issuing tokens. The problem with this is the password is sent to the backend in plain text completely defeating the entire point of this shebang. If a malicious actor, rather that be a hypothetical version of me trying to sell my customers data or an attacker in my server, wanted to access the customer data, they could simply harvest passwords as they come in, pull the KEK salt and encrypted DEK from the database, and follow the same process the client would to access the data.

My idea is to hash the password on the client side, and use that as the users "password" on the backend. asp.net would then hash it again like it does for any other password so an attacker in the database could not later exchange the hash for a token.

The problem is salting the hashed password before it is sent to the server. I can't just randomly generate a salt like I do on the server-side, because then when the user signs in from another device, that device won't know what the salt was and won't be able to regenerate the hash. I can't derive the salt from the password because then they would not be unique per account if 2 accounts had the same password. So my idea was to derive the salt from the email using a KDF. If the user ever changes their email, the client could just also provide an updated password hash and already authenticated devices would be unaffected since they already have tokens.

I don't want the user to have a second encryption password on top of the authentication password. I am certain this flow is somehow possible to do securely, as services like bitwarden and firefox do it.

Thanks in advance

6
  • 2
    If the app is a web app (Javascript), there's little protection for your users. A corporate proxy usually does TLS Interception for security scanning, they can change the source of the scripts and intercept anything. To be end to end, there must be some other party decrypting data on the other side. Who will it be? How they will get the decryption key?
    – ThoriumBR
    Commented May 14 at 18:54
  • @ThoriumBR I understand that but the same issue exists with normal server side hashing. The goal is not to keep the data from the users corporate networks IT team but to allow the user to be confident we will not access or leak their data. By end to end I mean from the user back to themselves if they sign out and back in or sign in on another device Commented May 14 at 19:01
  • 1
    Don't think there's any reason to salt client-side. (that helps with rainbow-table attacks on client?) Commented May 14 at 19:57
  • 2
    See protonmail.com/blog/encrypted_email_authentication for an interesting read on how protonmail uses SRP to derive two keys from the user's password - one that is used to authenticate with the server, and another that is used for client-side encryption.
    – mti2935
    Commented May 14 at 20:12
  • @browsermator my thinking was if our server was compromised the attacker could intercept some passwords and without salts they'd be too easy to crack Commented May 15 at 13:23

5 Answers 5

0

It means that if the communications is logged or intercepted the salt can be intercepted, any other user could look to see the method and since it is client side you might presume the hash method could be know in addition. That means it is insecure to use client-side salt unless your database storage uses different salt. What should happen is the encrypted communications are sent to the remote-server and salt is added to store the record in the database - even if it is the user phone number - so that without access to the server and program the method cannot be known even if the database is accessed.

2
  • this is why i mentioned the hash would be used as the asp.net identity "password". It will get hashed again by asp.net Commented May 15 at 13:19
  • The salt being intercepted should not pose any meaningful security risk. As Sjoerd points out, noone would ever pre-compute a rainbow table per user in practice to have just in case of a password leak. Some protocols considered secure are even built on the premise of publicly exposing the salts, such as the SRP protocol.
    – n-l-i
    Commented May 16 at 6:32
0
  1. Since your users send unencrypted data to the server, server sees unencrypted data before encrypting them and storing. Thus, "If a malicious actor ... wanted to access the customer data", this will be still possible.

  2. Since data are decrypted on the server side, server obtains unencrypted data each time before sending them to the user. Thus, "If a malicious actor ... wanted to access the customer data", this will be still possible.

That's why instead of trying to work the hashing around, I'd suggest you to consider another approach: Encrypt data on the client side before sending them to the server, decrypt data on the client side after receiving them from the server.

As a bonus, your problem with multiple devices will be resolved automatically.

3
  • Data is already encrypted on client side. This question is about authentication specifically. Commented May 15 at 13:16
  • Do you mean that you use authentication password for data encryption? Then this means many problems. 1) When users change authentication passwords, they will not be able to decrypt their data. 2) Leaked passwords in the authentication phase can lead to decryption of client side data. Encryption should be completely independent on authentication.
    – mentallurg
    Commented May 15 at 20:48
  • sharing 1 password for encryption and authentication securely is possible (e.g. protonmail or bitwarden). #1 is not a problem because the key derived from the users password is used to encrypt the actual DEK, and the encrypted DEK is stored on the server. When the user changes their password, only the encrypted DEK needs to be updated. #2 is what im trying to solve here Commented May 16 at 3:15
0

So my idea was to derive the salt from the email using a KDF.

In theory, this makes it possible for attackers to precompute the salt, and precompute many hashes, and use those to quickly brute-force your passwords as soon as they have access to the database.

In practice, this would take too much time and effort and nobody would do this. Creating rainbow tables is already prohibitively expensive, let alone if you have to create one for every possible user.

1
  • Thanks. I don't think what you mentioned is actually possible since in this case asp.net would apply a server side salt to the passwords before storing them. So this is even better then you described I think. The only way premaking a rainbow table would be useful is if you somehow knew which users would login so you could intercept their hashed passwords before they're hashed again Commented May 15 at 13:21
0

In order to make an encryption key out of a password you'll need a slow key derivation function made for passwords, such as scrypt, bcrypt, argon2, etc.
root_key = slow_kdf(password, salt_1)
Then you have one key and it's simple to split this one key into two by using any decent KDF and since it doesn't need to be slow you can use a fast one like HKDF.
kek = fast_kdf(root_key, salt_2)
authentication_key = fast_kdf(root_key, salt_3)
This is done so that your users do not use the same key to encrypt as to authenticate, as this would place the key in your hands. Since your users now have already used a slow KDF on their passwords, there is not much need for the server to do the same. The data encrypted with the kek cannot be stored in a hashed form since it needs to be decrypted which means that if the authentication_key was stored more securely then an attacker would target the kek instead. This results in no need for another slow KDF on the server, the KDF on the client must be strong enough on its own. To prevent an attacker with access to the database to impersonate the user and authenticate, a simple hash of the authentication_key on the server is enough before storage, using for example SHA-256 and you wouldn't need a salt here since the client has already salted the key. The authentication flow can be made more sophisticated but it is not in scope for this question.

salt_2 and salt_3 need to be unique only among each other, so they can be set to different static strings. Maybe simply "salt 2" and "salt 3". salt_1 however needs to be unique for the user and the service. Using the email address or phone number as is would be unique to the user but not necessarily unique to the service as other services may also use the email address as their salt. This is easily fixed by adding your service or url or any other unique identifier to the salt. If you're using email as part of your salt and the user can change their email then, as you mention, you would need some additional logic to reset this.

An alternative to get rid of this extra application logic would be to let the server provide the salt. It would then never need to be reset. The caveat is that the client would need to place some trust in the server, but this is already the case if it's the same server that delivers the frontend code running in the client browser.

-2

The primary point of using a salt is to make sure that your passwords don’t match other people’s passwords. Many people use 123456 as a password. Without salt, they all have the same hash. Lots of passwords with the same hash means the password is easy to crack, so not only is 123456 easy to crack, it will also be targeted.

123456 with a salt means the hash for your ridiculously insecure password is unique. It is insecure, but there is no indication of that to an attacker. You will only be attacked if an attacker wants to get into your account specifically, so most likely not at all. If you get attacked, the salt helps very little except that rainbow tables cannot be used. But the salt removes the bulls eye from your back. And even if the attacker knows your salt, that doesn’t help them.

2
  • 1
    This is incorrect. The purpose of salts is to prevent attackers from brute-forcing multiple hashes at once. Let's say the attacker has a list of n passwords they want to try out, and there are m different user accounts. Without salts, only n hash calculations (one for each passwords) are necessary to attack all m accounts. However, with salts, each hash is parameterized with a salt and has to be attacked individually, leading to m * n calculations. Note that an attacker may also try to reuse hash calculations across different applications, so salts should ideally be globally unique.
    – Ja1024
    Commented May 15 at 18:31
  • 1
    The idea that salts can hide weak passwords or password collisions doesn't make much sense. Attackers aren't stupid. They will of course look for low-hanging fruit first. For example, they'll try a couple of common weak passwords on all accounts, and if they succeed, the accounts are done. Salts don't prevent this.
    – Ja1024
    Commented May 15 at 18:35

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .