Compartilhando dados criptografados em meios de formato curto
Nesta página
Compartilhar muitos dados em meios de formato curto, como QR codes ou tags NFC, é difícil. Os dados precisam ser criptografados para garantir a confidencialidade, mas encaixar dados úteis num espaço pequeno é complicado.
Eu procurava um jeito de compartilhar dados com segurança, por um meio de formato curto, com um destinatário com quem eu não tenho confiança prévia.
Enquanto trabalhava nisso, me inspirei no Signal Protocol e no Noise Protocol Framework.
Os dois protocolos combinam criptografia simétrica e assimétrica para chegar a um nível alto de segurança, com o mínimo de dados trafegando.
Eu queria um objetivo parecido, mas sem depender de criptografia assimétrica. Queria usar uma chave simétrica para criptografar os dados e compartilhar essa chave de pessoa para pessoa pelo meio de formato curto. Queria também um intermediário centralizado para guardar os dados criptografados e entregar um token.
Isso lembra, em alguns pontos, a computação segura multipartidária.
Cheguei a um protocolo que chamo de Tokenized Encrypted Payload Sharing (TEPS). Documentei o protocolo abaixo. Não tenho certeza da minha abordagem, então qualquer feedback ajuda muito.
Tokenized Encrypted Payload Sharing (TEPS)
Esta é uma técnica para criptografar dados que ficam guardados temporariamente por um intermediário. Não confiamos os dados ao intermediário, mas confiamos que ele os tokeniza. O cliente criptografa os dados com uma chave simétrica, e o token serve para buscar os dados no intermediário. Depois o cliente descriptografa os dados com a mesma chave simétrica. A técnica serve para compartilhar muitos dados em meios de formato curto, como QR codes ou tags NFC.
Um exemplo é um celular compartilhando muitos dados com outro celular. O remetente criptografa os dados e manda para um intermediário. O intermediário guarda os dados criptografados e devolve um token. O remetente então compartilha o token e a chave simétrica com o destinatário por um meio de formato curto, como um QR code ou uma tag NFC. O destinatário usa o token para buscar os dados criptografados no intermediário. Depois descriptografa os dados com a mesma chave simétrica, ou compartilha o token e a chave simétrica com outra parte para ela buscar os dados.
Especificação
1. Introdução
Esta é a especificação do protocolo TEPS. O protocolo explica o processo de compartilhar dados criptografados por um intermediário e de compartilhar o token e a chave simétrica de pessoa para pessoa.
2. Terminologia
- Intermediário: um servidor centralizado de confiança que guarda os dados e entrega um token.
- Chave de criptografia simétrica: uma chave simétrica aleatória usada para criptografar os dados.
- Token: um ID único usado para buscar os dados no intermediário.
- Cliente: a entidade que criptografa os dados e os envia ao intermediário. (por exemplo, um celular)
- Meio de compartilhamento: o meio usado para passar o token e a chave de criptografia simétrica entre clientes. (por exemplo, QR code, tag NFC etc.)
3. Protocolo
3.1 Visão geral
O cliente gera uma chave simétrica aleatória e criptografa os dados com ela. Depois envia os dados criptografados ao intermediário em troca de um token. O intermediário guarda os dados criptografados e o token, e devolve o token ao cliente. O cliente combina o token e a chave simétrica e compartilha o resultado com o cliente destinatário pelo meio de compartilhamento (por exemplo, QR code, tag NFC etc.). O destinatário usa o token para buscar os dados criptografados no intermediário. Por fim, o cliente destinatário descriptografa os dados com a chave simétrica.
Em alguns casos, o destinatário pode repassar a informação a outra parte, mas isso fica fora desta especificação.
3.2 Processo
3.2.1 Gerar a chave simétrica
O cliente gera uma chave simétrica aleatória. A chave simétrica serve para criptografar os dados. Depois ela será combinada com o token do intermediário para ser compartilhada de pessoa para pessoa. A chave simétrica pode ter qualquer tamanho, mas é recomendado mantê-la num tamanho adequado ao meio de compartilhamento.
// Generate a 32 byte symmetric key
symmetricKey := make([]byte, 32)
if _, err := rand.Read(symmetricKey); err != nil {
panic(err)
}
3.2.2 Criptografar os dados
O cliente criptografa os dados com a chave simétrica, usando o algoritmo AES-256 (ou parecido).
Por exemplo:
// 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 os dados ao intermediário
O cliente envia os dados criptografados ao intermediário em troca de um token.
Por exemplo:
POST /data HTTP/1.1
Host: api.example.com
Content-Type: application/json
Content-Length: 123
{
"data": "encrypted data"
}
Em 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 os dados e gerar o token
O intermediário guarda os dados criptografados e gera um token aleatório. O token serve para buscar os dados no intermediário. O token pode ter qualquer tamanho, mas é recomendado mantê-lo num tamanho adequado ao meio de compartilhamento.
Depois o intermediário devolve o token ao cliente.
Por exemplo:
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 123
{
"lookup_id": "random token"
}
Em 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 Expirar os dados (opcional)
O intermediário pode, se quiser, expirar os dados depois de um tempo. Ele pode usar qualquer método para isso.
Guarde a data de expiração junto com os dados:
{
"data": "encrypted data",
"expiry_date": "2023-07-19T21:12:43+10:00"
}
Use um cron job para apagar os dados expirados.
3.2.6 Combinar o token e a chave simétrica
O cliente combina o token e a chave simétrica. O cliente escolhe como empacotar o conjunto, desde que o método seja compatível com o meio de compartilhamento e com o cliente destinatário.
Para os fins desta especificação, o token e a chave simétrica são combinados neste formato:
<lookup_id><symmetric_key>
3.2.7 Compartilhar o token e a chave simétrica
O cliente compartilha o token e a chave simétrica com o cliente destinatário pelo meio de compartilhamento (por exemplo, QR code, tag NFC etc.).
3.2.8 Buscar os dados no intermediário
O cliente destinatário busca os dados criptografados no intermediário usando o token.
Por exemplo:
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"
}
Em 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 Descriptografar os dados
O cliente destinatário descriptografa os dados com a chave simétrica.
Por exemplo:
// 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)
}
Pedido de comentários
Qualquer feedback sobre esse protocolo ajuda. Fique à vontade para falar comigo se tiver algum.
Obrigado por ler!