I've recently done some work on an application that provides a way of sharing text data. The data is encrypted on server, but the server operator cannot decrypt or modify the data.

Here is some notes on the cryptographic scheme. It's an implementation of Authenticated Encryption and Associated Data.

The application has 2 modes of working, one with user defined passphrase to the data, and one without. Both are very similar.

The encryption function used is AES256-CTR, with a random initial value, the HMAC is HMAC-SHA512, with pbkdf2 for key derivation function.

Initial configuration

We generate 3 salts that we use later for key generation

$ salt_{enc} \overset{R}{\leftarrow} \{ 0,1 \} ^{128} $

$ salt_{hmac} \overset{R}{\leftarrow} \{ 0,1 \} ^{128} $

$ salt_{filename} \overset{R}{\leftarrow} \{ 0,1 \} ^{128} $

These salts remain constant for the application, and if changed we cannot retrieve any data from the application.

Encryption without user defined passphrase

For each use of the application

We generate a random 128 bit nonce to seed some key derivation functions.

$ nonce \overset{R}{\leftarrow} \{ 0,1 \} ^{128} $

We generate a random 96 bit initial value for a 128 bit counter, this gives ability to encode up to 2^{32} blocks

$ iv \overset{R}{\leftarrow} \{ 0,1 \} ^{96} $

We use the nonce and a key derivation function to generate keys for encryption, a hmac, and a random filename

$ KDF(salt_{enc}, nonce) = K_{enc} $

$ KDF(salt_{hmac}, nonce) = K_{hmac} $

$ base64.encode(KDF(salt_{filename}, nonce)) = filename $

We encrypt the plaintext and calculate an HMAC tag for the counter initial value concatenated with the encrypted data

$ t_{hmac} = HMAC(K_{hmac},salt_{hmac}, iv || E(K_{enc},iv,plaintext)) $

We encode the filename random with base64, and use that as the filename to store the tag, iv and encrypted data

$ t_{hmac} || iv || E(K_{enc},iv,plaintext) {\rightarrow} {filename} $

and return the nonce as the to the client encoded in the url

$ base64.encode(nonce) {\rightarrow} url $

To decrypt and return the message

We take the url that is requested and base64 decode it to get the nonce

$ nonce {\leftarrow} base64.decode(url) $

This is used to generate the stored filename, encryption and hmac keys

$ KDF(salt_{enc}, nonce) = K_{enc} $

$ KDF(salt_{hmac}, nonce) = K_{hmac} $

$ base64.encode(KDF(salt_{filename}, nonce)) = filename $

Read the data stored in filename, parsing the first 64 bytes as a HMAC tag, the next 12 bytes as a counter initial value, and the remaining as the cipher text

$ data {\leftarrow} filename $

$ t_{hmac} = data[:64] $

$ iv = data[64:76] $

$ ciphertext = data[76:] $

We decrypt the ciphertext, and recalculate the HMAC tag to compare

$ plaintext = D(K_{enc}, iv, ciphertext ) $

$ {t'}_{hmac} = HMAC(K_{hmac},salt_{hmac}, iv || ciphertext) $

if $ {t'}_{hmac} == t_{hmac} $ we return the $ plaintext $ otherwise we return a file not found

Encryption with a user defined passphrase

This is very similar, but we use a user defined passphrase to generate the keying material.

For each use of the application

We generate a random 128 bit nonce to seed some key derivation functions.

$ nonce \overset{R}{\leftarrow} \{ 0,1 \} ^{128} $

We generate a random 96 bit initial value for a 128 bit counter, this gives ability to encode up to 2^{32} blocks

$ iv \overset{R}{\leftarrow} \{ 0,1 \} ^{96} $

We use the user passphrase, nonce and a key derivation function to generate keys for encryption, a hmac, and a random filename

$ KDF(salt_{enc}, passphrase) = K_{enc} $

$ KDF(salt_{hmac}, passphrase) = K_{hmac} $

$ base64.encode(KDF(salt_{filename}, nonce)) = filename $

We encrypt the plaintext and calculate an HMAC tag for the counter initial value concatenated with the encrypted data

$ t_{hmac} = HMAC(K_{hmac},salt_{hmac}, iv || E(K_{enc},iv,plaintext)) $

We encode the filename random with base64, and use that as the filename to store the tag, iv and encrypted data

$ t_{hmac} || iv || E(K_{enc},iv,plaintext) {\rightarrow} {filename} $

and return the nonce as the to the client encoded in the url

$ base64.encode(nonce) {\rightarrow} url $

To decrypt and return the message

We take the url that is requested and base64 decode it to get the nonce, additionally we recieve the passphrase

$ nonce {\leftarrow} base64.decode(url) $

This is used to generate the stored filename, encryption and hmac keys

$ KDF(salt_{enc},passphrase) = K_{enc} $

$ KDF(salt_{hmac}, passphrase) = K_{hmac} $

$ base64.encode(KDF(salt_{filename}, nonce)) = filename $

Read the data stored in filename, parsing the first 64 bytes as a HMAC tag, the next 12 bytes as a counter initial value, and the remaining as the cipher text

$ data {\leftarrow} filename $

$ t_{hmac} = data[:64] $

$ iv = data[64:76] $

$ ciphertext = data[76:] $

We decrypt the ciphertext, and recalculate the HMAC tag to compare

$ plaintext = D(K_{enc}, iv, ciphertext ) $

$ {t'}_{hmac} = HMAC(K_{hmac},salt_{hmac}, iv || ciphertext) $

if $ {t'}_{hmac} == t_{hmac} $ we return the $ plaintext $ otherwise we return a file not found