Wie Counted ein geteiltes Kassenbuch verschlüsselt, wenn der Link der Schlüssel ist
Jedes Counted-Projekt hat einen zufälligen 32-Byte-Schlüssel im URL-Fragment, das Browser nie an einen Server senden. Alles, was jemand eintippt, wird auf dem Gerät mit XChaCha20-Poly1305 verschlüsselt, bevor es das Gerät verlässt. Konten sind optional; gibt es eines, verlässt auch das Passwort das Gerät nie. Der Client steht unter AGPL und wird reproduzierbar gebaut, sodass jeder prüfen kann, dass counted.fr ausliefert, was der Quellcode sagt.
Aktualisiert am 2. November 2026
Der Link ist der Schlüssel
Beim Anlegen eines Projekts wird auf dem Gerät ein zufälliger 32-Byte-Schlüssel erzeugt. Der Teilen-Link trägt ihn, base64url-kodiert, nach dem #:
https://counted.fr/projects/<uuid>#<key>
Laut HTTP-Spezifikation bleibt das Fragment beim Client: Es wird nicht mit der Anfrage gesendet und taucht daher nie in Server-Logs oder in einem Referer-Header auf. Beim Öffnen speichert der Client den Schlüssel lokal und entfernt das Fragment aus der Adressleiste. Das ist das ganze Zugriffsmodell für Leute ohne Konto: Wer den Link hat, kann das Projekt lesen und bearbeiten. Behandle ihn wie einen Hausschlüssel.
Was verschlüsselt wird, und wie
Jeder Datensatz wird als JSON serialisiert und als ein Blob mit XChaCha20-Poly1305 verschlüsselt, mit einer neuen zufälligen 24-Byte-Nonce pro Aufruf. Dank des 16-Byte-Poly1305-Tags scheitert die Entschlüsselung bei einem falschen Schlüssel oder einem veränderten Byte sofort.
| Datensatz | Verschlüsselte Felder |
|---|---|
| Projekt | Name, Währung, Beschreibung |
| Teilnehmer | Name |
| Ausgabe | Name, Betrag, Datum, Typ, Beschreibung, Kategorie, ursprüngliche Währung und Kurs |
| Zahlungszeile | Betrag, bezahlt oder geschuldet |
| Wiederholungsregel | Vorlage, Zeitplan, Cursor |
| Konto | Anzeigename, Einstellungen, Zahlungsdaten |
Warum nicht AES-256-GCM: WebAssembly hat kein AES-NI, daher ist ChaCha20 in Software sowohl schneller als auch zeitkonstant, und die 24-Byte-Nonce macht Kollisionen zufälliger Nonces zum Nicht-Problem. Die Kryptografie läuft in Rust (das Crate chacha20poly1305), kompiliert zu WebAssembly fürs Web und nativ für die Apps auf iOS und Android. Salden, Ausgleich und Diagramme werden ebenfalls auf dem Gerät berechnet: Der Server braucht nie einen Betrag.
Optionale Konten und was die Schlüsselhinterlegung kostet
Ein Schlüssel, der auf einem einzigen Gerät liegt, ist mit einem verlorenen Handy weg. Deshalb wird für alle, die ein Konto anlegen, jeder Projektschlüssel auch auf dem Server gespeichert, verpackt unter einem zufälligen Kontoschlüssel, der selbst unter einem Schlüssel verpackt ist, den das Gerät mit Argon2id (64 MiB, t=3) aus dem Passwort und einem Salt pro Konto ableitet. Ein Passwortwechsel verpackt nur diesen Kontoschlüssel neu; ein Konto, das nie sein Passwort geändert oder einen Wiederherstellungsschlüssel angelegt hat, nutzt noch direkt den abgeleiteten Schlüssel. Ändert ein Admin den Schlüssel eines Projekts, geht der neue an jedes Mitglied mit Konto in einer hybriden X25519 + ML-KEM-768-Box; die anderen bekommen einen neuen Link. Der Server speichert diese Bytes und kann sie nicht lesen.
Das ist ein bewusster Kompromiss. Ohne Hinterlegung bekam jemand mit der Datenbank und dem Passwort eines Nutzers nichts von dessen Projekten. Mit Hinterlegung bekommt er jedes Projekt dieses Kontos. Die Alternative wäre gar keine Wiederherstellung gewesen, und das ist für die meisten schlimmer. Anonyme Mitglieder haben keine Hinterlegung: Ihre einzigen Kopien sind der Link und ihr Gerät.
Das Passwort verlässt nie das Gerät
Beim Anmelden wird das Passwort nicht gesendet. Der Client sendet einen Nachweis, ein zweites Argon2id des Passworts unter einem separaten Login-Salt, und der Server speichert nur SHA-256(proof), verglichen in konstanter Zeit. Nichts, was der Server erhält oder speichert, führt zum Kontoschlüssel, und der Server leitet selbst keine Schlüssel ab. Der Preis ist ein zusätzlicher Roundtrip, um das Login-Salt abzurufen.
Es bedeutet auch, dass wir ein Passwort nicht für dich zurücksetzen können. Ein 256-Bit-Wiederherstellungsschlüssel, einmal angezeigt und vom Server nie gesehen (er behält nur einen daraus abgeleiteten Hash), öffnet eine zweite Verpackung des Kontoschlüssels. Ohne ihn greift ein Zurücksetzen per E-Mail nach 72 Stunden, und ein seit 72 Stunden gesetztes Passwort kann es abbrechen; das Konto startet mit einem neuen Schlüssel neu, die Projekte kommen von den Geräten und über die Links zurück, und die vorherige Generation wird 30 Tage aufbewahrt und lässt sich nur mit dem alten Passwort oder dem Wiederherstellungsschlüssel öffnen.
Was der Server noch sieht
Nur „Zero Knowledge“ zu sagen, wäre unehrlich. Der Server sieht:
- Bei Konten: die E-Mail-Adresse, einen Hash des Login-Nachweises, die Salts der Schlüsselableitung, welches Konto in welchem Projekt ist, Freundschaftsverbindungen zwischen Konten, Hashes eingeladener E-Mail-Adressen mit einem Servergeheimnis als Schlüssel (HMAC-SHA256), öffentliche Schlüssel, ob das Konto einen Wiederherstellungsschlüssel hat und seit wann, wann das Passwort geändert und das Konto zurückgesetzt wurde, wer jedes Projekt verwaltet, jede Änderung eines Projektschlüssels, wer sie vorgenommen hat und wer entfernt wurde, und, wenn Benachrichtigungen an sind, das Push-Token und seine Sprache.
- Bei allen: IP-Adresse, User-Agent und Uhrzeit in Logs, die höchstens 30 Tage aufbewahrt werden, Größe und Zeitpunkt verschlüsselter Schreibvorgänge und tägliche Zählungen über ganze Tabellen.
- Die Form jedes Kassenbuchs, nur als Anzahl: wie viele Teilnehmer und Ausgaben, wann jede Ausgabe hinzugefügt, geändert oder gelöscht wurde, und bei einem Konto, das gewählt hat, wer es in einem Projekt ist, welchen Teilnehmer es gewählt hat. Seit Version 3.2.5 sind auch verschlüsselt, wer an einer Ausgabe beteiligt ist, wer sie eingetragen hat und ob das Projekt geschlossen ist. Ein Projekt, das zuletzt von einer älteren Version gespeichert wurde, behält diese Verknüpfungen auf dem Server, bis eines seiner Mitglieder es mit einer aktuellen App öffnet, die sie in die verschlüsselten Daten verschiebt; Backups behalten sie bis zu 30 Tage länger.
- Ein Import aus Tricount: Der Browser kann Tricount nicht direkt aufrufen, also holt der Server das geteilte Register für ihn ab und reicht es im Klartext weiter. Es wird weder gespeichert noch protokolliert; das Gerät verschlüsselt es, bevor es in Counted gespeichert wird.
- Die Länge des Chiffretexts: Eine Stromchiffre behält die Länge des Klartexts bei, daher kann der Server eine Ausgabe mit Beschreibung oder eine in Fremdwährung erfasste von einer ohne unterscheiden. Nie den Inhalt.
Er sieht nie Namen, Beträge, Daten, Beschreibungen oder Projektnamen. Jede Klartextspalte ist in den Designnotizen aufgeführt und begründet, und eine Release-Prüfung schlägt fehl, wenn das Schema eine bekommt, die dort fehlt.
Du kannst den Code prüfen
Der Client, also alles, was je Klartext sieht, ist unter der AGPL auf github.com/counted-labs/counted veröffentlicht. Der Build ist reproduzierbar: Die CI baut ihn von Grund auf neu, vergleicht die SHA-256 jeder Datei und signiert die Liste mit Sigstore. counted.fr/de/verify zeigt die Fingerabdrücke dessen, was ausgeliefert wird, und wie du es selbst nachbaust.
Der Server ist Closed Source, um die dort geplanten kostenpflichtigen Business-Funktionen zu schützen. Er hält immer nur Chiffretext, daher ändert diese Entscheidung nichts an der Vertraulichkeit eines Kassenbuchs.
Bekannte Grenzen
- Ende-zu-Ende-Verschlüsselung im Web vertraut weiterhin der Seite, die du lädst. /verify beweist, dass ein Release seinem Quellcode entspricht, und ein öffentlicher Monitor prüft alle sechs Stunden von GitHub aus, dass counted.fr ein signiertes Release ausliefert und keinen anderen Code ausführt. Keines von beiden kann beweisen, dass die Seite, die du gerade geladen hast, dieses Release ist: Ein Austausch, der einem einzelnen Nutzer ausgeliefert wird, bliebe unbemerkt. Die Apps für iOS und Android bringen ihren Code bei der Installation mit, daher kann eine einzelne Anfrage ihn nicht austauschen.
- Noch kein unabhängiges Audit. Sieben interne Audit-Durchgänge sind dokumentiert, mit ihren Korrekturen.
- Ein geleakter Link legt das Projekt offen, bis ein Admin seinen Schlüssel ändert. Ein entferntes Mitglied behält, was es schon gelesen oder gespeichert hatte, und ein ohne Konto erstelltes Projekt bekommt nur dann einen Admin, wenn sich die Person, die es erstellt hat, auf dem Gerät anmeldet, auf dem es erstellt wurde; ältere haben keinen, und um dort jemanden auszuschließen, legt man ein neues Projekt an.