3

I've narrowed down on a scheme that would allow my application to encrypt user data client side and transmit that data to the server, with no chance of our server ever being able to decrypt the data.

I want to ensure there are no obvious flaws in this scheme.

Method

Note: every step below happens on the client side.

Precursor:

  1. Upon login/registration, generate global key gk = SHA256(user_password) and store gk in a cookie localStorage. (The reason for this is to not store the user's password plainly.)

Encrypting:

  1. For every file, generate a random key fk.
  2. Encrypt file with fk.
  3. Encrypt fk with gk to get fk'.
  4. Transmit encrypted file and fk' to server for storage.

Decrypting:

  1. Receive encrypted file and fk' from server.
  2. Decrypt fk' using gk to get fk.
  3. Decrypt file using fk.

When user changes password:

  1. For every file, decrypt fk' using current gk to get fk.
  2. Generate new gk using new password.
  3. Encrypt fk with new gk to get fk'.
  4. Transmit new file fk' to server for storage.

When user forgets password:

  1. Out of luck. (Unless?)

What flaws exist in this procedure?

Update

One flaw is that if the user authenticates login with a password, then technically, I can easily compute and store gk on the server, thus compromising the promise of client side encryption.

A solution to this:

  1. Generate pw = SHA256(SHA256(user_password)) and send this to the server as the user's password. The server then receives this password, and encrypts it again before storing it.

So as far as the server is concerned, it's just receiving a regular password. But this way, gk cannot be computed server side.

Flaws?

  • 3
    Since gk is stored in a cookie, it's also transmitted to the server - isn't this then enough for the server to decrypt the data? – MrWhite Nov 16 '16 at 21:23
  • @w3d The cookie is stored in the client browser. What makes you think the cookie is sent to the server? – Snowman Nov 16 '16 at 21:30
  • 1
    @maq Browsers the send cookies to the server on every request. – Macil Nov 16 '16 at 21:32
  • 1
    Ah, didn't realize that. Then yeah, in localStorage. – Snowman Nov 16 '16 at 21:37
  • 2 thoughts on the update: a) AgentMe is right, don't use sha256. It's much too fast for hashing passwords. b) I think you should make the master encryption key be independent of the authentication password you use with the server. Right now, if someone manages to steal the localstorage, he gets gk, which he can then hash to yield the login password. Once he has that, he can authenticate with your service. – Out of Band Nov 17 '16 at 0:01
5

The biggest flaw in this scheme is that it depends on javascript code that will be transmitted to the client by the (a) server.

So, what's to keep the server (or a man in the middle) to send the client modified javascript that circumvents all the security measures? How would the client, or, for that matter, the user ever know? All the code that you write to check fingerprints, checksums, digital signatures etc can likewise be circumvented.

A related problem is other, malicious javascript code running in your Browser that can't be reliably isolated from your security-related javascript code.

This is impossible to fix. In-Browser Javascript is currently not a secure platform and all browser client-side cryptography suffers from this gaping security hole that can't be closed.

Summary of the discussion in the comments regarding the updated question

Calculating pw = sha256(sha256(user_password)) strikes me as a bad idea for several reasons.

  1. The first one was already mentioned by AgentMe: Don't use simple hash functions such as sha-whatever to hash passwords. These hash functions were designed to be implemented efficiently in hardware and are much, much too fast; to hash passwords, you want slow hash functions. See AgentMe's answer.
  2. With this scheme, pw can be calculated if you know gk (which is just sha256(user_password)). So when someone steals your localstorage, you give him access both to the master encryption key and to the authentication/authorization token he needs to download the encrypted files. It would be better to keep the two secrets independent of each other, so if one is compromised by a third party, it's not enough to calculate the other one and break the confidentiality of the encrypted data.
  3. Finally, you're not salting your user_password with this scheme. This means that identical passwords across your whole userbase will yield identical hash values (and there are already large rainbow tables for unsalted sha-hashes out there containing the few million most common passwords).

You suggest an alternative, e.g. gk=sha256(user_password) and pw=sha128(user_password). While this solves point 2, it doesn't address 1 und 3. Also I wouldn't recommend it because I could imagine that this needlessly provides additional information to an attacker (the same password hashed to two different hash values), even though I don't know how to exploit it.

I suggested using gk=hmac(user_password, n) and pw=hmac(user_password, m), where m and n are known, but different for each user. How you arrive at m and n is irrelevant, as long as they're random and sufficiently large to make them unique across your user base with a high probability. You could create them on the client and submit them for storage at the server, indexed by the username so they can be retrieved on other clients. Or you can have the server generate them when the user first creates an account. The values don't need to be kept secure; they're worthless without the user_password. An improvement improvement would be to use gk=hmac(bcrypt(user_password), n) and pw=hmac(bcrypt(user_password), m) to make brute force attacks harder.

Now, I'm not convinced that this suggestion is a good idea, since hmac is designed for message authentication, not for creating authentication tokens or encryption keys, so I'd take that suggestion with a grain of salt. But it seems to solve all three points I raise, so it seems like a better solution than chaining the same hash function or using two different hash functions on the password.

  • But if I did server side encryption, then the user will never be satisfied that their data was handled respectfully. And couldn't that same argument be made server side? What if my server was hacked and the unencrypted data was stored plainly? So if both have vulnerabilities, what makes server side encryption a better option? – Snowman Nov 16 '16 at 21:50
  • Also, the client side vulnerability you mentioned puts the responsibility of safety on the user, and not myself, right? Whereas server side, the responsibility for safety would be mine. – Snowman Nov 16 '16 at 21:52
  • The user can't be satisfied that you handled his data respectfully anyway, since there is no way for him to make sure that the server doesn't cheat. I'm not saying you shouldn't use client-side encryption, but you should be aware that your security model is broken before you start to implement it. You can't do it securely, no matter how hard you try, because the platform doesn't allow for it. The user has no choice but to trust the server. – Out of Band Nov 16 '16 at 21:54
  • And yes, getting malicious javascript executed in the browser is the user's fault. :-) – Out of Band Nov 16 '16 at 21:54
  • So then even LastPass is susceptible to this vulnerability? See part on Master Password lastpass.com/support.php?cmd=showfaq&id=6926. No, when you login to LastPass, two things are generated from your Master Password using our code discussed previously before anything is sent to the server: the password hash and the decryption key. This is all done locally. – Snowman Nov 16 '16 at 21:56
1
  1. gk needs to be stored in localStorage/IndexedDB and not a cookie, because cookies get sent to the server!
  2. You should use a stronger password hashing / key derivation algorithm than SHA256 which is more resistant to brute-force.
  3. How does the user authenticate to the server? You probably don't want to let just any user request or update the encrypted fk' files for any other user. You'll want users to authenticate in a way besides sending their plaintext password. You could have them send the gk value put through the key derivation function a second time.
  4. See https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/august/javascript-cryptography-considered-harmful/. You (or someone coercing you, or someone who has hacked you) can get user data by changing the javascript your server gives so that it sends the users' gk values back to the server or otherwise leaks them. This could be mitigated with good use of service workers that have openly-reviewed code but I'm not sure anyone has done that satisfactorily yet.
  • 2. which do you recommend? 3. The user authenticates through HTTPS connection to a regular Rails app. Is this sufficient? 4. Is this a flaw in my design or just a general issue? Are there better methods for me to explore? – Snowman Nov 16 '16 at 21:37
  • 2. Any password hashing algorithm typically used for making password hashes on the server should work, like bcrypt. 3. But what does the user type to be authenticated? What does the server receive? If the user authenticates by typing and sending their password to the server as is usual for websites then the server will have their password and could decrypt their files. – Macil Nov 16 '16 at 21:41
  • The server receives their password to authenticate. You're right then, that seems to be a flaw in this design. Are there any simple edits I can make to this design that would fix this, or is this entire scheme bust? – Snowman Nov 16 '16 at 21:43
  • You could have them send the gk value put through the key derivation function a second time. Can you elaborate on this? – Snowman Nov 16 '16 at 21:59
  • Actually I think I get what you mean. See edits. Let me know if this works. – Snowman Nov 16 '16 at 22:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.