Technik
Für alle, die es genau wissen wollen: Formate, Parameter, Konstruktionen — und die Stellen, an denen wir bewusst nichts versprechen.
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öße | Wert |
|---|---|
version | 4 |
| AEAD | AES-256-GCM |
| KDF | Argon2id (libsodium), opsLimit 2, memLimit 64 MiB, p = 1 |
| KDF (Altbestand) | PBKDF2-HMAC-SHA256, 600 000 |
| HKDF | HKDF-SHA256, reine Dart-Implementierung |
| Slots | 2 (immer beide vorhanden) |
| Wraps je Slot | 2 — Faktorpfad, Notfallpfad |
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.
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öße | Wert |
|---|---|
| Raster | 50 m, metrisch quantisiert |
| Zellkennung | SHA-256 über tva:geo:v1:… |
| Ableitung | HKDF-SHA256, salt = Zellkennung |
| Suchradius | 150 / 300 / 600 / 1200 m |
| Kandidaten | 25 / 121 / 529 / 2209 Zellen |
| Kosten (razr 50 ultra) | 72 / 162 / 220 / 385 ms |
| Gespeichert | nichts — weder Koordinaten noch Zelle noch Radius |
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öße | Wert |
|---|---|
| Format | beattime-seal-v1 |
| Hauptgeheimnis | 32 B, Shamir über GF(28), Polynom 0x11B |
| Nutzlast | AES-256-GCM-STREAM, Segment 65 536 B |
| Nonce | Präfix 7 B ‖ Zähler 4 B BE ‖ last 1 B |
| AAD | SHA-256 des kanonischen Kopfs |
| Schlüssel | HKDF-SHA256, info = beattime-seal-v1|aead |
| IBE | Boneh–Franklin über BLS12-381 |
| Kurvenschema | bls-unchained-g1-rfc9380 |
| Chiffrat | U 96 B (G2) ‖ V 32 B ‖ W 32 B |
| Beacons | drand quicknet + BeatTime-Schlüsselserver |
| Profil | standard — 2 von 3 |
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.
Ein Format, das nur mit sich selbst redet, beweist nichts. Drei Prüfungen laufen gegen fremden Code, je eine pro Schicht:
tlock-js öffnen,
und umgekehrt; zusätzlich gegen eine Signatur, die drand wirklich
veröffentlicht hat.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.