Oasis uses Firebase for its authentication, database, and hosting services. Because of this, Firebase is responsible for handling a lot of sensitive data, such as user accounts and personal data. For this sensitive data, Firebase provides encryption of passwords via SCrypt as well as encryption in transit and encryption at rest which prevents those without administrative rights from viewing the content; however, those with administrative rights are still able to view data stored in the database. Thus, in Oasis, we resolved this issue by implementing end-to-end encryption for data.
Since user's data is stored in Firebase's database, we encrypted the contents of the data prior to its storage. This was done via a hybrid encryption scheme where each user possesses one symmetric data key and a pair of asymmetric encryption keys. In this scheme, the symmetric data key is used to encrypt user data, and the key pair provides a secure way to share data keys between users.
When signing up for Oasis, these keys are randomly generated, and the public key of the paired keys is stored on firebase. The diagram below illustrates the process of how data keys are shared between users that are friends.
Alice and Bob just became friends. At this moment, they exchange data keys so that they can view each other’s posts and comments. This happens in the following manner:
Since users' posts and comments are stored in Firebase's database, we encrypted the contents of the data prior to its storage. This was done via a hybrid encryption scheme where each user possesses one symmetric data key and a pair of asymmetric encryption keys. In this scheme, the symmetric data key is used to encrypt user data, and the key pair is used sharing of data keys between users that are friends.
Suppose that Alice and Bob are two users who are friends. They are able to view each other’s posts and comments in this manner:
Hybrid encryption was chosen because it scales with the size of the plaform given the limited storage space of Firebase's free plan. Each user has a single data key for sending information to all other users. As a result, Firebase is only required to store a single copy of each encrypted data. In contrast to other secure asymmetric encryption schemes, this prevents the database from being cluttered with multiples copies of each posts and comments, limiting space usage of Firebase.
Note:
Our project does not currently have messaging implemented, however the basic overall
structure on how to add encryption to an existing messaging feature will be outlined
below.
For messaging encryption, we chose to use the Signal protocol used in the Signal App. More information can be found here with regards to that project.
We chose this in order to ensure that there was end to end encryption between users messaging each other. This meant, for encryption, we would keep all the encryption private keys only on the user’s local storage. Though, this can be modified to make the availability of private keys more feasible in terms of key storage.
I will first explain our understanding the Signal protocol at a fairly high level view, only going into details when deemed appropriate. Then, I will explain our proposed use of the Signal protocol and finally end with the necessary infrastructure needed to bring it all together and integrate Signal into this project.
There are two people wanting to message each other: Alice and Bob. They need to message securely in order to keep others from viewing any of their messages in transit or at rest when stored on the database. Signal requires them to each have the following keys in order to complete this end to end encryption messaging transaction:
The Identity Key Pair consists of a public/private key pair that will be unique to each Alice and Bob. This will be used for each user to send the other messages. This works as asymmetric encryption normally works: If Bob wants to send a message to Alice, then Bob will need to have Alice’s Public Key in order to encrypt his message with Alice’s public key. Then, Alice will use her private key to decrypt the message and read it in plaintext. This scenario does not include verification that Bob sent the message (no signing of the message’s ciphertext).
This is where part of the PreKey comes into play. The PreKey Pair are one time keys used for an entire session between Alice and Bob: the PreKey Pair are also a public/private key pair, however, whoever starts the session will determine whose PreKey will be used for the session. For example, if Bob starts the session between Alice and him, then Bob’s PreKey will be used for the remainder of the session’s life until the session is terminated (This is briefly explained in the Signal Documentation).
The purpose of the PreKey Pair is to add another level of security in the session between each pair of users. For example, when Bob sends a message to Alice for the first time, he must first establish a session with Alice. This will requrie the following:
Bob will go to a Key Distribution Center (a Database of public keys of all users available to all users, this does not require the actual user Bob to actually see all these keys; this part can be abstracted away to the developer’s liking) and retrieve Alice’s Public Key Bundle. The PreKey Bundle will include:
Once Bob has Alice’s PreKey Bundle/Public Key Bundle, he will use this in order to begin a session with Alice. (For specifics on the how to start a Session, please refer to the Signal Documentation).
Now that Bob has a session started with Alice, he can now send encrypted messages to Alice and likewise, Alice will send encrypted messages to Bob using his PreKey Bundle. Once a session is established however, each of their public keys are stored within the session, so they do not have to retrieve the PreKey Bundle each time from the database.
For our proposed implementation, we chose to have the session information stored on the client side. The session does not store any message text or ciphertext, so the developer must decide where to securely store the messages’ text. An issue arises with this because in order for each user to retain a log of the previously sent and received messages in this session, they must store a copy on their side. An idea we had was that we could store the message log on each client’s local storage using the current client’s public key, that way the client could decrypt the messages when their session is restored (this would allow for the client to store the messages elsewhere also, since only the client has access to their own private key to decrypt the message/thread log). For example, if Bob has sent Alice multiple messages and they have been messaging back and forth, then Alice and Bob must somehow retain a copy of their conversation. This is required for the purpose of the UI: each user will need to have a copy of the thread (messages between Alice and Bob) in order to be able to display to each user the history of the messages sent to and from one another. They cannot share a common thread storage in plaintext because the thread should remain encrypted so others will not be able to read it. This either requires them to share a key that will be used to decrypt the messages thread or they each need to retain of the messages in realtime. We chose the latter for simplicity. Also note, that each user will retain the private key counterpart to each public key that is put into the Key Distribution Center. Also note that each PreKey should only be used per session; this means that a PreKey is used for each session they have with themselves and others. If Alice talks with Bob and Eve in separate threads (and therefore separate sessions), then Bob and Eve should each have a unique PreKey to give to Alice to use when she retrieves their PreKey Bundles. This results in Bob and Eve having to manage the distribution of their PreKeys given out to other users so that each of them will know which private PreKey to refer to (using the key ID) when they are sent a message by Alice. This is all that is required for them to message each other. If this seem simple to you, please note that you might face issues, or, rather obstacles, with regard to how and where keys are stored. Please follow encryption best practices when choosing where to store each user’s private keys.
Below, you will find the attached implementation for one-to-one message. Group messaging was not in the scope of this project yet. There are two files: SignalProtocolStore.js and libsignal-helper.js.