# schattenpost Vertrauliche Nachrichten via **One-Time-Pad** + **LSB-Steganografie**. Verschlüsselt Text mit einem echten One-Time-Pad (XOR gegen `os.urandom`- bzw. `crypto.getRandomValues`-Zufall) und versteckt den Ciphertext in den niederwertigsten Bits eines Trägerfotos. Das Ergebnis sieht aus wie ein ganz normales Bild, trägt die Nachricht aber bit-genau in sich. Es gibt zwei Frontends mit **identischem Dateiformat**: * `schattenpost.py` — Kommandozeilen-Tool (Python + Pillow). * `web/` — eine client-side Web-App / PWA (läuft im Browser, auch iOS). ## Warum das (theoretisch) unknackbar ist One-Time-Pad ist der einzige Cipher mit *informationstheoretischer* Sicherheit — beweisbar unknackbar, solange **alle drei** Regeln gelten: 1. Der Schlüssel ist echt zufällig. 2. Der Schlüssel ist mindestens so lang wie die Nachricht. 3. Jeder Schlüsselabschnitt wird **nur ein einziges Mal** benutzt. Regel 3 ist der klassische Todesstoß (`two-time-pad`). schattenpost erzwingt sie über **richtungsgetrennte Schlüssel** + einen Offset, der im Schlüssel selbst lebt. ## Das Schlüsselmodell Jede Beziehung nutzt **zwei unabhängige Schlüsselströme** — einen pro Senderichtung. Jede Seite hält zwei Dateien: | Datei | Zweck | Verhalten | |------------|-------|-----------| | `send.key` | Senden | Trägt den **Sende-Offset** im Header; er wandert bei jeder Nachricht vor. | | `recv.key` | Empfangen | Wird nur **gelesen** (Offset kommt aus dem Bild), nie verändert. | Dein `send.key` besteht aus demselben Zufallsmaterial wie der `recv.key` deines Partners — und umgekehrt. Weil du und dein Partner zum Senden **nie denselben Strom** benutzt, könnt ihr gleichzeitig schreiben, ohne je denselben Schlüsselbereich zu treffen. Ein zufälliger **Stream-Identifier** in jedem Schlüssel landet auch im Bild. Dadurch erkennt das Aufdecken sofort, ob der richtige `recv.key` benutzt wird — statt stillen Kauderwelsch zu liefern. ### OPSEC-Empfehlung Lege die Schlüssel auf einen **USB-Stick** und häng ihn nur ans gerade benutzte Gerät. Dann existiert jeder `send.key` (mit seinem Offset) **physisch nur einmal** — zwei Zähler können nicht auseinanderdriften, und auf Handy/Laptop liegt nie ein Schlüssel herum. Mehrere Kontakte = mehrere Schlüsselordner auf dem Stick. ## CLI ```bash pip install Pillow ``` ### 1. Schlüsselpaar erzeugen (einmalig, pro Kontakt) ```bash python3 schattenpost.py genkey -o kontakt_bob -s 1000000 # 1 MB je Richtung ``` Erzeugt zwei Ordner: * `kontakt_bob_meine_seite/` → **du** behältst ihn (enthält `send.key` + `recv.key`). * `kontakt_bob_partner_seite/` → **dem Partner** geben (Stick, persönlich). ### 2. Senden ```bash python3 schattenpost.py hide \ -t "Treffpunkt morgen 18 Uhr." \ -k kontakt_bob_meine_seite/send.key \ -c urlaubsfoto.jpg \ -o harmlos.png ``` Der Sende-Offset im `send.key` wird dabei automatisch fortgeschrieben. Alternativ `-i datei` statt `-t`, oder Text über stdin. ### 3. Empfangen ```bash python3 schattenpost.py reveal -s harmlos.png -k kontakt_bob_meine_seite/recv.key ``` Mit `-o klartext.txt` in eine Datei statt auf stdout. ## Weboberfläche & PWA (`web/`) Eine **client-side** App: Verschlüsselung und Steganografie laufen zu 100 % im Browser. Schlüssel, Klartext und Nachricht verlassen das Gerät nie. Ein Server, der die Dateien ausliefert, sieht nichts. * **Überall erreichbar, iOS inklusive** (nur Safari-Standard-APIs). * **PWA**: über „Zum Home-Bildschirm" installierbar; der Service Worker cacht die App-Shell → danach **offline** nutzbar (kein Server mehr nötig). * **Format-kompatibel zum CLI** — am Terminal senden, am Handy empfangen und umgekehrt. Abgesichert durch `web/_interop_test.mjs`, der exakt den Krypto-Kern aus der ausgelieferten `index.html` lädt und beide Richtungen gegen das CLI prüft. ### Rollenverteilung (wichtig wegen der Browser-Sandbox) Ein Browser darf **nicht** in eine Datei auf dem Stick zurückschreiben. Deshalb: * 📥 **Empfangen**: überall per Web/PWA — der `recv.key` wird nur gelesen. ✓ * 📤 **Senden**: am Laptop am besten per **CLI** (schreibt den Offset direkt auf den Stick). In der Web-App bekommst du nach dem Verstecken den **aktualisierten `send.key` zum Download** und musst ihn auf dem Stick ersetzen — sonst würde der nächste Sendevorgang denselben Schlüsselbereich erneut benutzen. ### Deployment Statische Dateien — einfach hosten: ```bash python3 -m http.server -d web 8000 # lokal: http://localhost:8000 ``` Produktiv hinter einen beliebigen Static-Host / Webserver (HTTPS nötig, damit der Service Worker / die PWA-Installation greift). ### ⚠️ iOS-Eigenheit Stego-Bild über **„In Dateien sichern"** ablegen und als **Datei** teilen — nicht in „Fotos" speichern. iOS re-komprimiert Bilder aus der Fotos-Mediathek und würde die Nachricht zerstören. ## Formate **Schlüsseldatei** (`SPK1`, 24-Byte-Header + Zufallsmaterial): | Feld | Größe | Inhalt | |------|-------|--------| | magic | 4 B | `SPK1` | | version | 1 B | `1` | | role | 1 B | `1` = send, `0` = recv | | stream_id | 8 B | Strom-Kennung (identisch im Paar) | | send_offset | 8 B | nächster freier Offset (uint64 BE) | | *(padding)* | 2 B | | | material | n B | echtes Zufalls-Pad | **Stego-Container im Bild** (`SPS2`, 25 B Header, dann Ciphertext, LSB MSB-first in die R/G/B-Kanäle): | Feld | Größe | Inhalt | |------|-------|--------| | magic | 4 B | `SPS2` | | version | 1 B | `2` | | stream_id | 8 B | welcher Strom (Schlüssel-Check) | | offset | 8 B | Pad-Offset (uint64 BE) | | length | 4 B | Ciphertext-Länge (uint32 BE) | ## Grenzen / Hinweise * **Steganografie versteckt, authentifiziert aber nicht.** Wer das Format kennt, könnte Bits kippen. Für Manipulationsschutz bräuchte es zusätzlich einen MAC. * **Der eine Rest-Fall:** derselbe `send.key` gleichzeitig auf zwei Geräten (ohne Stick-Prinzip) kann trotzdem kollidieren — das Bild-Offset hilft nur dem Empfänger. Regel: **ein `send.key`, ein aktives Sendegerät.** * Verschickte Stego-Bilder **immer als Datei/Dokument**, nie als „Foto". * Schlüssel gehören **niemals** in ein Repository — die `.gitignore` blockt `*.key` bewusst.