Eine Zero-Knowledge-Architektur: Der Server besitzt Entschlüsselungsschlüssel für nichts als technische Kennungen. Jedes bedeutungstragende Feld — Rufnummern, Adressen, Koordinaten, Webhook-Geheimnisse, WLAN-BSSIDs, Begrüßungen — wird auf dem Gerät des Besitzers verschlüsselt und liegt auf dem Server als undurchsichtiger Blob. Der Entschlüsselungsschlüssel erreicht den Gast im Einladungslink, im Adressfragment, das der Browser nie an den Server sendet.
Algorithm: AES-256-GCM. Auf der JVM ab Werk verfügbar (über javax.crypto),
und vom Android Keystore unterstützt. Es liefert Vertraulichkeit und Authentifizierung zugleich.
v1:<base64url(nonce || ciphertext || tag)>
v1: — ein Versionspräfix, um das Format später zu wechseln.nonce — 12 zufällige Bytes, für jede Verschlüsselung neu.ciphertext+tag — alles, was Cipher.doFinalzurückgibt; das 128-Bit-Tag ist enthalten.SecureRandom() (die des Systems). Auf Android ist das /dev/urandom + Linux-PRNG.
EncryptedSharedPreferences unter dem Namen master_key und verlässt das Gerät nie im Klartext.K_obj zur lokalen Speicherung verwendet.encrypt_k_master(K_obj) unter dem Schlüssel obj_key_{id}.data_cipher auf dem Server).K_obj_new → data_cipher und jedes Bundle, das ihn trug, neu verschlüsseln.{obj_id: K_obj} für die Objekte, die dem Gast erlaubt sind.bundle_cipher. Der K_guest selbst bekommt der Server nie zu sehen.entrixy.com/key#<user_key><base64url(K_guest)> — ein einziger Block aus 75 Zeichen.
Das Fragment geht mit der HTTP-Anfrage nicht an den Server — eine grundlegende Garantie des Browsers.K_guest ist nur durch Ausstellung eines neuen Links möglich. Genau darauf beruht der Widerruf:
Der Server löscht den Schlüsseleintrag, und der Gast erhält keinen aktuellen bundle_cipher mehr.| Key | Wert |
|---|---|
master_key | 32 Bytes im Klartext (SharedPreferences verschlüsselt sie ohnehin). |
obj_key_{id} | Der K_obj des Objekts, umschlossen mit K_master. |
obj_plain_{id} | (optional) ein Cache des entschlüsselten JSON, damit die Oberfläche schnell zeichnet. |
| Tabelle / Feld | Inhalt |
|---|---|
numbers.data_cipher | Mit K_obj verschlüsseltes JSON: {phone, url, secret, geo_lat, geo_lon, share_lat, share_lon, snapshot, welcome, label}. |
numbers.id / type | Im Klartext — nötig für Routing und die Anzeige des Symboltyps. |
keys.bundle_cipher | Mit K_guest verschlüsseltes JSON: {obj_ids: [1,2,3], obj_keys: {1: K_obj1, 2: K_obj2, ...}, welcome_cipher: ..., ...}. |
keys.user_key / mode / force_when_busy | Im Klartext — für Routing und eine schnelle Zugriffsprüfung. |
https://entrixy.com/key#<user_key(32)><K_guest_base64url(43)>
user_key und 43 Zeichen des Gastschlüssels.user_key — die Kennung auf Serverseite. Ohne sie gibt der Server das Bundle nicht heraus.#...) landet nie im Serverprotokoll — das ist eine Garantie des Browsers./key — schlichtes JS: sie liest location.hash, zerlegt den Block und übergibt den Schlüssel
entweder der App oder der Webversion /app/. Der Server sieht den Schlüssel nie./download/android,
von wo die App den Schlüssel selbst aufnimmt.K_obj.K_obj → data_cipher.POST /api/number_add mit data_cipher plus dem Typ. Der Server gibt eine id zurück.obj_key_{id} = encrypt_k_master(K_obj).Das neue JSON lokal bauen, mit dem vorhandenen K_obj → POST /api/number_update.
K_obj verschlüsseln, der sich nicht ändert.
[obj1, obj2, obj3]. Dabei entsteht K_guest.{obj_ids: [...], obj_keys: {1: K_obj1, ...}, welcome_cipher: ...}.K_guest → bundle_cipher.POST /api/key_create mit bundle_cipher, obj_ids (im Klartext — für die Zugriffsmatrix).user_key.entrixy.com/key#{user_key}{base64url(K_guest)}./key reads location.hash und übergibt den Schlüssel der App oder der Webversion.POST /api/key_bundle.php mit user_key → erhält bundle_cipher und die Liste der obj_ids.K_guest→ und erhält {obj_keys, welcome}.K_guest und die entschlüsselten obj_keys werden gespeichert im EncryptedSharedPreferences des Gast-Clients.data_cipher des Objekts → Entschlüsselung mit dem lokalen obj_key_{id}.Der Besitzer drückt „Löschen“ → POST /api/key_revoke.php → der Server löscht den Schlüsseleintrag.
Beim nächsten Bundle-Abruf wird der Gast abgewiesen und verliert den Zugang.
Der Besitzer drückt in den Objekteinstellungen „Schlüssel neu erzeugen“. Dabei entsteht K_obj_new,
und jeder bundle_cipher, der dieses Objekt enthält, wird neu verschlüsselt. Der Client kennt sie nicht
auswendig, deshalb baut der Client des Besitzers die Bundles neu: Er holt den bundle_cipher jedes
eigenen Schlüssels, entschlüsselt ihn, setzt K_obj_newein und verschlüsselt neu.
Die neuen Blobs gehen in einem Schwung an den Server.
Losing K_master bedeutet den Verlust des Zugangs zu allen Objekten und Schlüsseln, deshalb ist ein Export unerlässlich.
K_master → es entsteht master_backup_blob.obj_keys vom Server — sie liegen im Bundle der eigenen Schlüssel des Besitzers — und verschlüsselt sie lokal neu.K_master, deshalb ist die Passphrase Pflicht.Add Crypto.kt: ein AES-256-GCM-Wrapper, Erzeugung und Speicherung von K_master,
Hilfsfunktionen für K_obj, Serialisierung des Formats v1:<base64url>.
Unit-Tests: verschlüsseln → entschlüsseln → vergleichen.
An den Server geht noch nichts — die Verkabelung ist schlicht bereit.
Alle sensiblen Felder sind umgezogen nach numbers.data_cipher und user_keys.bundle_cipher.
Die alten Klartextspalten (phone, label, radius, time_*, geo_*, wifi_*, share_*, has_avatar, security_level, user_keys.label, hosts.label, pending_actions.phone, hosts.last_ip) wurden aus der Datenbank entfernt (Phase 7).
webhook_url und webhook_secret bleiben NUR dann im Klartext, wenn webhook_mode='server' — ohne sie kann der Server die HTTP-Anfrage nicht senden. Bei webhook_mode='phone' werden sie verschlüsselt in data_cipher.
Beim Anlegen oder Ändern verschlüsselt der Client das JSON und sendet data_cipher. Beim Synchronisieren liest er den Blob und entschlüsselt ihn. Neue Objekte leben ganz im Blob; alte ohne K_obj nutzen aus Gründen der Rückwärtskompatibilität weiter die Klartextfelder.
Das Erstellen eines Schlüssels erzeugt K_guest, setzt das Bundle zusammen und sendet bundle_cipheran den Server.
Der Link trägt den Schlüssel im Fragment; die Seite /key liest den Hash und reicht den Schlüssel an die App weiter.
Ein Einstellungsbildschirm mit „Hauptschlüssel exportieren“: Passphrase → Argon2id → QR-Code. Wiederherstellung über den Scanner, dazu Tests des Umzugs zwischen Geräten. Nicht umgesetzt.
Eine einmalige Routine im Client beim ersten Start der neuen Version:
K_obj für jedes davon erzeugen.data_cipher.phone=NULL, …) — aber erst nach Bestätigung.Ein Sicherheitsaudit, extern oder in Eigenregie. Wir prüfen, dass der Server sensible Daten wirklich nicht sieht: Ein Datenbank-Dump darf nichts als Blobs enthalten.
| Risk | Measure |
|---|---|
| Verlust von K_master | Ein verpflichtender Sicherungsablauf (Phase 5). Solange keine Kopie existiert, wird ein Warnhinweis gezeigt. |
| Kompromittierung des Besitzergeräts | Nicht vollständig vermeidbar: Der Zugang zur App ist durch PIN oder Biometrie geschützt, dazu EncryptedSharedPreferences und Android Keystore. |
| Die Rotation eines Objektschlüssels erfordert den Neuaufbau aller Bundles | Der Client des Besitzers nimmt alle bundle_cipher — lokal oder vom Server —, baut sie neu und sendet sie in einem Schwung. Eine seltene Operation. |
| Der Server manipuliert ein Bundle | Das AES-GCM-Tag passt nicht, und der Client meldet einen beschädigten Schlüssel. |
| Abfluss von K_guest über einen Screenshot des QR-Codes | Technisch nicht zu verhindern: Der Schlüssel steckt im Code selbst. Zeigen Sie den QR-Code nur einem Gast, dem Sie vertrauen. |
| Das Adressfragment bleibt im Browserverlauf | Die Seite /key ruft direkt nach dem Lesen des Hashs history.replaceState(..., '#')auf, was das Fragment löscht. |
Zuletzt aktualisiert: 19. April 2026