Technik

Wie das im Einzelnen funktioniert

Für alle, die es genau wissen wollen: Formate, Parameter, Konstruktionen — und die Stellen, an denen wir bewusst nichts versprechen.

1. Der Container

Eine Tresordatei besteht aus einem im Klartext lesbaren, aber MAC-geschützten Kopf und einem verschlüsselten Rumpf. Im Kopf steht nur, was zum Ableiten des Schlüssels gebraucht wird: Salz, KDF-Parameter und die Art der Faktoren. Alles andere — Einträge, Notizen, Fotos und die Zugangsbedingungen — liegt im Rumpf.

Bis Version 3 standen Koordinaten und Öffnungsdatum im Klartextkopf. Für eine App, deren Zweck es ist zu verbergen, wo Wertsachen liegen, war das das Produkt, das seinen eigenen Gegenstand ausplaudert. Seit Version 4 sind sie im Rumpf, und die Folge ist Absicht: Bedingungen lassen sich erst nach dem Entschlüsseln lesen.

Die Datei enthält immer zwei Slots. Ein nie eingerichteter zweiter Slot wird mit Zufallsdaten gleicher Länge gefüllt und ist von einem echten nicht zu unterscheiden — deshalb lässt sich aus der Datei nicht ablesen, ob es einen zweiten Tresor gibt.

GrößeWert
version4
AEADAES-256-GCM
KDFArgon2id (libsodium), opsLimit 2, memLimit 64 MiB, p = 1
KDF (Altbestand)PBKDF2-HMAC-SHA256, 600 000
HKDFHKDF-SHA256, reine Dart-Implementierung
Slots2 (immer beide vorhanden)
Wraps je Slot2 — Faktorpfad, Notfallpfad

2. Vom Passwort zum Schlüssel

Aus den Faktoren entsteht mit Argon2id ein Basisschlüssel. Argon2id ist speicherhart: 64 MiB pro Versuch treffen einen Angreifer mit Spezialhardware weit härter als reine Rechenrunden. Auf einem Gerät von 2024 kostet ein Versuch etwa eine Sekunde; PBKDF2 mit einer vergleichbar vertretbaren Rundenzahl lag bei mehreren Sekunden bis über zwanzig, weil Android dafür keinen nativen Pfad anbietet.

Der Basisschlüssel entschlüsselt nicht direkt. Verschlüsselt wird mit einem zufälligen Rumpfschlüssel, der zweimal eingepackt wird: einmal unter dem Faktorpfad, einmal unter dem Notfallpfad. Wo ein Pfad nicht gebraucht wird, steht ein Blindwert derselben Länge. Das hat zwei Folgen: das teure Argon2id läuft einmal, egal wie viele Ortszellen geprüft werden, und der Kopf verrät nicht, welche Art von Tresor vor einem liegt.

Dateien als Schlüsselteil laufen über Shamir über GF(28) mit dem Rijndael-Polynom 0x11B: entweder alle N in fester Reihenfolge, oder beliebige K davon als Notweg.

3. Der Ort als Schlüsselfaktor

Der Ort ist keine Abfrage, die die App stellt, sondern Teil des Schlüsselplans. Die Position wird auf ein Raster quantisiert, aus der Zelle wird eine Kennung gehasht, und diese Kennung geht als Salz in die Ableitung des Pfadschlüssels ein:

masterKey = HKDF(baseKey, salt = Zellkennung)

Außerhalb der Zelle existiert der Schlüssel also nicht — es gibt nichts, was eine veränderte App oder ein vorgetäuschter Standort umgehen könnte. Der Tresor ist dabei an genau eine Zelle gebunden; der Suchradius sagt nur, wie viele Nachbarzellen der Client durchprobiert, und kostet daher Zeit, nicht Sicherheit.

GrößeWert
Raster50 m, metrisch quantisiert
ZellkennungSHA-256 über tva:geo:v1:…
AbleitungHKDF-SHA256, salt = Zellkennung
Suchradius150 / 300 / 600 / 1200 m
Kandidaten25 / 121 / 529 / 2209 Zellen
Kosten (razr 50 ultra)72 / 162 / 220 / 385 ms
Gespeichertnichts — weder Koordinaten noch Zelle noch Radius

4. Zeitkapseln

Eine Zeitkapsel ist eine Umsetzung von beattime-seal-v1. Der Schlüssel der Notiz wird nach Shamir geteilt; die Beacon-Anteile werden per identitätsbasierter Verschlüsselung (Boneh–Franklin über BLS12-381) auf eine Identität verschlüsselt, die aus dem Öffnungszeitpunkt folgt. Die zugehörige „private“ Schlüsselhälfte ist die Signatur, die der Beacon zu diesem Zeitpunkt veröffentlicht — vorher existiert sie nicht, auch nicht beim Betreiber.

Die Paarungsarithmetik liegt in einer eigenen Rust-Bibliothek auf ark-bls12-381. Die verbreitete tlock-Kiste wird nicht verwendet: sie hat ein 16 Byte langes Sigma fest verdrahtet, weil sie nur age-Dateischlüssel verschlüsselt. Die Referenz zieht Sigma in Länge der Nachricht — bei den 32 Byte, die hier versiegelt werden, ergeben beide unterschiedliche Chiffrate, und keines öffnet das des anderen.

Über die C-Schnittstelle wird nichts alloziert, was jemand freigeben müsste: beide Ergebnisse haben eine im Voraus berechenbare Länge, der Aufrufer stellt den Puffer, und jeder Einsprungpunkt fängt Panics ab.

GrößeWert
Formatbeattime-seal-v1
Hauptgeheimnis32 B, Shamir über GF(28), Polynom 0x11B
NutzlastAES-256-GCM-STREAM, Segment 65 536 B
NoncePräfix 7 B ‖ Zähler 4 B BE ‖ last 1 B
AADSHA-256 des kanonischen Kopfs
SchlüsselHKDF-SHA256, info = beattime-seal-v1|aead
IBEBoneh–Franklin über BLS12-381
Kurvenschemabls-unchained-g1-rfc9380
ChiffratU 96 B (G2) ‖ V 32 B ‖ W 32 B
Beaconsdrand quicknet + BeatTime-Schlüsselserver
Profilstandard — 2 von 3

5. Zeitnachweis für Fotos

Für ein in der App aufgenommenes Foto lässt sich ein Existenznachweis anfordern. Übertragen wird der SHA-256-Prüfwert, nie das Bild. Der Dienst nimmt den Wert in ein Merkle-Blatt auf; nach Wochenschluss wird die Wurzel mit Ed25519 signiert. Die App speichert Prüfwert, Zeit, Inklusionsbeweis und Signatur und rechnet den Beweis offline nach: vom Foto zum Blatt, vom Blatt zur Wurzel, Signatur gegen den öffentlichen Schlüssel.

Das ist ein Nachweis rückwärts — „das gab es schon damals“ — und deshalb ausdrücklich kein Zeitschloss. Ein Zeitschloss verlangt die Gegenrichtung, und dafür stehen die Kapseln.

6. Woran das geprüft ist

Ein Format, das nur mit sich selbst redet, beweist nichts. Drei Prüfungen laufen gegen fremden Code, je eine pro Schicht:

Dazu kommen Tests auf echter Hardware: dass die arm64-Bibliothek im Paket lädt, dass eine Kapsel mit Ort in einer anderen Stadt nicht aufgeht, und was das alles kostet — eine Paarung liegt bei rund 10 ms.

7. Was hier nicht behauptet wird