Verschlüsselte Daten über Kurzmedien teilen
Auf dieser Seite
Große Datenmengen über Kurzmedien wie QR-Codes oder NFC-Tags zu teilen, ist schwierig. Die Daten müssen verschlüsselt sein, damit sie vertraulich bleiben, aber nützliche Daten auf kleinem Raum unterzubringen ist hart.
Ich suchte nach einem Weg, Daten sicher über ein Kurzmedium an einen Empfänger zu übergeben, zu dem ich noch keine Vertrauensbeziehung habe.
Bei der Arbeit daran haben mich das Signal Protocol und das Noise Protocol Framework inspiriert.
Beide Protokolle kombinieren symmetrische und asymmetrische Verschlüsselung. So erreichen sie ein hohes Sicherheitsniveau und halten die Menge der zu übertragenden Daten klein.
Ich wollte etwas Ähnliches erreichen, aber ohne asymmetrische Verschlüsselung. Ich wollte die Daten mit einem symmetrischen Schlüssel verschlüsseln und diesen Schlüssel Peer-to-Peer über das Kurzmedium teilen. Außerdem wollte ich einen zentralen Vermittler, der die verschlüsselten Daten speichert und ein Token ausgibt.
Das ähnelt in mancher Hinsicht der Secure Multi-Party Computation.
Ich bin bei einem Protokoll gelandet, das ich Tokenized Encrypted Payload Sharing (TEPS) nenne. Unten habe ich es dokumentiert. Ich bin mir bei meinem Ansatz nicht sicher und freue mich deshalb sehr über Feedback.
Tokenized Encrypted Payload Sharing (TEPS)
Das ist eine Technik, um Daten zu verschlüsseln, die ein Vermittler vorübergehend speichert. Dem Vermittler vertraust du die Daten nicht an, aber du vertraust ihm, dass er sie tokenisiert. Der Client verschlüsselt die Daten mit einem symmetrischen Schlüssel, und mit dem Token holt man die Daten beim Vermittler ab. Danach kann der Client sie mit demselben symmetrischen Schlüssel entschlüsseln. Die Technik eignet sich, um große Datenmengen über Kurzmedien wie QR-Codes oder NFC-Tags zu teilen.
Ein Beispiel: Ein Smartphone teilt eine große Datenmenge mit einem anderen Smartphone. Der Sender verschlüsselt die Daten und schickt sie an einen Vermittler. Der Vermittler speichert die verschlüsselten Daten und gibt ein Token aus. Der Sender teilt dann Token und symmetrischen Schlüssel über ein Kurzmedium wie einen QR-Code oder NFC-Tag mit dem Empfänger. Der Empfänger holt mit dem Token die verschlüsselten Daten beim Vermittler ab. Dann kann er sie mit demselben symmetrischen Schlüssel entschlüsseln, oder er gibt Token und Schlüssel an eine weitere Partei weiter, damit auch sie die Daten abrufen kann.
Spezifikation
1. Einleitung
Das Folgende ist eine Spezifikation für das TEPS-Protokoll. Das Protokoll beschreibt, wie du verschlüsselte Daten über einen Vermittler teilst und Token und symmetrischen Schlüssel Peer-to-Peer weitergibst.
2. Begriffe
- Vermittler (Intermediary): Ein vertrauenswürdiger zentraler Server, der die Daten speichert und ein Token ausgibt.
- Symmetrischer Verschlüsselungsschlüssel (Symmetric Encryption Key): Ein zufälliger symmetrischer Schlüssel, mit dem die Daten verschlüsselt werden.
- Token: Eine eindeutige ID, mit der du die Daten beim Vermittler abrufst.
- Client: Die Instanz, die die Daten verschlüsselt und an den Vermittler schickt. (z. B. ein Smartphone)
- Share Medium: Das Medium, über das Token und symmetrischer Schlüssel zwischen Clients wandern. (z. B. QR-Code, NFC-Tag usw.)
3. Protokoll
3.1 Überblick
Der Client erzeugt einen zufälligen symmetrischen Schlüssel und verschlüsselt damit die Daten. Dann schickt er die verschlüsselten Daten an den Vermittler und bekommt dafür ein Token. Der Vermittler speichert die verschlüsselten Daten und das Token. Danach gibt er das Token an den Client zurück. Der Client kombiniert Token und symmetrischen Schlüssel und teilt sie über das Share Medium (z. B. QR-Code, NFC-Tag usw.) mit dem Empfänger-Client. Der Empfänger holt mit dem Token die verschlüsselten Daten beim Vermittler ab. Der Empfänger-Client entschlüsselt die Daten dann mit dem symmetrischen Schlüssel.
Manchmal kann der Empfänger die Information an eine weitere Partei weitergeben. Das deckt diese Spezifikation nicht ab.
3.2 Ablauf
3.2.1 Symmetrischen Schlüssel erzeugen
Der Client erzeugt einen zufälligen symmetrischen Schlüssel. Mit ihm werden die Daten verschlüsselt. Später kombinierst du ihn mit dem Token des Vermittlers, um beides Peer-to-Peer zu teilen. Der symmetrische Schlüssel darf beliebig lang sein. Empfehlenswert ist aber eine Größe, die zum Share Medium passt.
// Generate a 32 byte symmetric key
symmetricKey := make([]byte, 32)
if _, err := rand.Read(symmetricKey); err != nil {
panic(err)
}
3.2.2 Daten verschlüsseln
Der Client verschlüsselt die Daten mit dem symmetrischen Schlüssel und dem Algorithmus AES-256 (oder einem ähnlichen).
Zum Beispiel:
// 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 Daten an den Vermittler schicken
Der Client schickt die verschlüsselten Daten an den Vermittler und bekommt dafür ein Token.
Zum Beispiel:
POST /data HTTP/1.1
Host: api.example.com
Content-Type: application/json
Content-Length: 123
{
"data": "encrypted data"
}
Im 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 Daten speichern und Token erzeugen
Der Vermittler speichert die verschlüsselten Daten und erzeugt ein zufälliges Token. Mit dem Token werden die Daten beim Vermittler abgerufen. Das Token darf beliebig lang sein. Empfehlenswert ist aber eine Größe, die zum Share Medium passt.
Danach gibt der Vermittler das Token an den Client zurück.
Zum Beispiel:
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 123
{
"lookup_id": "random token"
}
Im 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 Daten ablaufen lassen (optional)
Der Vermittler kann die Daten nach einer gewissen Zeit ablaufen lassen. Welche Methode er dafür nutzt, bleibt ihm überlassen.
Speichere das Ablaufdatum zusammen mit den Daten:
{
"data": "encrypted data",
"expiry_date": "2023-07-19T21:12:43+10:00"
}
Lösche abgelaufene Daten mit einem Cron-Job.
3.2.6 Token und symmetrischen Schlüssel kombinieren
Der Client kombiniert Token und symmetrischen Schlüssel. Wie er das Paket schnürt, entscheidet er selbst, solange die Methode zum Share Medium und zum Empfänger-Client passt.
Für diese Spezifikation kombinierst du Token und symmetrischen Schlüssel im folgenden Format:
<lookup_id><symmetric_key>
3.2.7 Token und symmetrischen Schlüssel teilen
Der Client teilt Token und symmetrischen Schlüssel über das Share Medium (z. B. QR-Code, NFC-Tag usw.) mit dem Empfänger-Client.
3.2.8 Daten beim Vermittler abrufen
Der Empfänger-Client ruft die verschlüsselten Daten mit dem Token beim Vermittler ab.
Zum Beispiel:
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"
}
Im 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 Daten entschlüsseln
Der Empfänger-Client entschlüsselt die Daten mit dem symmetrischen Schlüssel.
Zum Beispiel:
// 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
Ich freue mich über jedes Feedback zu diesem Protokoll. Schreib mir einfach, wenn du etwas dazu hast.
Danke fürs Lesen!