2
$\begingroup$

I'm a cryptography novice, but I think that the world needs to move towards an "encrypt everything" mentality as much as possible. As a result, I've been thinking a lot about ways to build a web app that stores all data on the server in a way that the server can't decrypt the data even if it wanted to.

Here's my thinking. I'd love feedback.

(Let me start by saying everything would be done over SSL. This isn't meant to protect the data over the wire, but to protect the data from the server.)

Server = any web server/technology, Client = a JavaScript web app

When a user signs up OR changes his/her password:

  • Either an RSA key pair is provided by the customer (high security, less convenient) OR a key pair is generated by the server and sent to the client (customer has to trust that we won't store the private key, but more convenient)
  • Also, a UUID is generated by the server and sent to the client
  • The user enters their password into the client—this password is NEVER sent to the server
  • The client uses PBKDF2 to generate a secret key
  • This secret key is used to encrypt:
    • The user's private key
    • The UUID sent from the server
  • The client then sends the following to the server:
    • The public key
    • The encrypted private key
    • The original UUID
    • The encrypted UUID

When a user logs in:

  • The client sends the username to the server
  • The server responds with the unencrypted UUID & a new UUID
  • The client sends the following back to the server:
    • The unencrypted UUID
    • An encrypted copy of the UUID using the same method as above
    • The unencrypted new UUID
    • An encrypted copy of the new UUID
  • If the encrypted copy matches the encrypted copy on the server, the user is considered authenticated, and the new unencrypted/encrypted UUID pair is stored for the next time this used needs to log in
  • The server responds with an authentication token that is used for all further requests during this session, as well as the public key and encrypted private key

As the app is used

All data is encrypted using the public key before it's sent to the server, and decrypted using the private key in the client. If the server has to add content to the database, it's encrypted using the public key so that the server only has access to the data while it's generating it. Obviously this would not work in all use-cases, particularly highly relational data, but I think if the structure of the data is generally maintained, it wouldn't matter what the content was.

For example:

{
    "username": "jsmith",
    "name": "John Smith",
    "message": "Hello there!"
}

Can be handled by the server very similarly to:

{
    "username": "U2FsdGVkX1+B9f7mMjoU9sWnYU79eN2IiDxNsetNeUI=",
    "name": "U2FsdGVkX188VmG67BS5lGDEs8NAUsQrG8eaVmyOlIg=",
    "message": "U2FsdGVkX1+7cqObxGFq2ZR3rc05NznGdoPENDujAa4="
}

So maybe not all metadata could be encrypted, but it's definitely a step in the right direction.

I'd love feedback! Like I said, I'm a complete novice, so I could have made a terrible assumption somewhere in there, but it seems like it theoretically would work…

One final note: I recognize that JavaScript isn't ideal for encryption. That said, I think it's acceptable in this case because we're using JavaScript as an added layer on top of SSL & all other existing security best-practices.

$\endgroup$
2
  • 1
    $\begingroup$ Your words: "Let me start by saying everything would be done over SSL. This isn't meant to protect the data over the wire, but to protect the data from the server.)" make no sense. SSL can't protect the data from the server. The server gets an unencrypted stream of data after the SSL decryption. $\endgroup$
    – jfriend00
    Commented Sep 15, 2014 at 3:55
  • 1
    $\begingroup$ @jfriend00 : $\:$ "This" is the part of the OP that comes after your quote. $\;\;\;\;$ $\endgroup$
    – user991
    Commented Sep 15, 2014 at 9:57

3 Answers 3

4
$\begingroup$

all those concerns have been studied a lot and still are. I'll try to give some keywords for them.

a web app that stores all data on the server in a way that the server can't decrypt the data even if it wanted to.

Solution for this is User-side encryption. That's why, forget about the server chosing the encryption key himself.

It's quite well spread already. Problems happen when the server is supposed to do something with the data, since he's unable to read it. To tackle this there is a lot of efforts being put in homomorphic encryption, with wich you can manipulate encrypted data without the need (and the possibility) of decrypting it.

When the Server "Has to add data by himself" it works to use public key mechanism, but you cannot have a proof that he did not keep a copy of it, so this falls back to protecting the data against an external attacker (someone who steals the hard disk for instance), disk encryption should do the job and is available almost everywhere.

About the way of logging in without revealing the password, it seems that it called Zero-Knowledge Password Policy Checks (ZKPPC). See this recent paper about it: http://eprint.iacr.org/2014/242

$\endgroup$
3
$\begingroup$

First observe that the password is all that protects the user's data from the server. There is no security added by the public and private keys.

Here's a simple solution that should work reasonably well. It may even be secure...

Assumptions

We rely on a secure symmetric cryptosystem $(E,D)$ that supports authenticated data, that is, $c = E(k, ad, m)$ and $m = D(k, ad, c)$.

There are two players, the server and the client. We shall assume that there is a secure, unauthenticated channel between client and server.

The client has a password $pw$ (entered by a user or retrieved from secure storage or whatever).

Client preparation

The client derives two keys $k_a$ and $k_e$ from the password (using PBKDF2 or whatever).

Registration

On registration, the client sends $k_a$ to the server. It also chooses a key $k$ and sends $c_0 = E(k_e, -, k)$ to the server.

Connection

To authenticate the channel, the client and server uses $k_a$ and any reasonable mutual authentication protocol that ties authentication to the channel. (Since $k_a$ contains knowledge about the password, you may want to use a password-based protocol.)

Once the channel has been authenticated, the server immediately sends $c_0$ to the client. The client decrypts $c_0$ and recovers $k$.

Storage and retrieval

The client stores a pair of metadata and plaintext $(m_0, m_1)$ on the server by computing $c = E(k, m_0, m_1)$ and sending $(m_0, c)$ to the server. The server stores the pair.

To client can now recover $(m_0, m_1)$ by retrieving $(m_0, c)$ from the server and decrypting $c$.

Password change

To change the password, the user derives $k_a'$ and $k_e'$ from the new password, computes $c_0' = E(k_e', -, k)$ and sends $k_a'$ and $c_0'$ to the server.

$\endgroup$
2
$\begingroup$

Either an RSA key pair is provided by the customer (high security, less convenient) OR a key pair is generated by the server and sent to the client (customer has to trust that we won't store the private key, but more convenient)

Any reason not to generate a key pair in the client app? Nowadays even browsers should have access to sufficient entropy for cryptographic key generation. If RSA key generation is found to be too performance intensive, you could use elliptic curve cryptography, where key generation is often fast.

If the encrypted copy matches the encrypted copy on the server, the user is considered authenticated, and the new unencrypted/encrypted UUID pair is stored for the next time this used needs to log in

This is at best abuse of the encryption algorithm. There is generally no guarantee that encrypting the same message twice results in the same ciphertext (which is known as deterministic encryption). In fact it is a very desirable property of an encryption algorithm that it does not!

The server could, instead, ask the client to sign the UUID, then verify that signature using a public key. In this case "UUID" need not be stored on the server, it can instead be a random, unique one time challenge that the server generates and only keeps in memory until it gets a response. (In this case you may want two key pairs.)

One final note: I recognize that JavaScript isn't ideal for encryption. That said, I think it's acceptable in this case because we're using JavaScript as an added layer on top of SSL & all other existing security best-practices.

Note that if this is meant to be a "web page" where the client downloads the JavaScript from the server immediately prior to use, all bets are generally off. The server can simply provide a version of the application that leaks the private key. This is as opposed to an installed application that just happens to use JS, which has to be compromised from the get-go.

OTOH, the encryption could still have value against third parties, even if not against the server. If someone breaks into the server and gets the database, they'll only see encrypted data, unless they go through the trouble of replacing the application with a key-stealing one and manage to remain undetected.

$\endgroup$
3
  • $\begingroup$ Three great points. For some reason I didn't realize that you could generate a key pair in the browser. Also, that makes much more sense—using a signature instead of a pre-encrypted value. I'm used to hashing algorithms where the result is consistent! As for the whole "web app" problem, I"m not sure how much this can be mitigated using current technology. I guess I'm trying to come up with the best option that's available today… $\endgroup$
    – inxilpro
    Commented Sep 15, 2014 at 14:01
  • $\begingroup$ @inxilpro : $\:$ Allow users to install an application if they wish, and list the SHA256 hashes of each version of that application. $\;\;\;\;$ $\endgroup$
    – user991
    Commented Sep 15, 2014 at 14:26
  • $\begingroup$ Not always the browser downloads JS before executing it, sometimes it's cached, either with HTTP, AppCache or Service Workers. Why don't someone create a spec which allows the user to control what code is loaded similar to a downloaded application based on Service Workers? I.E.: "there is a new update for this website. Here is the changelog and diff. Do you want to update?" It solves the problem, don't requires the user to download an application or trust in a model where is webstore as almost a CA. $\endgroup$ Commented Jul 15, 2015 at 12:58

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