← Todos los artículos

Compartir datos cifrados por medios de formato corto

En esta página

Compartir grandes cantidades de datos por medios de formato corto, como códigos QR o etiquetas NFC, es difícil. Hay que cifrar los datos para garantizar la confidencialidad, pero meter datos útiles en poco espacio no es fácil.

Yo buscaba una forma de compartir datos de manera segura por un medio de formato corto con un destinatario con el que no tengo ninguna confianza previa.

Mientras trabajaba en ello, me inspiraron el Signal Protocol y el Noise Protocol Framework.

Los dos protocolos combinan cifrado simétrico y asimétrico para lograr un nivel alto de seguridad con la menor cantidad posible de datos transferidos.

Yo quería lograr algo parecido, pero sin depender del cifrado asimétrico. Quería cifrar los datos con una clave simétrica y compartir esa clave de igual a igual por el medio de formato corto. También quería un intermediario centralizado que guardara los datos cifrados y me diera un token.

En algunos aspectos, se parece a la computación multiparte segura.

Se me ocurrió un protocolo al que llamo Tokenized Encrypted Payload Sharing (TEPS). Lo documento más abajo. No estoy seguro de mi enfoque, así que agradecería mucho tus comentarios.

Tokenized Encrypted Payload Sharing (TEPS)

Es una técnica para cifrar datos que un intermediario guarda de forma temporal. No se confía al intermediario el contenido de los datos, pero sí se confía en que los tokenice. El cliente cifra los datos con una clave simétrica y el token sirve para recuperarlos del intermediario. Después, el cliente puede descifrarlos con la misma clave simétrica. La técnica sirve para compartir grandes cantidades de datos por medios de formato corto, como códigos QR o etiquetas NFC.

Un ejemplo es un móvil que comparte muchos datos con otro móvil. El emisor cifra los datos y los envía a un intermediario. El intermediario guarda los datos cifrados y devuelve un token. Luego el emisor comparte el token y la clave simétrica con el destinatario por un medio de formato corto, como un código QR o una etiqueta NFC. El destinatario usa el token para recuperar los datos cifrados del intermediario. Después puede descifrarlos con la misma clave simétrica, o compartir el token y la clave simétrica con un tercero para que también pueda recuperarlos.

Especificación

1. Introducción

Esta es la especificación del protocolo TEPS. El protocolo explica cómo compartir datos cifrados mediante un intermediario y cómo compartir el token y la clave simétrica de igual a igual.

2. Terminología

  • Intermediario: un servidor centralizado de confianza que guarda los datos y entrega un token.
  • Clave de cifrado simétrica: una clave simétrica aleatoria con la que se cifran los datos.
  • Token: un ID único con el que se recuperan los datos del intermediario.
  • Cliente: la entidad que cifra los datos y los envía al intermediario. (p. ej., un móvil)
  • Medio de compartición: el medio con el que se transfieren el token y la clave de cifrado simétrica entre clientes. (p. ej., un código QR, una etiqueta NFC, etc.)

3. Protocolo

3.1 Resumen

El cliente genera una clave simétrica aleatoria y cifra los datos con ella. Luego envía los datos cifrados al intermediario a cambio de un token. El intermediario guarda los datos cifrados y el token, y devuelve el token al cliente. El cliente combina el token y la clave simétrica y los comparte con el cliente destinatario por el medio de compartición (p. ej., un código QR, una etiqueta NFC, etc.). El destinatario usa el token para recuperar los datos cifrados del intermediario. Por último, el cliente destinatario descifra los datos con la clave simétrica.

En algunos casos, el destinatario puede retransmitir la información a otra parte, pero esta especificación no lo cubre.

3.2 Proceso

3.2.1 Generar la clave simétrica

El cliente genera una clave simétrica aleatoria. La clave simétrica sirve para cifrar los datos. Más adelante se combinará con el token del intermediario para compartirla de igual a igual. La clave simétrica puede tener cualquier longitud, pero conviene mantenerla en un tamaño adecuado para el medio de compartición.

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

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

3.2.2 Cifrar los datos

El cliente cifra los datos con la clave simétrica usando el algoritmo AES-256 (o uno similar).

Por ejemplo:

// 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 Enviar los datos al intermediario

El cliente envía los datos cifrados al intermediario a cambio de un token.

Por ejemplo:

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

{
  "data": "encrypted data"
}

En código:

// 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 Guardar los datos y generar el token

El intermediario guarda los datos cifrados y genera un token aleatorio. El token sirve para recuperar los datos del intermediario. Puede tener cualquier longitud, pero conviene mantenerlo en un tamaño adecuado para el medio de compartición.

Después, el intermediario devuelve el token al cliente.

Por ejemplo:

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

{
  "lookup_id": "random token"
}

En código:

// 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 Caducar los datos (opcional)

El intermediario puede hacer que los datos caduquen pasado un tiempo. Puede usar el método que quiera.

Guarda la fecha de caducidad junto con los datos:

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

Usa un cron job para borrar los datos caducados.

3.2.6 Combinar el token y la clave simétrica

El cliente combina el token y la clave simétrica. Puede empaquetarlos como quiera, siempre que el método sea compatible con el medio de compartición y con el cliente destinatario.

Para esta especificación, el token y la clave simétrica se combinan con este formato:

<lookup_id><symmetric_key>

3.2.7 Compartir el token y la clave simétrica

El cliente comparte el token y la clave simétrica con el cliente destinatario por el medio de compartición (p. ej., un código QR, una etiqueta NFC, etc.).

3.2.8 Recuperar los datos del intermediario

El cliente destinatario recupera del intermediario los datos cifrados con el token.

Por ejemplo:

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"
}

En código:

// 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 Descifrar los datos

El cliente destinatario descifra los datos con la clave simétrica.

Por ejemplo:

// 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)
}

Petición de comentarios

Agradecería cualquier comentario sobre este protocolo. Si tienes alguno, escríbeme.

¡Gracias por leer!