Files
schattenpost/README.md
Claude Code 426d57a2b1 README: Logo-Header (zentriertes Icon + Tagline)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 11:40:44 +00:00

9.9 KiB

schattenpost

schattenpost

Vertrauliche Nachrichten via One-Time-Pad + LSB-Steganografie.
Beweisbar sicher verschlüsselt, authentifiziert, versteckt in einem harmlosen Foto — komplett lokal im Browser.


schattenpost verschlüsselt Text mit einem echten One-Time-Pad (XOR gegen os.urandom- bzw. crypto.getRandomValues-Zufall), authentifiziert ihn mit einem Poly1305-Einmal-MAC und versteckt das Ganze 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.

Zwei Frontends, ein identisches Dateiformat:

  • schattenpost.py — Kommandozeilen-Tool (Python + Pillow).
  • web/ — client-side Web-App / PWA, läuft im Browser (auch iOS), offline-fähig.

⚠️ Ehrliche Einordnung (bitte lesen)

Dies ist ein Hobby- und Lernprojekt, kein auditiertes Sicherheitsprodukt. Die Krypto-Bausteine (OTP, Poly1305 nach RFC 8439) sind solide und getestet, aber:

  • Kein unabhängiges Sicherheits-Audit. Nutzung auf eigenes Risiko.
  • Steganografie verschleiert den Inhalt, nicht die Existenz einer Kommunikation. Wer Metadaten/Traffic beobachtet, sieht dass du Bilder verschickst.
  • Die harte Nuss bleibt der Schlüsselaustausch. Ein One-Time-Pad ist nur so sicher wie die Art, wie ihr das Schlüsselmaterial persönlich übergebt (siehe OPSEC). Wird das Pad abgefangen oder kopiert, ist alles hin.
  • Endgeräte-Sicherheit ist Voraussetzung. Gegen ein kompromittiertes Gerät (Keylogger, Screen-Grab) hilft keine Verschlüsselung.

Kurz: mathematisch sauber, aber die Sicherheit steht und fällt mit deiner Handhabung der Schlüssel und Geräte.


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.

Authentifizierung (ohne die Sicherheit aufzugeben)

OTP verschlüsselt, sagt aber nichts über Echtheit — wer das Format kennt, könnte einzelne Bits kippen. schattenpost hängt deshalb an jede Nachricht einen Poly1305-Einmal-MAC (RFC 8439). Der 32-Byte-MAC-Schlüssel wird pro Nachricht frisch aus demselben One-Time-Pad gezogen — direkt hinter dem Keystream — und nie wiederverwendet. Genau unter dieser Bedingung ist Poly1305 ein informationstheoretisch sicherer Authenticator: die „beweisbar unknackbar"- Zusage bleibt vollständig erhalten, kein rechnerisch-sicheres HMAC/SHA nötig.

Der MAC deckt den Header (stream_id, offset, length) und den Ciphertext ab (encrypt-then-MAC). Jede Veränderung am Bild — oder ein nicht synchroner Schlüssel — lässt das Aufdecken hart abbrechen, statt stillen Müll zu liefern.

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.

Voraussetzungen

  • CLI: Python ≥ 3.8 und Pillow (pip install Pillow).
  • Web: ein moderner Browser. Kein Build-Schritt, keine Abhängigkeiten — die App ist eine einzelne index.html mit eingebettetem Krypto-Kern.

CLI

pip install Pillow

1. Schlüsselpaar erzeugen (einmalig, pro Kontakt)

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

python3 schattenpost.py hide \
    -t "Treffpunkt morgen 18 Uhr." \
    -k kontakt_bob_meine_seite/send.key \
    -c urlaubsfoto.jpg \
    -o Screenshot_20260714_220801.png

Der Sende-Offset im send.key wird dabei automatisch fortgeschrieben. Alternativ -i datei statt -t, oder Text über stdin.

3. Empfangen

python3 schattenpost.py reveal -s Screenshot_20260714_220801.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 bash web/_interop_run.sh: der Runner lädt exakt den Krypto-Kern aus der ausgelieferten index.html, fährt beide Senderichtungen CLI↔Web durch, prüft den Poly1305-MAC cross-implementation gegen den RFC-8439-Vektor und verifiziert die Manipulations-Erkennung.

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.

Lokal starten

python3 -m http.server -d web 8000   # http://localhost:8000

Produktiv hinter einen beliebigen Static-Host / Webserver. HTTPS ist nötig, damit Service Worker und PWA-Installation greifen.

⚠️ 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 (SPS3, 41 B Header, dann Ciphertext, LSB MSB-first in die R/G/B-Kanäle):

Feld Größe Inhalt
magic 4 B SPS3
version 1 B 3
stream_id 8 B welcher Strom (Schlüssel-Check)
offset 8 B Pad-Offset (uint64 BE)
length 4 B Ciphertext-Länge (uint32 BE)
tag 16 B Poly1305-MAC über die 25 B davor + Ciphertext

Pro Nachricht verbraucht der Pad-Strom length + 32 Bytes: length für den Keystream, 32 für den Poly1305-Einmalschlüssel (r|s). Der Sende-Offset wandert entsprechend weiter. Alte SPS2-Bilder (ohne Tag) werden beim Aufdecken noch gelesen, aber als unauthentifiziert markiert.

Projektstruktur

schattenpost.py        CLI (genkey / hide / reveal)
web/
  index.html           Web-App + Krypto-Kern (zwischen ==CORE START/END== markiert)
  sw.js                Service Worker (Offline-Cache der App-Shell)
  manifest.webmanifest PWA-Manifest
  icon-192.png,        App-Icons
  icon-512.png
  _interop_run.sh      Reproduzierbarer CLI↔Web-Interop-Test
  _interop_test.mjs    Web-Core-Seite des Tests (lädt den Kern aus index.html)
  _interop_rawrgb.py   Test-Helfer (PNG ↔ rohes RGB)

Dateien mit _-Präfix sind Entwickler-/Test-Werkzeuge, nicht Teil der App.

Tests

pip install Pillow
bash web/_interop_run.sh

Prüft Poly1305 gegen den RFC-8439-Vektor (Python und JS), beide Senderichtungen CLI↔Web und die Manipulations-Erkennung.

Grenzen / Hinweise

  • Manipulationsschutz ist drin (SPS3 / Poly1305-Einmal-MAC): Bit-Kippen am Bild wird erkannt, das Aufdecken bricht ab. Steganografie versteckt aber weiterhin nur — sie verschleiert nicht, dass überhaupt kommuniziert wird.
  • 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.

Lizenz

MIT — mach damit, was du willst, aber ohne Gewähr.