Partager des données chiffrées sur des supports courts
Sur cette page
Partager beaucoup de données sur des supports courts comme les QR codes ou les tags NFC n’est pas simple. Il faut chiffrer les données pour garantir la confidentialité, mais faire tenir des données utiles dans un si petit espace est difficile.
Je cherchais un moyen de partager des données en sécurité sur un support court, avec un destinataire en qui je n’ai pas de confiance établie.
En travaillant sur le problème, je me suis inspiré du Signal Protocol et du Noise Protocol Framework.
Ces deux protocoles combinent chiffrement symétrique et asymétrique pour atteindre un haut niveau de sécurité tout en limitant la quantité de données à transférer.
Je visais le même but, mais je ne voulais pas dépendre du chiffrement asymétrique. Je voulais chiffrer les données avec une clé symétrique, et partager cette clé de pair à pair sur le support court. Je voulais aussi un intermédiaire centralisé pour stocker les données chiffrées et fournir un token.
Ça ressemble un peu au calcul multipartite sécurisé.
J’ai fini par concevoir un protocole que j’appelle Tokenized Encrypted Payload Sharing (TEPS). Je le documente ci-dessous. Je ne suis pas sûr de mon approche, donc tout retour m’aiderait beaucoup.
Tokenized Encrypted Payload Sharing (TEPS)
C’est une technique pour chiffrer des données qu’un intermédiaire stocke temporairement. On ne fait pas confiance à l’intermédiaire pour les données, mais on lui fait confiance pour les tokeniser. Le client chiffre les données avec une clé symétrique, et le token sert à les récupérer chez l’intermédiaire. Le client peut ensuite les déchiffrer avec la même clé symétrique. La technique sert à partager beaucoup de données sur des supports courts comme les QR codes ou les tags NFC.
Prenons un téléphone qui partage beaucoup de données avec un autre téléphone. L’expéditeur chiffre les données et les envoie à un intermédiaire. L’intermédiaire stocke les données chiffrées et fournit un token. L’expéditeur partage ensuite le token et la clé symétrique avec le destinataire sur un support court, comme un QR code ou un tag NFC. Le destinataire utilise le token pour récupérer les données chiffrées chez l’intermédiaire. Il peut alors les déchiffrer avec la même clé symétrique, ou partager le token et la clé symétrique avec une autre partie pour qu’elle les récupère aussi.
Spécification
1. Introduction
Voici la spécification du protocole TEPS. Le protocole décrit comment partager des données chiffrées via un intermédiaire, et comment partager le token et la clé symétrique de pair à pair.
2. Terminologie
- Intermédiaire : un serveur centralisé de confiance qui stocke les données et fournit un token.
- Clé de chiffrement symétrique : une clé symétrique aléatoire qui sert à chiffrer les données.
- Token : un ID unique qui sert à récupérer les données chez l’intermédiaire.
- Client : l’entité qui chiffre les données et les envoie à l’intermédiaire (par exemple un téléphone).
- Support de partage : le support qui transfère le token et la clé de chiffrement symétrique entre clients (par exemple un QR code, un tag NFC, etc.).
3. Protocole
3.1 Vue d’ensemble
Le client génère une clé symétrique aléatoire et chiffre les données avec. Il envoie ensuite les données chiffrées à l’intermédiaire en échange d’un token. L’intermédiaire stocke les données chiffrées et le token, puis renvoie le token au client. Le client combine alors le token et la clé symétrique, et les partage avec le client destinataire via le support de partage (par exemple un QR code, un tag NFC, etc.). Le destinataire utilise le token pour récupérer les données chiffrées chez l’intermédiaire. Le client destinataire déchiffre ensuite les données avec la clé symétrique.
Parfois, le destinataire peut relayer l’information à une autre partie, mais cette spécification ne couvre pas ce cas.
3.2 Déroulement
3.2.1 Générer la clé symétrique
Le client génère une clé symétrique aléatoire. Elle sert à chiffrer les données. Elle sera ensuite combinée au token de l’intermédiaire pour le partage de pair à pair. La clé symétrique peut avoir n’importe quelle longueur, mais je recommande de la garder à une taille adaptée au support de partage.
// Generate a 32 byte symmetric key
symmetricKey := make([]byte, 32)
if _, err := rand.Read(symmetricKey); err != nil {
panic(err)
}
3.2.2 Chiffrer les données
Le client chiffre les données avec la clé symétrique, en utilisant l’algorithme AES-256 (ou un équivalent).
Par exemple :
// 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 Envoyer les données à l’intermédiaire
Le client envoie les données chiffrées à l’intermédiaire en échange d’un token.
Par exemple :
POST /data HTTP/1.1
Host: api.example.com
Content-Type: application/json
Content-Length: 123
{
"data": "encrypted data"
}
En 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 Stocker les données et générer le token
L’intermédiaire stocke les données chiffrées et génère un token aléatoire. Ce token sert à récupérer les données chez l’intermédiaire. Il peut avoir n’importe quelle longueur, mais je recommande de le garder à une taille adaptée au support de partage.
L’intermédiaire renvoie ensuite le token au client.
Par exemple :
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 123
{
"lookup_id": "random token"
}
En 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 Faire expirer les données (facultatif)
L’intermédiaire peut faire expirer les données au bout d’un certain temps. Il peut utiliser la méthode de son choix.
Stocke la date d’expiration avec les données :
{
"data": "encrypted data",
"expiry_date": "2023-07-19T21:12:43+10:00"
}
Utilise une tâche cron pour supprimer les données expirées.
3.2.6 Combiner le token et la clé symétrique
Le client combine le token et la clé symétrique. Il choisit comment empaqueter le tout, tant que la méthode reste compatible avec le support de partage et le client destinataire.
Pour cette spécification, on combine le token et la clé symétrique dans ce format :
<lookup_id><symmetric_key>
3.2.7 Partager le token et la clé symétrique
Le client partage le token et la clé symétrique avec le client destinataire via le support de partage (par exemple un QR code, un tag NFC, etc.).
3.2.8 Récupérer les données chez l’intermédiaire
Le client destinataire récupère les données chiffrées chez l’intermédiaire grâce au token.
Par exemple :
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 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 Déchiffrer les données
Le client destinataire déchiffre les données avec la clé symétrique.
Par exemple :
// 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)
}
Appel à commentaires
Tout retour sur ce protocole m’aiderait. N’hésite pas à me contacter si tu as des remarques.
Merci de m’avoir lu !