$ cat scuttle
███████╗ ██████╗██╗ ██╗████████╗████████╗██╗ ███████╗
██╔════╝██╔════╝██║ ██║╚══██╔══╝╚══██╔══╝██║ ██╔════╝
███████╗██║ ██║ ██║ ██║ ██║ ██║ █████╗
╚════██║██║ ██║ ██║ ██║ ██║ ██║ ██╔══╝
███████║╚██████╗╚██████╔╝ ██║ ██║ ███████╗███████╗
╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚══════╝╚══════╝
Kleine, hybride Post-Quanten-Verschlüsselung für sensible Daten.
> Überall versiegeln.
> Woanders öffnen.
> Datenbank gestohlen? Du bekommst Chiffretext.
>
> BITTE BRICH ES.
English · 简体中文 · 日本語 · 한국어 · Deutsch · Français · Español
scuttle ist eine Open-Source-Verschlüsselungsschicht für sensible Anwendungslogs.
Sie ist für Systeme gedacht, in denen Anfrage- und Antwort-Payloads vorübergehend einsehbar sein müssen, Kopien davon aber jahrelang in Datenbanken, Replikaten, Snapshots und Backups überdauern können.
Die normale Anwendungsflotte erhält nur einen öffentlichen Capture-Schlüssel. Damit kann sie neue Logs verschlüsseln, aber keine historischen entschlüsseln.
Langlebiges Schlüsselmaterial wird durch eine hybride Post-Quanten-Konstruktion aus ML-KEM-768 + X25519 geschützt, während Payloads mit AES-256-GCM und unabhängig abgeleiteten Feldschlüsseln verschlüsselt werden.
scuttle ist die hybride Post-Quanten-Verschlüsselungsschicht für sensible Log-Payloads bei OrcaRouter.
Die Open-Source-Bibliothek wird veröffentlicht, damit die Konstruktion öffentlich geprüft, gefuzzt, angegriffen und verbessert werden kann.
Wir wollen, dass Leute das angreifen.
Kryptografie wird stärker, wenn ihre Annahmen explizit sind und Menschen einen Anreiz haben, herauszufinden, wo sie versagen.
ATTACK.md listet die Sicherheitseigenschaften auf, die das System
unserer Überzeugung nach bietet – geordnet danach, wie teuer es für uns wäre,
falschzuliegen –, zusammen mit Fuzz-Zielen, die auf genau diese Annahmen zielen.
Etwas gefunden?
→ Lies SECURITY.md
→ Reproduziere es
→ Brich eine Invariante
→ Sag uns, was wir falsch gemacht haben
v0 — nicht unabhängig auditiert.
Das Wire-Format ist nicht stabil. Verwende dies noch nicht für Daten, deren Verlust oder dauerhafte Unlesbarkeit du dir nicht leisten kannst.
Ein Schiff zu scuttlen (selbst zu versenken) heißt, es absichtlich unbrauchbar zu machen, bevor ein Gegner es kapern kann.
Scuttle wendet dieselbe Idee auf sensible Daten an.
attacker captures storage
│
▼
┌─────────────────┐
│ DATABASE │
│ │
│ █ ciphertext │
│ █ wrapped keys │
│ █ metadata │
└────────┬────────┘
│
│ no capture private key
▼
┌───────────┐
│ ¯\_(ツ)_/¯ │
└───────────┘
got the database.
not the data.
KI-Infrastruktur erzeugt ungewöhnlich sensible Logs.
Eine einzige Anfrage kann Folgendes enthalten:
- Prompts
- Modellantworten
- Quellcode
- API-Ausgaben
- abgerufene Dokumente
- personenbezogene Daten (PII)
- versehentlich in den Kontext geratene Zugangsdaten
- Tool-Aufrufe von Agenten
- proprietäre Unternehmensdaten
Eine normale Logging-Architektur sieht früher oder später so aus:
flowchart LR
A["User Request"] --> B["Gateway"]
B --> C["Application Logs"]
C --> D["Primary DB"]
D --> E["Replica"]
D --> F["Snapshot"]
D --> G["Backup"]
F --> H["Someone's Laptop"]
style C stroke-width:2px
style D stroke-width:2px
Eine Zeile nach 30 Tagen zu löschen, löscht nicht zwangsläufig das Backup von gestern, einen Oplog-Eintrag, ein hinterherhinkendes Replikat oder einen alten Snapshot.
Eine Verschlüsselung allein auf der Speicherebene bedeutet außerdem: Wer den normalen Entschlüsselungspfad der Datenbank erlangt, erlangt womöglich auch den Klartext.
scuttle verlagert die Verschlüsselung vor die Speicherung.
flowchart LR
P["Sensitive Payload"] --> Z["Independent zstd compression"]
Z --> AES["AES-256-GCM"]
LK["Ephemeral Leaf Key"] --> HKDF["HKDF"]
HKDF --> FK["Per-record / per-field key"]
FK --> AES
AES --> CT["Encrypted Payload"]
PUB["Public Capture Key"] --> KEM["Hybrid KEM<br/>ML-KEM-768 + X25519"]
LK --> KEM
KEM --> WK["Wrapped Leaf Key"]
CT --> DB[("Untrusted Storage")]
WK --> DB
PRIV["Private Capture Key"] --> READER["Privileged Reader"]
DB --> READER
READER --> PT["Plaintext"]
Die entscheidende Grenze ist:
┌──────────────────────── ORCAROUTER DATA PLANE ────────────────────────┐
│ │
│ API Gateway Workers Relays Logging Infrastructure │
│ │ │ │ │ │
│ └───────────────┴─────────────┴──────────────────┘ │
│ │ │
│ PUBLIC CAPTURE KEY ONLY │
│ │ │
│ can encrypt │
│ cannot open history │
│ │
└──────────────────────────────┬────────────────────────────────────────┘
│
▼
┌─────────────────────┐
│ UNTRUSTED STORAGE │
│ │
│ encrypted payload │
│ wrapped leaf key │
│ metadata │
└──────────┬──────────┘
│
separate trust boundary
│
▼
┌─────────────────────┐
│ PRIVILEGED READER │
│ │
│ capture private key │
│ access controls │
│ audit boundary │
└─────────────────────┘
Ein Datenbank-Zugangsdatum ist daher nicht dazu gedacht, zugleich ein Zugangsdatum zur Entschlüsselung historischer Prompts zu sein.
scuttle ersetzt nicht jedes Primitiv durch ein Post-Quanten-Primitiv.
Es setzt Post-Quanten-Kryptografie dort ein, wo die langfristige Quantenbedrohung tatsächlich eine Rolle spielt.
SCUTTLE
Payload
│
▼
┌──────────────┐
│ zstd │ compress independently
└──────┬───────┘
│
▼
┌──────────────┐
│ AES-256-GCM │ ◀──── HKDF-derived field key
└──────┬───────┘
│
▼
Ciphertext
│
│ stored together with
▼
┌──────────────────────────────┐
│ Wrapped Leaf Key │
│ │
│ ML-KEM-768 ─┐ │
│ ├─ Hybrid KEM │
│ X25519 ──────┘ │
│ │
│ X-Wing construction │
└──────────────────────────────┘
Die Payload-Verschlüsselung verwendet AES-256-GCM.
Ein künftiger kryptografisch relevanter Quantencomputer würde die Sicherheitsanalyse symmetrischer Kryptografie anders verändern als die von Public-Key-Kryptografie. Grovers Algorithmus liefert eine generische, quadratische Beschleunigung der Suche – nicht die Art von Bruch, die Shors Algorithmus für weit verbreitete klassische Public-Key-Systeme bedeutet.
Ein 256-Bit-Schlüssel bietet daher eine erhebliche Sicherheitsreserve für langlebige verschlüsselte Daten.
Es gibt keinen Grund, ML-KEM über jedes Byte eines Prompts laufen zu lassen.
Verwende symmetrische Kryptografie für die Daten.
Verwende Post-Quanten-Kryptografie, um die Schlüssel zu schützen.
Das langfristige Risiko liegt an der Grenze der Schlüsselkapselung.
Ein Angreifer kann verschlüsselten Verkehr oder Backups heute kopieren, aufbewahren und später zu entschlüsseln versuchen, falls die Kryptografie, die ihre Schlüssel schützt, brechbar wird.
Das ist die Bedrohung „Jetzt sammeln, später entschlüsseln“ (harvest now, decrypt later).
2026 FUTURE
capture encrypted logs
│
▼
██████████████████████████████████████████████►
│ │
│ keep ciphertext │
│ ▼
│ cryptographically
│ relevant quantum
│ computers?
│ │
└──────────────────────────────────────┘
try to decrypt history
scuttle kapselt Leaf-Schlüssel daher mit ML-KEM-768, einem Post-Quanten-Mechanismus zur Schlüsselkapselung.
Weil es eine andere Art von Risiko schafft, ein ausgereiftes Primitiv durch ein neueres zu ersetzen.
scuttle kombiniert:
ML-KEM-768
│
├──────► HYBRID SECRET
│
X25519
über die hybride X-Wing-Konstruktion.
Das Ziel ist gestaffelte Verteidigung (Defense in Depth):
| Komponente | Zweck |
|---|---|
| ML-KEM-768 | Schutz vor künftigen Quantenangriffen |
| X25519 | Ausgereifte klassische Sicherheit auf Basis elliptischer Kurven |
| Hybride Konstruktion | Keine ausschließliche Abhängigkeit von einer der beiden Annahmen |
| AES-256-GCM | Authentifizierte Payload-Verschlüsselung |
| HKDF | Domänengetrennte Ableitung der Feldschlüssel |
| AAD | Bindet den Chiffretext kryptografisch an seinen Kontext |
Deshalb bezeichnet sich scuttle als hybride Post-Quanten-Verschlüsselung, statt einfach X25519 durch ML-KEM zu ersetzen.
Jedes sensible Feld wird unabhängig verschlüsselt.
Für einen konzeptionellen Datensatz:
{
"request_id": "req_01JQ8F7YKX2M",
"model": "example/model",
"status": 200,
"request_body": "<encrypted>",
"response_body": "<encrypted>"
}leitet scuttle für jedes Feld einen eigenen AES-256-Schlüssel ab:
(leaf, record, request_body)
│
└── HKDF ──► AES key A
(leaf, record, response_body)
│
└── HKDF ──► AES key B
Inhaltsschlüssel werden abgeleitet, nicht gespeichert.
Ein einzelner geleakter Feldschlüssel wird daher nicht zu einem universellen Entschlüsselungsschlüssel für die Datenbank.
Die Datenbank gilt als nicht vertrauenswürdig.
Die Associated Data von AES-GCM bindet einen Chiffretext an:
schema version
+
epoch
+
leaf
+
tenant / workspace
+
user
+
record ID
+
field name
Diese Werte werden vor der Authentifizierung eindeutig kodiert.
Ein Angreifer sollte also nicht in der Lage sein, folgenden Chiffretext zu nehmen:
Tenant A
Request 123
response_body
ihn zu verpflanzen nach:
Tenant B
Request 456
request_body
und ihn dort erfolgreich authentifizieren zu lassen.
Die Datenbank speichert den Chiffretext.
Sie darf aber nicht umdefinieren, was dieser Chiffretext bedeutet.
Die Bindung hindert einen Angreifer daran, einen Chiffretext zu verschieben. Für sich allein hindert sie ihn nicht daran, einen neuen zu schreiben.
Der Capture-Schlüssel ist absichtlich öffentlich. Wer in deinen Speicher schreiben kann, kann sich daher ein eigenes Leaf erzeugen, beliebigen Text unter der Bindung eines beliebigen Mandanten versiegeln und ihn einfügen. Ohne Authentifizierungsschlüssel lässt sich diese Zeile sauber öffnen und sieht genau wie ein echtes Log aus – was relevant ist, sobald irgendetwas nachgelagert (ein Support-Tool, ein Analyse-Job, ein LLM, das seine eigenen Logs liest) dem Gelesenen vertraut.
Envelope.AuthKey schließt diese Lücke:
env := scuttle.Envelope{
MaxPlaintext: 256 << 10,
AuthKey: authKey, // 32 secret bytes, writers + readers, never stored with data
}Mit einem AuthKey wird der Feldschlüssel zu
HKDF(salt = AuthKey, ikm = leaf key, info = binding). Ein Fälscher wählt
seinen eigenen Leaf-Schlüssel, kennt aber den AuthKey nicht, kann also weder
den Feldschlüssel ableiten noch ein Tag erzeugen, das der Leser akzeptiert.
Was es bringt und was nicht:
Ohne AuthKey |
Mit AuthKey |
|
|---|---|---|
| Gestohlenen Dump lesen | ❌ nein | ❌ nein |
| Chiffretext zu anderem Mandanten / Datensatz / Feld verschieben | ❌ scheitert | ❌ scheitert |
| Schreibender mit Speicherzugriff fügt neue Zeile ein | ❌ scheitert | |
| Kompromittierter Schreiber fügt Zeile ein | ||
| Schreibender mit Speicherzugriff löscht oder unterschlägt eine Zeile |
Das Einschalten ist eine Umstellung (Cutover). Ein Leser mit AuthKey
weist Zeilen zurück, die ohne ihn versiegelt wurden; andernfalls würde ein
Fälscher einfach unauthentifizierte Zeilen schreiben. Geh in dieser
Reihenfolge vor:
- Gib jedem Schreiber den
AuthKey, damit neue Zeilen authentifiziert werden. - Versiegle jede vorhandene Zeile einmal mit
scuttle.Resealneu, vom unauthentifiziertenEnvelopezum authentifizierten. Dabei bleiben Leaf und Binding der Zeile erhalten; es läuft auf der Leserseite, die die Leaf-Schlüssel besitzt. - Lies ab dann ausschließlich mit dem authentifizierten
Envelope.
Wähle den Envelope niemals pro Zeile anhand eines gespeicherten Werts, etwa
Binding.SchemaVersion. Diesen Wert schreibt der Fälscher ebenfalls, setzt ihn
auf „alt“, und der Leser öffnet die Fälschung mit dem unauthentifizierten
Envelope.
Solange die Migration aus Schritt 2 nicht abgeschlossen ist, sind Fälschungen weiterhin möglich. Die Migration kann eine gefälschte Altzeile nicht von einer echten unterscheiden – sie versiegelt alles neu, was sich öffnen lässt –, daher kommt jede Fälschung, die irgendwann vor ihrem Abschluss eingeschleust wurde, authentifiziert heraus. Deshalb:
- Lass die Leser beim unauthentifizierten Envelope, bis die Migration
abgeschlossen ist; nach Schritt 1 geschriebene Zeilen werden bis dahin als
ErrUndecryptablegelesen. Lass einen Leser niemals einen Envelope versuchen und auf den anderen zurückfallen: Genau das ist der Fälschungsweg. - Führe die Migration einmal aus und stelle dann jeden Leser um. Ein späterer erneuter Lauf öffnet das Fenster wieder.
Die Umstellung verhindert neue Fälschungen; die neu versiegelten Zeilen kann sie
nicht beglaubigen. Das Rotieren des AuthKey folgt demselben Verfahren, mit
Neuversiegeln vom alten auf den neuen Schlüssel.
Der AuthKey ist symmetrisch und 256 Bit lang, bringt also keine zusätzliche
Angriffsfläche für Quantencomputer mit sich.
Das ist eine der wichtigsten Eigenschaften des Designs.
Der normale Schreiber erhält:
PUBLIC CAPTURE KEY
nicht:
DATABASE MASTER DECRYPTION KEY
Also:
flowchart TD
PUB["Public Capture Key"]
PUB -->|"YES"| WRITE["Encrypt new payload"]
PUB -.->|"NO"| HISTORY["Decrypt historical payload"]
PRIV["Private Capture Key"] -->|"YES"| HISTORY
Ein Relay, Gateway oder Worker kann daher Daten verschlüsseln, ohne automatisch die Fähigkeit zu erhalten, die historische Log-Datenbank zu entschlüsseln.
Deshalb trennt die API
LeafSealervon:
KeyringBeide Seiten in denselben Prozess zu legen, zerstört diese Vertrauenstrennung.
Jeder gespeicherte Datensatz trägt seinen eigenen gekapselten Leaf-Schlüssel.
┌────────────────────────────────────────┐
│ Stored Record │
│ │
│ metadata │
│ encrypted request │
│ encrypted response │
│ wrapped leaf key │
│ capture-key generation │
└────────────────────────────────────────┘
Es gibt keine In-Memory-Datenbank zur Schlüsselverteilung, die neben der Anwendung überleben muss.
Leser, die den passenden privaten Capture-Schlüssel besitzen, können den Leaf-Schlüssel wiederherstellen nach:
- Prozessneustarts
- Deployments
- Wechseln von Replikaten
- Failover
- Austausch von Maschinen
Capture-Schlüssel kennen ihre Generation.
Generation 3 ACTIVE
Generation 2 decrypt only
Generation 1 decrypt only
Rotation sieht dann so aus:
// seeds[0] is ACTIVE — what NewLeaf seals under.
// Remaining seeds only open historical records.
keyring, err := scuttle.NewLocalKeyringMulti(
[][]byte{newSeed, oldSeed},
)Neue Datensätze verwenden den neuesten Capture-Schlüssel.
Alte Datensätze bleiben lesbar, solange der zugehörige ausgemusterte Schlüssel im Keyring verbleibt.
Sobald die Aufbewahrungsfrist alles beseitigt hat, was durch eine alte Generation geschützt war, kann diese Generation entfernt werden.
Wiederholte Seeds werden zurückgewiesen, statt stillschweigend eine Rotation vorzutäuschen, die gar nicht stattgefunden hat.
Zeilen, die geschrieben wurden, bevor Datensätze einen Generationsstempel trugen
(KemKeyID leer), werden gegen jede Generation im Keyring geprüft, die aktive
zuerst. Das ist sicher, weil das HPKE-Öffnen authentifiziert ist: Die falsche
Generation scheitert an ihrem Tag, statt einen falschen Schlüssel zu liefern.
plaintext
│
▼
zstd
│
▼
AES-256-GCM
│
▼
ciphertext
Verschlüsselte Daten sind praktisch nicht komprimierbar.
Erst zu komprimieren verhindert, dass die Komprimierung der Speicher-Engine wirkungslos wird.
Jedes Feld wird unabhängig komprimiert, sodass die Daten eines Mandanten nie einen Komprimierungskontext mit denen eines anderen Mandanten teilen.
Innerhalb eines Felds gibt es sehr wohl ein Leck. Die komprimierte Länge
hängt davon ab, wie repetitiv der Klartext ist. Enthält ein Feld ein Geheimnis
neben Text, den ein Angreifer beeinflussen kann – ein System-Prompt oder
Zugangsdaten neben Nutzer- oder abgerufenen Inhalten, ein
Authorization-Header neben vom Client gewählten Headern –, kann ein Angreifer,
der die Längen gespeicherter Chiffretexte lesen kann, Vermutungen über das
Geheimnis testen, eine Anfrage nach der anderen. Das ist der CRIME-Angriff.
Setze für solche Felder Envelope.DisableCompression. Das Feld wird dann
unkomprimiert gespeichert (weiterhin als zstd-Frame, Leser müssen also nichts
ändern), und seine Länge verrät nur die Länge des Klartexts. Der Preis ist
Speicherplatz.
go get github.com/Continuum-AI-Corp/scuttle
go install github.com/Continuum-AI-Corp/scuttle/cmd/scuttle-keygen@latest # prints a fresh key set// ─────────────────────────────────────────────────────────────
// WRITER
//
// Holds a PUBLIC capture key.
// Can seal new data.
// Cannot use that key to open historical data.
// ─────────────────────────────────────────────────────────────
capturePublicKey, err := scuttle.ParsePublicKey(os.Getenv("SCUTTLE_CAPTURE_PUBLIC_KEY"))
if err != nil {
return err // unset or malformed: refuse to start
}
authKey, err := scuttle.ParseAuthKey(os.Getenv("SCUTTLE_AUTH_KEY"))
if err != nil {
return err // never fall back to sealing unauthenticated rows
}
sealer, err := scuttle.NewLeafSealer(capturePublicKey)
if err != nil {
return err
}
leafKey, sealed, err := sealer.NewLeaf("tenant:42|user:7|2026-09-12T10")
if err != nil {
return err
}
// Keep leafKey in memory only for the chosen lifetime.
// Store sealed.LeafID, sealed.KemKeyID and sealed.Blob with the record.
// Writers and readers must use the same Envelope configuration.
env := scuttle.Envelope{
MaxPlaintext: 256 << 10,
AuthKey: authKey,
}
bind := scuttle.Binding{
SchemaVersion: 1,
LeafID: sealed.LeafID,
WorkspaceID: 42,
UserID: 7,
RequestID: "req_01JQ8F7YKX2M", // unique per stored record
}
// ErrPlaintextTooLarge: the body is over MaxPlaintext. Nothing was written.
ct, err := env.Seal(leafKey, bind, scuttle.FieldRequestBody, payload)
if err != nil {
return err
}Das Öffnen geschieht auf der privilegierten Seite:
// ─────────────────────────────────────────────────────────────
// READER
//
// Privileged.
// Keep this trust boundary away from ordinary writers.
// ─────────────────────────────────────────────────────────────
seed, err := scuttle.ParseCaptureSeed(os.Getenv("SCUTTLE_CAPTURE_SEED"))
if err != nil {
return err // an unset seed must stop the reader, not start it
}
keyring, err := scuttle.NewLocalKeyring(seed)
if err != nil {
return err
}
authKey, err := scuttle.ParseAuthKey(os.Getenv("SCUTTLE_AUTH_KEY"))
if err != nil {
return err
}
env := scuttle.Envelope{MaxPlaintext: 256 << 10, AuthKey: authKey}
key, err := keyring.OpenLeaf(ctx, sealed)
switch {
case errors.Is(err, scuttle.ErrKeyUnavailable):
return err // retryable: a remote keyring is unreachable, or ctx ended
case err != nil:
return err // ErrUndecryptable: NOT retryable; investigate
}
defer clear(key) // zero the leaf key when done
plaintext, err := env.Open(key, bind, scuttle.FieldRequestBody, ct)
if err != nil {
return err // ErrUndecryptable: tampered, forged, or bound elsewhere
}Diese beiden Fehler bedeuten bewusst Unterschiedliches.
Die Entschlüsselungsinfrastruktur kann den benötigten Schlüssel derzeit nicht bereitstellen.
Möglicherweise wiederholbar.
Der Datensatz kann in der vorliegenden Form nicht entschlüsselt werden.
Kein normaler Fall für einen erneuten Versuch. Untersuche ihn.
Wer die beiden vermischt, lässt Infrastrukturausfälle wie Datenkorruption aussehen – oder, schlimmer, macht aus echten kryptografischen Fehlern endlose Wiederholungsversuche.
Eine Zeile ganz ohne versiegeltes Leaf ist ErrUndecryptable: Jeder Datensatz
trägt sein eigenes, Warten wird also keines hervorbringen.
Zwei weitere Fehler beschreiben deinen Code, nicht eine Zeile:
ErrPlaintextTooLarge—Sealhat einen Inhalt über der Obergrenze des Envelopes erhalten. Es wurde nichts geschrieben.ErrInvalidConfig— einAuthKeymit falscher Größe oder nur aus Nullen, einMaxPlaintextüberMaxPlaintextCeiling(16 MiB) oder ein fehlerhafter Schlüssel-String. Korrigiere das Deployment.
Eine vollständige, ausführbare Version dieses Abschnitts findest du in
example_test.go.
scuttle soll den Ausgang von Szenarien wie diesen verbessern:
| Ereignis | Beabsichtigtes Ergebnis |
|---|---|
| Datenbank-Zugangsdaten gestohlen | Payload bleibt verschlüsselt |
| Datenbank-Dump gestohlen | Payload bleibt verschlüsselt |
| Backup geleakt | Payload bleibt verschlüsselt |
| Snapshot offengelegt | Payload bleibt verschlüsselt |
| Speicheradministrator liest die DB | Kein Payload-Klartext allein aus dem Speicher |
| Schreiber kompromittiert | Historische Datenbank nicht automatisch mit dem öffentlichen Capture-Schlüssel entschlüsselbar |
| Chiffretext zwischen Mandanten verschoben | Authentifizierung scheitert |
| Vorhandener Chiffretext verändert | Authentifizierung scheitert |
| Schreibender mit Speicherzugriff fügt eine neue, gefälschte Zeile ein | Scheitert nur mit Envelope.AuthKey, gelesen wie in Zeilen authentifizieren beschrieben; ohne ihn öffnet sich die Zeile als echt |
| Angreifer liest die Chiffretextlängen eines Felds, das ein Geheimnis mit seinem eigenen Text mischt | Vermutungen lassen sich testen, sofern nicht Envelope.DisableCompression gesetzt ist |
| Künftiger Quantenangriff auf klassischen Schlüsselaustausch | Die ML-KEM-Schicht bietet PQ-Schutz |
Die letzte Zeile ist der Grund, warum es die Post-Quanten-Schicht gibt.
Sicherheitsaussagen sind nützlicher, wenn ihre Grenzen explizit sind.
Verschlüsselungsschlüssel zu vernichten, damit historischer Chiffretext dauerhaft unlesbar wird – kryptografisches Löschen –, ist ein eigenes Problem.
Informationen, die für Abfragen nötig sind, können sichtbar bleiben, etwa:
- Zeitstempel
- Payload-Größen
- Modellnamen
- Statuscodes
- Kennungen
Ein Angreifer mit Datenbankzugriff kann dennoch wichtige Verkehrsmetadaten erfahren.
Die Anwendung sieht den Klartext zwangsläufig, während sie ihn verarbeitet.
Ein kompromittierter Prozess, ein Speicherabbild, ein Debugger oder eine ausreichend privilegierte Laufzeit-Instrumentierung kann diesen Klartext sehen.
Ist eine Identität legitim berechtigt, Klartext anzufordern, bleibt die Zugriffskontrolle dafür verantwortlich zu entscheiden, ob diese Anfrage zulässig ist.
scuttle schützt kryptografisches Material.
Es ist kein Autorisierungssystem.
Hält der Schreiberprozess auch den privaten Capture-Schlüssel, liefert eine
Kompromittierung dieses Prozesses jeden historischen Datensatz, nicht nur sein
aktuelles Leaf-Fenster. Die Eigenschaft des reinen Schreibens besteht nur, wenn
der Capture-Seed an einem Ort liegt, auf den die Schreiber keinen Zugriff haben:
in einem separaten Lesedienst oder in einem KMS / HSM hinter deiner eigenen
Implementierung des Keyring-Interfaces.
Der Capture-Seed ist ein einziges langlebiges Wurzelgeheimnis. Wer ihn erlangt – heute oder im Jahr 2040 –, kann jeden noch existierenden Datensatz öffnen, der unter seinem öffentlichen Schlüssel versiegelt wurde. Die Post-Quanten-Kapselung schützt vor jemandem, der den Seed nicht hat; gegen jemanden, der ihn hat, hilft sie nicht. Bewahre den Seed in einem KMS oder HSM auf, gib ihn an so wenige Prozesse wie möglich, rotiere ihn und lass die Aufbewahrungsfristen löschen, was alte Generationen geschützt haben.
Ohne Envelope.AuthKey kann jeder, der in den Speicher schreiben kann, eine
Zeile einfügen, die sich als echt entschlüsseln lässt. Siehe
Zeilen authentifizieren.
Nur die Werte in Binding sind an den Chiffretext gebunden. Andere Spalten –
Modellname, Status, Zeitstempel, der Generationsstempel – kann jeder mit
Schreibzugriff umschreiben, auch mit einem AuthKey. Nimm alles, worauf du dich
verlässt, in das Binding auf.
Das Binding ist zudem nur so spezifisch wie seine Werte: Zwei gespeicherte
Datensätze mit demselben Binding können ihre Feld-Blobs unbemerkt vertauschen.
RequestID muss daher pro gespeichertem Datensatz eindeutig sein; schreibt eine
Anfrage mehrere Datensätze (Wiederholungen, Fallbacks), nimm den Versuch mit auf.
Siehe Komprimierung erfolgt vor der Verschlüsselung. Verwende
Envelope.DisableCompression für Felder, die ein Geheimnis mit vom Angreifer
beeinflusstem Text mischen.
Ein Schreiber besitzt den AuthKey, daher kann ein Angreifer, der einen
Schreiber kontrolliert, Zeilen unter beliebigen Bindings schreiben – auch unter
vergangenen –, solange dieser Schlüssel in Gebrauch ist. Rotiere den AuthKey
nach der Kompromittierung eines Schreibers.
Ein Angreifer mit Schreibzugriff kann Zeilen löschen oder zurückhalten. Verschlüsselung kann eine Zeile, die nicht da ist, nicht erkennen.
scuttle ist v0 und wurde nicht unabhängig auditiert. Es gab interne
Reviews, festgehalten in CHANGELOG.md, die jedoch keinen
Ersatz dafür darstellen. Die Primitive stammen alle aus der Go-Standardbibliothek
(crypto/hpke, crypto/hkdf, AES-GCM), aber die Art, wie sie kombiniert
werden, stammt von uns. SPEC.md beschreibt das Format
präzise, und ATTACK.md listet die Aussagen auf, bei denen sich ein
Angriffsversuch lohnt.
SPEC.md— das Wire-Format, Byte für Byte, mit Testvektoren intestdata/vectors.json.ATTACK.md— die Aussagen und die Fuzz-Ziele, die auf sie zielen.SECURITY.md— wie man einen Fund meldet.CHANGELOG.md— was sich geändert hat, einschließlich Formatänderungen.CONTRIBUTING.md— wie man daran mitarbeitet.
Lizenziert unter der Apache License 2.0.