Per-User Encryption with Elixir IV

Creating the Elixir Project

Badu
The Startup
5 min readJun 13, 2020

--

In the previous post, we discussed the design of how to share and update encrypted documents with multiple users. In this post, we will create an Elixir project based on those ideas.

Create Elixir Project

Let’s start by creating a new elixir project using mix new command

This will create a new elixir project for us.

Project Dependencies
Since we are working with security stuff, we will need a cryptographic library. We will useenacl (pronounced Na-Cl). The library provides Erlang bindings for the libsodium cryptographic library which we will use for generating public-private keys, perform encryption and decryption; both symmetric and asymmetric.

Update project dependencies in mix.exs

Update dependencies in mix.exs

Add Common Cryptographic Function

Let’s add a module for our common crypto functions inlib/utils.ex
This module provides the needed cryptographic operations for our application as well as some private functions. The module contains functions for generating public-private key pairs (generate_key_pairs/0), encrypting data (encrypt/1, encrypt_message_for_user_with_pk/2) and decrypting data (decrypt/1, decrypt_message_for_user/2)

Utility cryptographic functions

Add User Module

Let’s add User module lib/user.ex to model data on users.
The user struct contains information about a user. username, key_hashand key_pair. The key_hash is a password derived key that unique to the user.
The module also contains new/2 a helper function generating new user struct from username and password. The key_pair field in the user struct contains a public key and a private key hash— which is a private key encrypted with the password derived key for that user. This is important since the private key for a user must be kept a secret. In this case, only that user can decrypt the private key. Note that, we don’t store the password of the user since we are storing the key_hash and we can verify a user’s password by trying to decrypt the key_hash with the password, say, during login.

User and KeyPair modules

Add Document and User Document Module

Add a module for modeling an encrypted document. First, the EncryptedDocument module defines a struct with field id and data_hash — the encrypted content of the document — to represent data needed about our encrypted document. It also contains new/2 (new/3); a helper function for creating a new encrypted document with a key and content (and optional document id). The content will be encrypted with the given key. Note that we do not store the key used to encrypt the document content in the document. That is handled in the UserDocument module.

The UserDocument module defines a struct that links a user to a document. To simplify things, the struct has two fields: id and user_key. The id field is a concatenation of the user’s username and the document’s id. Note that you can keep separate fields for a user_id and document_id in the UserDocument struct. This way we can list easily list all documents for a user and all users that have access to a document. The user_key contains the document key encrypted with the user’s public key.
The module also contains new/3 ; a function which takes in a user, an encrypted document, and a document key. The function returns a user document with the user_key set to the document key encrypted with the user’s public key.

Add Database Module
Create a new file lib/database.ex for our Database module. This will be the main interface for our application— creating and verifying users and creating, updating, and sharing encrypted user documents.

The module contains a struct with fields — users, documents and user_documents — an in-memory structure that holds all the data relevant to running our app. This can easily be represented in a real database as tables.
The users field is a map of username as key and user struct as value; documents field is a map of document id as key and document struct as value. Similarly, the user_documents field is a map with the user-document id as key, and the user-document struct as the value.

The relevant functions in the module include the following:

User APIs
add_user/2 — takes in a database struct and a user — a map containing username and password. This function creates a new user and adds the user to the database.

login_user/2 — takes in a database struct and a user — a map containing username and password. This function verifies the user. The implementation basically checks if the encrypted password-derived user key can be decrypted with the given password.

get_user/2 — takes in a database and username and returns a user with that username (if found) or nil if no user is found with the given username.

Document APIs
add_document/4 takes in a database, a user, the user’s password, and content to create a new encrypted document for that user. If successful, the function returns the document created and the updated database — {:ok, created_document, updated_database} otherwise, an error is returned.

decrypt_document/4 — takes in the database, a user, a document and a password to decrypt an encrypted document. The user must have access to the document and the password must be the correct password for that user. If successful, the decrypted content of the document is returned.

update_document/5 — takes in the database, a user, the document to update, the user’s password and the new content to update the document with. The user must have access to the document and the user’s password must be correct.

share_user_document/5 — takes in the database, the user who is sharing (user A), the user who the document is being shared with (user B), the encrypted document, and the sharer’s password. User A must have access to the document and the password must be correct. First, the function decrypts the encrypted document key for user A using the user A’s password. Once we have the decrypted document key, a user document record is created for user B, using user B’s public key. Next time user B accesses the document they can decrypt the encrypted document key with their private key and successfully access the content of the document.

Running and the code

This should start the Elixir shell with our project loaded. Now let’s test our user API functions

Let’s test our document API functions
We will create an encrypted document for a user and decrypt it

Let’s test sharing the encrypted document with other users

The new user can decrypt the document with their password

You can find the full repo code here on Github.
Happy coding!

--

--