Skip to main content

Sharing encrypted data over short-form mediums

·1332 words·7 mins
A photo of a key.
Photo by Everyday basics

Sharing large amounts of data over short-form mediums like QR codes or NFC tags can be challenging. The data needs to be encrypted to ensure confidentiality, but packing useful data into a small space is difficult.

I was looking for a way to share data securely over a short-form medium to a recipient that I don’t have pre-existing trust with.

While working on this issue, I was inspired by the Signal Protocol and the Noise Protocol Framework.

Both of these protocols use a combination of symmetric and asymmetric encryption to achieve a high level of security while minimizing the amount of data that needs to be transferred.

I wanted to achieve a similar goal, but I didn’t want to rely on asymmetric encryption. I wanted to use a symmetric key to encrypt the data and share the symmetric key peer-to-peer over the short-form medium. I also wanted to use a centralized intermediary to store the encrypted data and provide a token.

This is similar in some regards to Secure Multi-Party Computation.

I came up with a protocol that I’m calling the Tokenized Encrypted Payload Sharing (TEPS) protocol. I have documented the protocol below. I’m uncertain about my approach so I would really appreciate any feedback.

Tokenized Encrypted Payload Sharing (TEPS) #

This is a technique for encrypting data that is temporarily stored by an intermediary. The intermediary is not trusted with the data, but it is trusted to tokenize the data. The data is encrypted by the client using a symmetric key, and the token is used to retrieve the data from the intermediary. The client can then decrypt the data using the same symmetric key. This technique is useful for sharing large amounts of data over short-form mediums like QR codes or NFC tags.

An example is a mobile phone sharing a large amount of data with another mobile phone. The data is encrypted by the sender and sent to an intermediary. The intermediary stores the encrypted data and provides a token. The sender then shares the token and symmetric key with the recipient using a short-form medium like a QR code or NFC tag. The recipient then uses the token to retrieve the encrypted data from the intermediary. The recipient can then decrypt the data using the same symmetric key, or they can share the token and symmetric key with another party to allow them to retrieve the data.

Specification #

1. Introduction #

The following is a specification for the TEPS protocol. The protocol explains the process for sharing encrypted data via an intermediary and sharing the token and symmetric key peer-to-peer.

2. Terminology #

  • Intermediary: A trusted centralized server that stores the data and provides a token.
  • Symmetric Encryption Key: A random symmetric key used to encrypt the data.
  • Token: A unique ID used to retrieve the data from the intermediary.
  • Client: The entity that encrypts the data and sends it to the intermediary. (ie. A mobile phone)
  • Share Medium: The medium used to transfer the Token and Symmetric Encryption Key between clients. (ie. QR code, NFC tag, etc)

3. Protocol #

3.1 Overview #

The client generates a random symmetric key and encrypts the data using the symmetric key. The client then sends the encrypted data to the intermediary in exchange for a token. The intermediary stores the encrypted data and the token. The intermediary then returns the token to the client. The client then combines the token and symmetric key and shares it with the recipient client using the Share Medium (ie. QR code, NFC tag, etc). The recipient then uses the token to retrieve the encrypted data from the intermediary. The recipient client then decrypts the data using the symmetric key.

In some cases, the recipient can relay the information to another party but that won’t be covered by this specification.

3.2 Process #

3.2.1 Generate Symmetric Key

The client generates a random symmetric key. The symmetric key is used to encrypt the data. The symmetric key will later be combined with the intermediary’s token for sharing peer-to-peer. The symmetric key can be of any length, but it is recommended to keep this to a size appropriate for the Share Medium.

// Generate a 32 byte symmetric key
symmetricKey := make([]byte, 32)

if _, err := rand.Read(symmetricKey); err != nil {
  panic(err)
}

3.2.2 Encrypt Data

The client encrypts the data using the symmetric key using the AES-256 algorithm (or similar).

For example:

// Encrypt the data using the symmetric key
block, err := aes.NewCipher(symmetricKey)

if err != nil {
  panic(err)
}

gcm, err := cipher.NewGCM(block)

if err != nil {
  panic(err)
}

nonce := make([]byte, gcm.NonceSize())

if _, err = rand.Read(nonce); err != nil {
  panic(err)
}

ciphertext := gcm.Seal(nonce, nonce, data, nil)

3.2.3 Submit Data to Intermediary

The client submits the encrypted data to the intermediary in exchange for a Token.

For example:

POST /data HTTP/1.1
Host: api.example.com
Content-Type: application/json
Content-Length: 123

{
  "data": "encrypted data"
}

In code:

// Submit the encrypted data to the intermediary
req, err := http.NewRequest("POST", "https://api.example.com/data", bytes.NewBuffer(ciphertext))

if err != nil {
  panic(err)
}

req.Header.Set("Content-Type", "application/json")

resp, err := http.DefaultClient.Do(req)

if err != nil {
  panic(err)
}

defer resp.Body.Close()

body, err := ioutil.ReadAll(resp.Body)

if err != nil {
  panic(err)
}

var data map[string]interface{}

if err := json.Unmarshal(body, &data); err != nil {
  panic(err)
}

lookupID := data["lookup_id"].(string)

3.2.4 Store Data & Generate Token

The intermediary stores the encrypted data and generates a random token. The token is used to retrieve the data from the intermediary. The token can be of any length, but it is recommended to keep this to a size appropriate for the Share Medium.

The intermediary then returns the token to the client.

For example:

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 123

{
  "lookup_id": "random token"
}

In code:

// Store the encrypted data and generate a token
lookupID := uuid.New().String()

if err := storeData(lookupID, ciphertext); err != nil {
  panic(err)
}

// Return the token to the client
resp, err := json.Marshal(map[string]interface{}{
  "lookup_id": lookupID,
})

if err != nil {
  panic(err)
}

w.Header().Set("Content-Type", "application/json")
w.Write(resp)

3.2.5 Expire Data (Optional)

The intermediary can optionally expire the data after a period of time. The intermediary can use any method to expire the data.

Store the expiry date with the data:

{
  "data": "encrypted data",
  "expiry_date": "2023-07-19T21:12:43+10:00"
}

Use a cron job to delete expired data.

3.2.6 Combine Token and Symmetric Key

The client combines the token and symmetric key. The client can choose how to package the combined token and symmetric key, as long as the method is compatible with the Share Medium and the recipient client.

For the purposes of this specification, the token and symmetric key are combined using the following format:

<lookup_id><symmetric_key>

3.2.7 Share Token and Symmetric Key

The client shares the token and symmetric key with the recipient client using the Share Medium (ie. QR code, NFC tag, etc).

3.2.8 Retrieve Data from Intermediary

The recipient client retrieves the encrypted data from the intermediary using the token.

For example:

GET /data/<lookup_id> HTTP/1.1
Host: api.example.com
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 123

{
  "data": "encrypted data"
}

In code:

// Retrieve the encrypted data from the intermediary
resp, err := http.Get(fmt.Sprintf("https://api.example.com/data/%s", lookupID))

if err != nil {
  panic(err)
}

defer resp.Body.Close()

body, err := ioutil.ReadAll(resp.Body)

if err != nil {
  panic(err)
}

var data map[string]interface{}

if err := json.Unmarshal(body, &data); err != nil {
  panic(err)
}

ciphertext := data["data"].(string)

3.2.9 Decrypt Data

The recipient client decrypts the data using the symmetric key.

For example:

// Decrypt the data using the symmetric key
block, err := aes.NewCipher(symmetricKey)

if err != nil {
  panic(err)
}

gcm, err := cipher.NewGCM(block)

if err != nil {
  panic(err)
}

nonceSize := gcm.NonceSize()

nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]

plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)

if err != nil {
  panic(err)
}

Request for Comment #

I would appreciate any feedback on this protocol. Please feel free to contact me if you have any feedback.

Thanks for reading!

Author
Will Hackett
I’m Will, a software engineer and architect based in Melbourne, Australia. I’m currently working at Blinq.