Per-User Encryption in Elixir Part I

Using passwords to encrypt user data

One of the projects I’m working on has one important feature — the privacy of users’ data. Basically, the ability for users to secure their data so that only that user can access the data. Even in case of a hack, the hacker should not be able to have access to the user’s data. This means all such data stored on the server must be encrypted and decrypted only when the user wants to access that data. In that case, all that the hacker will have is encrypted data.

In this 2-part post, we will explore how to encrypt user data on the server and make it accessible only to that user. The first part will explain the concepts and design considerations. In the second part, we’ll look at a sample project using Elixir and Phoenix.

Concepts

Symmetrical vs Asymmetrical Encryption There are two main kinds of encryption — symmetrical and asymmetrical. Understanding the difference is important in designing a secure system.

Symmetrical encryption is simple and works by using a single key to both encrypt and decrypt data. This means you have to use the same key used to encrypt the data to decrypt the data. Most importantly, if the encryption and decryption are happening at different points, then the key must be present at both points or must be sent across. This poses a security risk as the key might end up in the wrong hands while in transit.

Asymmetrical encryption (also known as public-key cryptography) uses public and private keys to encrypt and decrypt data. A message that is encrypted using a public key can only be decrypted using a private key, while also, a message encrypted using a private key can be decrypted using a public key. This makes asymmetrical encryption very secure since you can share your public key while keeping your private key a secret.

We’ll use symmetrical encryption in this post since our demo project in Part II, will be a simple web app and all encryption and decryption happens in one place — the server. For better security, asymmetrical encryption should be used especially when you have a client app such as a mobile app or a desktop app. In that case, encryption can happen on the server, and decryption happens on the client app. Also, remember to always use HTTPS. I will do another post using asymmetrical encryption in the near future.

User token Per-user encryption requires a token that will be used to encrypt data and has to be supplied by the user in order to decrypt data. A common example is a password or a PIN. We’ll use a password. Passwords are user-generated, so for security reasons, we’ll have to salt passwords. This means two or more users with similar passwords, will still result in different key hashes.

General Idea

First, when the user registers, with say email and password, we’ll take the password(the token) and unique salt, and derive a key for that user. The derived key will then be used to encrypt a unique key for the user. The unique salt and encrypted key (called a key hash) will then be saved in the database along with the user’s details.

On login, with say email and password, we’ll take the password, retrieve the salt from the user’s key hash, and again, derive the key for the user. Next, using the password-derived key, we will decrypt the encrypted key saved in the database for that user to get the user key. Finally, the key will be used for data encryption and decryption for that user.

Three most important terms to understand:

user key: unique key generated for each user on registration. We’ll use this key to encrypt and decrypt all user’s data. It remains unchanged for a user.

password derived key: Key generated based on the user’s password and a unique salt. The unique salt ensures that similar passwords result in different keys; very important for security reasons since passwords are user-generated and different users may have the same password. The salt will also be saved so we can get the same password-derived key given the same password. With the password-derived key, we’ll encrypt the user key. Using password derived allows the user to change their password while having access to previously encrypted data. We’ll see how that’s done below.

key hash: Key hash consists of 2 parts — the encrypted user key and the salt used to generate the password derived key. To decrypt the key hash to get the user key, we’ll need the user’s password, combine it with the salt (first half of the key hash) to generate the password-derived key. Then with the password-derived key, we can decrypt the encrypted user key (the second part of the key hash).

The diagrams below explain the mechanism for generating password derived key and user key hash.

User password and unique salt are passed through a key derivation function to generate password derived key

Generating password derived key using unique salt and user password.

Generating password derived key using unique salt and user password.

Then using the password-derived key, encrypt the user key.

Using password derived key to encrypt user key.

Using password derived key to encrypt user key.

Combine salt and encrypted key to get key hash. Save key hash to the database

key hash consists of unique salt and encrypted user key. Save key hash to the database

key hash consists of unique salt and encrypted user key. Save key hash to the database.

Understanding the relationship between the above terms is very important

Summary of Various Steps

On Registration

  1. Take user password

  2. Derive a key from user password using a unique salt

  3. Generate user key

  4. Encrypt user key with password derived key

  5. Save key hash — salt and encrypted user key — to database

Generating user’s key hash on registration.

Generating user’s key hash on registration.

On Encrypting data

  1. User login with password

  2. Retrieved user key hash — containing salt and encrypted key

  3. Using salt and user password, generate password derived key

  4. Decrypt encrypted key (the second part of key hash) with the password derived key

  5. Use the decrypted key to encrypt user data

  6. Save encrypted data to the database

Encrypting user data.

Encrypting user data.

On Decrypting data

  1. User login with password

  2. Retrieved user key hash — containing salt and encrypted key

  3. Using salt and user password, generate password derived key

  4. Decrypt encrypted key (the second part of key hash) with the password derived key

  5. Use the decrypted key to decrypt user data

  6. Send decrypted data to the user — HTML or JSON

Decrypting encrypted user data

Decrypting encrypted user data

On Password Change

  1. The user supplies both the old password and a new password

  2. Retrieved user key hash — containing salt and encrypted key

  3. Using salt and old password, generate old password-derived key

  4. Decrypt the encrypted key (the second part of key hash) with the old password derived key to get user key

  5. Using a new salt and new password, generate new password-derived key

  6. Re-encrypt the decrypted user key with new password-derived key

  7. Save new key hash — new salt and re-encrypted user key — to database

Changing user password and generating new key hash

Changing user password and generating new key hash

Note on Web Sessions For web apps with a persistent session — keep users logged-in till they log out, save the decrypted user key in session so that a user’s password is not required every time you need to decrypt or encrypt data.

In the next post, we will implement the system in a simple web app using Phoenix.

Thank you for reading. Happy coding!

--Badu