Clientseitige Verschlüsselung für Zero-Knowledge-Gesundheits-Apps: Der ultimative Leitfaden
Erfahren Sie, wie Sie Gesundheitsdaten Ihrer Nutzer mit React und der Web Crypto API clientseitig verschlüsseln, bevor sie Ihren Server erreichen – für vollständige Privatsphäre.
Auf dieser Seite
In der Welt der Gesundheitstechnologie ist das Vertrauen der Nutzer von größter Bedeutung. Ein einziger Datenverstoß kann nicht nur Ihren Ruf zerstören, sondern auch lebensverändernde persönliche Gesundheitsinformationen (PHI) offenlegen. Während serverseitige Verschlüsselung und HTTPS Standardpraxis sind, haben sie eine grundlegende Schwachstelle: Der Server und jeder, der ihn kompromittiert, kann Benutzerdaten im Klartext einsehen. Hier kommt eine Zero-Knowledge-Architektur ins Spiel.
Ein Zero-Knowledge-System ist ein Sicherheitsmodell, bei dem der Dienstanbieter (das sind Sie!) keine Kenntnis vom Inhalt der gespeicherten Daten hat. Dies wird erreicht, indem alle Verschlüsselungs- und Entschlüsselungsoperationen auf dem Gerät des Clients durchgeführt werden. Der Server empfängt und speichert lediglich verschlüsselte Datenblobs. Selbst bei einem vollständigen Datenbankverstoß bleiben die Gesundheitsdaten der Benutzer unlesbar.
In diesem Deep-Dive werden wir die grundlegende kryptografische Funktionalität für eine Zero-Knowledge-Gesundheits-Tagebuch-App mit React/React Native aufbauen. Wir werden die native und hochsichere Web Crypto API des Browsers verwenden, um Schlüssel aus dem Passwort eines Benutzers abzuleiten, seine Tagebucheinträge zu verschlüsseln und sie zur Ansicht zu entschlüsseln, um sicherzustellen, dass nur der Benutzer selbst auf seine sensiblen Gesundheitsinformationen zugreifen kann.
Voraussetzungen:
- Solides Verständnis von React/React Native und modernem JavaScript (async/await).
- Node.js und ein Paketmanager (npm/yarn) installiert.
- Vertrautheit mit grundlegenden kryptografischen Konzepten wie symmetrischer Verschlüsselung ist hilfreich, aber nicht erforderlich.
Warum dies für Entwickler relevant ist: Der Aufbau einer Zero-Knowledge-App ist ein starkes Differenzierungsmerkmal. Es demonstriert ein Engagement für die Privatsphäre der Nutzer, das über gesetzliche Anforderungen hinausgeht, baut tiefes Nutzervertrauen auf und bietet einen erheblichen Wettbewerbsvorteil.
Das Problem verstehen: Die Grenzen der Standardverschlüsselung
Traditionell sieht das Sicherheitsmodell einer Webanwendung folgendermaßen aus:
- Ein Benutzer gibt Daten in eine React-App ein.
- Die Daten werden über eine HTTPS-Verbindung an den Server gesendet.
- Der Server empfängt die Klartextdaten.
- Der Server verschlüsselt die Daten möglicherweise vor der Speicherung in einer Datenbank (Verschlüsselung im Ruhezustand).
Der kritische Fehler liegt in Schritt 3. Der Server hat Zugriff auf die Klartextdaten. Dies bedeutet, dass ein bösartiger Akteur, der Zugriff auf Ihren Server erlangt, ein disloyaler Mitarbeiter oder eine behördliche Vorladung potenziell Benutzerdaten offenlegen könnten.
Unser Zero-Knowledge-Ansatz verschiebt die Verschlüsselungsgrenze:
- Ein Benutzer gibt Daten in eine React-App ein.
- Die Daten werden direkt im Browser/Gerät mit einem Schlüssel verschlüsselt, den nur der Benutzer besitzt.
- Die verschlüsselten Daten (Chiffretext) werden über HTTPS an den Server gesendet.
- Der Server speichert den Chiffretext, ohne jegliche Kenntnis vom ursprünglichen Inhalt.
Dieses „Host-Proof-Hosting"-Modell stellt sicher, dass Sie als Dienstanbieter kein Risiko für die sensiblen Daten Ihrer Benutzer darstellen können.
Voraussetzungen: Einrichtung unseres Krypto-Toolkits
Wir werden uns auf die Web Crypto API verlassen, eine Low-Level-Schnittstelle für kryptografische Operationen, die direkt in moderne Browser integriert ist. Dies ist ein enormer Sicherheitsvorteil, da wir keine Drittanbieter-Kryptobibliotheken bündeln müssen, was unsere Angriffsfläche reduziert.
Wichtiger Hinweis: Die Web Crypto API ist nur in sicheren Kontexten (HTTPS oder localhost) verfügbar. Dies ist ein Sicherheitsmerkmal, um Man-in-the-Middle-Angriffe zu verhindern.
Lassen Sie uns eine Utility-Datei erstellen, die unsere kryptografischen Funktionen enthält.
# In Ihrem React- oder React-Native-Projekt
touch src/cryptoUtils.js
Wir werden diese Datei Schritt für Schritt aufbauen.
Schritt 1: Ableiten eines Verschlüsselungsschlüssels aus einem Passwort
Eine zentrale Herausforderung ist die Erstellung eines starken Verschlüsselungsschlüssels aus dem Passwort eines Benutzers. Wir können das Passwort nicht direkt als Schlüssel verwenden, da Benutzer oft schwache, vorhersehbare Passwörter wählen. Stattdessen verwenden wir eine Schlüsselableitungsfunktion (KDF). Wir werden PBKDF2 (Password-Based Key Derivation Function 2) verwenden, einen weit verbreiteten und robusten Algorithmus.
Eine KDF nimmt ein Passwort und ein Salt (einen einzigartigen, zufälligen Wert für jeden Benutzer) und führt sie durch einen rechenintensiven Prozess, um einen starken, einheitlichen kryptografischen Schlüssel zu erzeugen.
Was wir tun
Wir erstellen eine Funktion, die das Passwort und das Salt eines Benutzers nimmt, um ein CryptoKey-Objekt zu generieren, das das spezielle Objekt ist, das von der Web Crypto API für die Verschlüsselung verwendet wird.
Implementierung
// src/cryptoUtils.js
const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();
/**
* Leitet einen kryptografischen Schlüssel aus einem Passwort und Salt ab.
* @param {string} password - Das Passwort des Benutzers.
* @param {Uint8Array} salt - Ein zufälliges Salt.
* @param {number} iterations - Die Anzahl der Iterationen für PBKDF2.
* @returns {Promise<CryptoKey>} - Der abgeleitete kryptografische Schlüssel.
*/
export const deriveKeyFromPassword = async (password, salt, iterations = 100000) => {
const passwordBuffer = textEncoder.encode(password);
// Importiere das Passwort als Rohschlüssel für PBKDF2.
const baseKey = await window.crypto.subtle.importKey(
"raw",
passwordBuffer,
{ name: "PBKDF2" },
false,
["deriveKey"]
);
// Leite den Schlüssel mit PBKDF2 und AES-GCM-Parametern ab.
const derivedKey = await window.crypto.subtle.deriveKey(
{
name: "PBKDF2",
salt: salt,
iterations: iterations,
hash: "SHA-256",
},
baseKey,
{ name: "AES-GCM", length: 256 },
true, // Der Schlüssel kann bei Bedarf exportiert werden (z.B. für Backup)
["encrypt", "decrypt"]
);
return derivedKey;
};
/**
* Generiert ein neues zufälliges Salt.
* @returns {Uint8Array} - Ein 16-Byte zufälliges Salt.
*/
export const generateSalt = () => {
return window.crypto.getRandomValues(new Uint8Array(16));
};
Wie es funktioniert
generateSalt: Wir erstellen eine Funktion zur Generierung eines kryptografisch sicheren 16-Byte-Zufallssalts. Dieses Salt muss zusammen mit den Benutzerdaten auf dem Server gespeichert werden. Es ist kein Geheimnis, aber es muss für jeden Benutzer einzigartig sein, um Rainbow-Table-Angriffe zu verhindern.deriveKeyFromPassword:- Wir kodieren den Passwort-String des Benutzers in einen Bytestrom (
Uint8Array). importKeynimmt den Rohschlüssel und bereitet ihn für die Verwendung mit dem PBKDF2-Algorithmus vor.deriveKeyist der Kern des Prozesses. Es nimmt den Basisschlüssel (vom Passwort), das Salt, eine hohe Anzahl von Iterationen (was es für Angreifer langsam und teuer macht) und eine Hash-Funktion (SHA-256), um unser endgültigesCryptoKeyzu erzeugen. Wir geben an, dass dieser Schlüssel für AES-GCM verwendet wird, einen sicheren und modernen Verschlüsselungsalgorithmus.
- Wir kodieren den Passwort-String des Benutzers in einen Bytestrom (
Schritt 2: Verschlüsselung von Gesundheitsdaten
Nun, da wir einen Schlüssel haben, können wir den Tagebucheintrag der Gesundheitsdaten des Benutzers verschlüsseln. Für jeden Verschlüsselungsvorgang müssen wir auch einen einzigartigen Initialisierungsvektor (IV) generieren. Der IV stellt sicher, dass die Verschlüsselung desselben Textes mehrmals mit demselben Schlüssel unterschiedliche Chiffretexte erzeugt, was für die Sicherheit entscheidend ist. Wie das Salt ist der IV kein Geheimnis und sollte auf dem Server gespeichert werden.
Was wir tun
Erstellen Sie eine Funktion, die den Tagebucheintrag des Benutzers (Klartext) und seinen abgeleiteten Schlüssel nimmt und die verschlüsselten Daten (Chiffretext) zusammen mit dem für die Verschlüsselung verwendeten IV zurückgibt.
Implementierung
// src/cryptoUtils.js
/**
* Verschlüsselt Daten mit AES-GCM.
* @param {string} plaintext - Die zu verschlüsselnden Daten.
* @param {CryptoKey} key - Der kryptografische Schlüssel.
* @returns {Promise<{ciphertext: ArrayBuffer, iv: Uint8Array}>}
*/
export const encryptData = async (plaintext, key) => {
const dataBuffer = textEncoder.encode(plaintext);
// Der IV sollte für jeden Verschlüsselungsvorgang eindeutig sein.
const iv = window.crypto.getRandomValues(new Uint8Array(12));
const ciphertext = await window.crypto.subtle.encrypt(
{
name: "AES-GCM",
iv: iv,
},
key,
dataBuffer
);
return { ciphertext, iv };
};
Wie es funktioniert
- Wir generieren einen neuen 12-Byte-Zufalls-IV für diesen spezifischen Verschlüsselungsvorgang. Es ist entscheidend, einen neuen IV für jede Verschlüsselung zu generieren.
- Wir kodieren die Klartext-Gesundheitsdaten in einen Bytestrom.
window.crypto.subtle.encryptführt die Verschlüsselung unter Verwendung des angegebenen Algorithmus (AES-GCM), unseres abgeleiteten Schlüssels, des IV und des Datenpuffers durch.- Das Ergebnis ist ein
ArrayBuffer, das den Chiffretext enthält, den wir zusammen mit dem IV zurückgeben.
Schritt 3: Speichern von Daten auf dem „Zero-Knowledge"-Server
Unsere React/React-Native-Komponente ruft nun diese Utility-Funktionen auf und sendet das Ergebnis an den Server. Die Aufgabe des Servers ist einfach, die verschlüsselten Daten zu speichern, ohne jemals zu versuchen, sie zu interpretieren.
Serverseitiges Datenmodell
Ihr Backend-API und Ihre Datenbank müssen für diesen Ansatz konzipiert sein. Für eine journal_entries-Tabelle könnte Ihr Schema folgendermaßen aussehen:
CREATE TABLE journal_entries (
id UUID PRIMARY KEY,
user_id UUID NOT NULL,
encrypted_entry TEXT NOT NULL, -- Base64-kodierter Chiffretext
iv TEXT NOT NULL, -- Base64-kodierter IV
created_at TIMESTAMPZ NOT NULL,
-- Es werden KEINE Klartextdaten gespeichert!
);
-- Die Benutzertabelle muss das Salt speichern
CREATE TABLE users (
id UUID PRIMARY KEY,
username TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL, -- Für Authentifizierung, NICHT für Verschlüsselung
encryption_salt TEXT NOT NULL -- Base64-kodiertes Salt
);
React-Komponentenimplementierung
Hier ist, wie eine Komponente unsere Krypto-Utilities verwenden könnte, um einen neuen Tagebucheintrag zu speichern. Für Transport und Speicherung ist es am einfachsten, die ArrayBuffer- und Uint8Array-Daten in Base64-Strings zu konvertieren.
// src/components/JournalEntryForm.jsx
import React, { useState } from 'react';
import { deriveKeyFromPassword, encryptData } from '../cryptoUtils';
// Hilfsfunktion zur Konvertierung von ArrayBuffer zu Base64
const bufferToBase64 = (buffer) => {
return btoa(String.fromCharCode.apply(null, new Uint8Array(buffer)));
};
const JournalEntryForm = ({ userPassword, userSalt }) => {
const [entry, setEntry] = useState('');
const [isSaving, setIsSaving] = useState(false);
const handleSave = async () => {
if (!entry || !userPassword || !userSalt) {
alert("Fehlende Daten für die Verschlüsselung.");
return;
}
setIsSaving(true);
try {
// 1. Schlüssel aus dem Passwort und einzigartigen Salt des Benutzers ableiten
const key = await deriveKeyFromPassword(userPassword, userSalt);
// 2. Den Tagebucheintrag verschlüsseln
const { ciphertext, iv } = await encryptData(entry, key);
// 3. Binärdaten für JSON-Transport in Base64 konvertieren
const encryptedEntryBase64 = bufferToBase64(ciphertext);
const ivBase64 = bufferToBase64(iv);
// 4. Die verschlüsselten Daten an den Zero-Knowledge-Server senden
await fetch('/api/journal', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
encrypted_entry: encryptedEntryBase64,
iv: ivBase64,
}),
});
setEntry('');
alert('Ihr Eintrag wurde sicher gespeichert! ✨');
} catch (error) {
console.error("Verschlüsselung fehlgeschlagen:", error);
alert("Ihr Eintrag konnte nicht sicher gespeichert werden.");
} finally {
setIsSaving(false);
}
};
return (
<div>
<textarea
value={entry}
onChange={(e) => setEntry(e.target.value)}
placeholder="Schreiben Sie hier Ihren privaten Gesundheits-Tagebucheintrag..."
/>
<button onClick={handleSave} disabled={isSaving}>
{isSaving ? 'Wird gespeichert...' : 'Sicher speichern'}
</button>
</div>
);
};
export default JournalEntryForm;
Schritt 4: Entschlüsseln und Anzeigen von Daten
Um einen Eintrag anzuzeigen, kehren wir den Prozess um. Wir rufen die verschlüsselten Daten, den IV und das Salt vom Server ab, leiten den Schlüssel mit dem Passwort des Benutzers erneut ab und entschlüsseln die Daten.
Was wir tun
Erstellen Sie eine abschließende Utility-Funktion zur Entschlüsselung und verwenden Sie sie in einer Komponente, die Tagebucheinträge anzeigt.
Implementierung
// src/cryptoUtils.js
/**
* Entschlüsselt Daten mit AES-GCM.
* @param {ArrayBuffer} ciphertext - Die verschlüsselten Daten.
* @param {CryptoKey} key - Der kryptografische Schlüssel.
* @param {Uint8Array} iv - Der für die Verschlüsselung verwendete Initialisierungsvektor.
* @returns {Promise<string>} - Der entschlüsselte Klartext.
*/
export const decryptData = async (ciphertext, key, iv) => {
const decryptedBuffer = await window.crypto.subtle.decrypt(
{
name: "AES-GCM",
iv: iv,
},
key,
ciphertext
);
return textDecoder.decode(decryptedBuffer);
};
React-Komponente zur Entschlüsselung
// src/components/JournalViewer.jsx
import React, { useState, useEffect } from 'react';
import { deriveKeyFromPassword, decryptData } from '../cryptoUtils';
// Hilfsfunktion zur Konvertierung von Base64 zu Uint8Array
const base64ToUint8Array = (base64) => {
const binary_string = window.atob(base64);
const len = binary_string.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes;
};
const JournalViewer = ({ userPassword, userSalt }) => {
const [decryptedEntries, setDecryptedEntries] = useState([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const fetchAndDecryptEntries = async () => {
try {
// 1. Verschluesselte Eintraege vom Server abrufen
const response = await fetch('/api/journal');
const entries = await response.json();
// 2. Den Schluessel ableiten (muss nur einmal gemacht werden)
const salt = base64ToUint8Array(userSalt);
const key = await deriveKeyFromPassword(userPassword, salt);
// 3. Jeden Eintrag entschluesseln
const decryptedPromises = entries.map(async (entry) => {
const ciphertext = base64ToUint8Array(entry.encrypted_entry);
const iv = base64ToUint8Array(entry.iv);
const plaintext = await decryptData(ciphertext, key, iv);
return { id: entry.id, text: plaintext, createdAt: entry.created_at };
});
const results = await Promise.all(decryptedPromises);
setDecryptedEntries(results);
} catch (error) {
console.error("Entschluesselung fehlgeschlagen:", error);
alert("Ihre Eintraege konnten nicht entschluesselt werden. Bitte ueberpruefen Sie Ihr Passwort.");
} finally {
setIsLoading(false);
}
};
if(userPassword && userSalt) {
fetchAndDecryptEntries();
}
}, [userPassword, userSalt]);
if (isLoading) return <p>Ihre Daten werden entschluesselt...</p>;
return (
<div>
{decryptedEntries.map(entry => (
<div key={entry.id}>
<p>{entry.text}</p>
<small>Gespeichert am: {new Date(entry.createdAt).toLocaleString()}</small>
</div>
))}
</div>
);
};
export default JournalViewer;
Best Practices für die Sicherheit & Kompromisse
- Schlüsselverwaltung ist alles: Die Sicherheit des gesamten Systems hängt vom Passwort des Benutzers ab. Wenn das Passwort schwach ist, ist der abgeleitete Schlüssel schwach. Ermutigen Sie Benutzer, starke, einzigartige Passwörter zu verwenden. Die größte Herausforderung in einem Zero-Knowledge-System ist die Kontowiederherstellung. Wenn ein Benutzer sein Passwort vergisst, sind seine Daten unwiederbringlich verloren. Es gibt keinen „Passwort vergessen"-Ablauf, der den Verschlüsselungsschlüssel wiederherstellen kann. Einige Systeme implementieren komplexe soziale Wiederherstellungsmechanismen oder herunterladbare Wiederherstellungsschlüssel, aber dies erhöht die Komplexität erheblich.
- Speichern Sie das Passwort oder den Schlüssel nicht: Speichern Sie das Passwort oder den abgeleiteten
CryptoKeyniemals inlocalStorage, Cookies oder sogar im React-State für längere Zeit. Der Schlüssel sollte idealerweise direkt vor Bedarf aus einer Passworteingabe abgeleitet und nur im Speicher gehalten werden. - Verwenden Sie immer HTTPS: Die Web Crypto API erfordert dies aus einem bestimmten Grund. Ohne HTTPS könnte ein Angreifer Ihren JavaScript-Code abfangen und durch eine bösartige Version ersetzen, die das Passwort des Benutzers stiehlt.
- Leistungsaspekte: Die Schlüsselableitung (PBKDF2) ist absichtlich langsam, um Brute-Force-Angriffe abzuschrecken. Sie werden eine leichte Verzögerung bei der Ableitung des Schlüssels bemerken. Führen Sie diesen Vorgang sparsam durch – einmal pro Sitzung ist ideal. Cachern Sie das
CryptoKey-Objekt im Speicher (z.B. in einem React-Kontext) für die Dauer der Sitzung des Benutzers, aber seien Sie vorsichtig, es nirgendwo zu persistieren.
Alternative Ansätze
- Drittanbieter-Bibliotheken: Bibliotheken wie
crypto-jsexistieren, werden aber im Allgemeinen nicht über die native Web Crypto API empfohlen. Die Web Crypto API ist sicherer, wird von Browserherstellern gepflegt und ist oft schneller, da sie zugrunde liegende Hardwareunterstützung nutzen kann. - Asymmetrische Verschlüsselung (Public/Private Keys): Für Funktionen wie das Teilen von Gesundheitsdaten mit einem Arzt benötigen Sie eine asymmetrische Verschlüsselung (z.B. RSA-OAEP). In diesem Modell hat der Arzt einen öffentlichen Schlüssel (zum Verschlüsseln) und einen privaten Schlüssel (zum Entschlüsseln). Dies ist komplexer zu verwalten, ermöglicht aber sicheres Teilen von Daten.
Fazit
Wir haben erfolgreich die Grundlage eines Zero-Knowledge-Systems aufgebaut. Durch die Nutzung des Geräts des Clients und der robusten Web Crypto API haben wir eine Architektur geschaffen, bei der die Privatsphäre der Benutzer kryptografisch garantiert ist. Der Server fungiert als dummer Speicherschrank, der nicht auf die sensiblen Gesundheitsinformationen zugreifen kann, die er hält.
Dieser Ansatz gibt dem Benutzer die vollständige Kontrolle über seine Daten und baut ein Vertrauensfundament auf, das für jede Anwendung, die mit sensiblen Informationen umgeht, unerlässlich ist.
Nächste Schritte für Sie:
- Integrieren Sie diese Logik in eine State-Management-Lösung wie Redux oder React-Kontext, um das
CryptoKeyzu verwalten. - Erstellen Sie einen sicheren Authentifizierungsfluss, der das Passwort des Benutzers an die Kryptologik weitergibt.
- Erkunden Sie Schlüssel-Backup- und Wiederherstellungsstrategien für Ihre Benutzer.
Ressourcen
Kurzübersicht
| Aspekt | Wichtige Informationen |
|---|---|
| Thema | Entwicklung |
| Relevanz | Patientenaufklärung und -bewusstsein |
| Maßnahme | Besprechen Sie dies mit Ihrem Arzt |
| Häufigkeit | Wie von Ihrem Betreuungsteam empfohlen |
| Evidenz | Basierend auf aktuellen klinischen Leitlinien |
Häufig gestellte Fragen
F: Wie weiß ich, ob dies für mich relevant ist? A: Besprechen Sie Ihre persönliche und familiäre Krankengeschichte mit Ihrem Arzt, um Ihre individuellen Risikofaktoren zu bewerten und geeignete nächste Schritte zu bestimmen.
F: Was sollte ich meinen Arzt fragen? A: Gute Fragen sind: „Was bedeuten meine Ergebnisse?", „Was sind meine Optionen?", „Welche Veränderungen des Lebensstils würden helfen?" und „Wann sollte ich einen Folgetermin vereinbaren?"
F: Gibt es Veränderungen des Lebensstils, die helfen können? A: Die meisten Erkrankungen profitieren von grundlegenden Maßnahmen: ausgewogene Ernährung, regelmäßige körperliche Aktivität (150+ Minuten/Woche), качественнй Schlaf (7-9 Stunden), Stressmanagement und Vermeidung von Tabak.
Haftungsausschluss
Dieser Artikel dient ausschließlich zu Bildungszwecken und stellt keine medizinische Beratung dar. Konsultieren Sie immer einen qualifizierten Arzt, bevor Sie Entscheidungen über Ihre Gesundheit, Behandlung oder Pflege treffen. Individuelle Umstände variieren, und was für eine Person angemessen ist, ist möglicherweise nicht für eine andere geeignet.
Wenn Sie einen medizinischen Notfall erleben, rufen Sie sofort 112 (oder Ihre lokale Notrufnummer) an.