Eine einzelne index.html ohne Framework/CDN. Krypto + LSB-Steganografie laufen komplett im Browser (iOS-tauglich). Format-identisch zum CLI; Byte-Interop in beide Richtungen per Node-Test gegen das Python-CLI abgesichert (laedt den Krypto-Kern direkt aus der ausgelieferten index.html). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
144 lines
5.3 KiB
Markdown
144 lines
5.3 KiB
Markdown
# schattenpost
|
|
|
|
Vertrauliche Nachrichten via **One-Time-Pad** + **LSB-Steganografie**.
|
|
|
|
Verschlüsselt Text mit einem echten One-Time-Pad (XOR gegen `os.urandom`-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. Gedacht als unauffälliger Kanal, dessen Inhalt selbst bei Mitlesen des
|
|
Transports nicht rekonstruierbar ist.
|
|
|
|
## Warum das (theoretisch) unknackbar ist
|
|
|
|
One-Time-Pad ist der einzige Cipher mit *informationstheoretischer* Sicherheit —
|
|
nicht nur „rechnerisch schwer", sondern beweisbar unknackbar. Das gilt aber nur,
|
|
solange **alle drei** Regeln eingehalten werden:
|
|
|
|
1. Der Schlüssel (das *Pad*) ist echt zufällig.
|
|
2. Das Pad ist mindestens so lang wie die Nachricht.
|
|
3. Jeder Pad-Abschnitt wird **nur ein einziges Mal** benutzt.
|
|
|
|
Regel 3 ist der klassische Todesstoß (`two-time-pad`). schattenpost erzwingt sie
|
|
automatisch über einen Offset-Zähler (siehe unten).
|
|
|
|
## Installation
|
|
|
|
```bash
|
|
pip install Pillow
|
|
```
|
|
|
|
Sonst nur Python-Stdlib.
|
|
|
|
## Benutzung
|
|
|
|
### 1. Pad erzeugen (einmalig)
|
|
|
|
```bash
|
|
python3 schattenpost.py genkey -o pad.key -s 1000000 # 1 MB Pad
|
|
```
|
|
|
|
Das erzeugte `pad.key` **sicher und persönlich** an den Gesprächspartner
|
|
übergeben (USB-Stick, kein digitaler Kanal!). Beide Seiten brauchen die
|
|
byte-identische Datei. Daneben entsteht `pad.key.offset` — der Verbrauchszähler.
|
|
|
|
### 2. Nachricht verstecken
|
|
|
|
```bash
|
|
python3 schattenpost.py hide \
|
|
-t "Treffpunkt morgen 18 Uhr am alten Hafen." \
|
|
-k pad.key \
|
|
-c urlaubsfoto.jpg \
|
|
-o harmlos.png
|
|
```
|
|
|
|
Alternativ `-i datei.txt` statt `-t`, oder Text über stdin.
|
|
|
|
### 3. Nachricht rausholen (Gegenseite)
|
|
|
|
```bash
|
|
python3 schattenpost.py reveal -s harmlos.png -k pad.key
|
|
```
|
|
|
|
Mit `-o klartext.txt` in eine Datei statt auf stdout.
|
|
|
|
## ⚠️ Der eine wichtige Haken
|
|
|
|
**Verschicke das Stego-PNG immer als Datei/Dokument, niemals als „Foto".**
|
|
|
|
Messenger (WhatsApp, Telegram-„Foto", …) jagen Fotos durch verlustbehaftete
|
|
JPEG-Kompression. Das verändert die Pixelwerte und **zerstört die versteckten
|
|
Bits sofort** — `reveal` schlägt dann fehl. Als Dokument/Datei bleibt das PNG
|
|
bit-genau erhalten.
|
|
|
|
## Wie der Pad-Wiederverwendungsschutz funktioniert
|
|
|
|
- `pad.key.offset` merkt sich den nächsten unbenutzten Byte-Offset im Pad.
|
|
- `hide` verschlüsselt ab diesem Offset, schreibt den Offset **in den
|
|
Stego-Header** und zählt den Zähler um die Nachrichtenlänge weiter.
|
|
- `reveal` liest den Offset aus dem Bild und weiß automatisch, welcher
|
|
Pad-Abschnitt gilt.
|
|
|
|
So wird garantiert kein Pad-Byte doppelt verwendet — solange der Zähler nicht
|
|
manuell zurückgedreht wird. Der Sender führt den Zähler; der Empfänger braucht
|
|
ihn nicht (der Offset steckt im Bild).
|
|
|
|
## Container-Format
|
|
|
|
Vor der LSB-Einbettung wird ein kleiner Header vorangestellt:
|
|
|
|
| Feld | Größe | Inhalt |
|
|
|---------|-------|-------------------------------|
|
|
| magic | 4 B | `SPST` |
|
|
| version | 1 B | `1` |
|
|
| offset | 8 B | Pad-Offset (uint64, BE) |
|
|
| length | 4 B | Ciphertext-Länge (uint32, BE) |
|
|
| ciphertext | n B | XOR(plaintext, pad[offset…]) |
|
|
|
|
Die Bits werden linear (MSB zuerst) in die LSBs der R/G/B-Kanäle geschrieben.
|
|
|
|
## Weboberfläche (`web/index.html`)
|
|
|
|
Eine **einzige, self-contained HTML-Datei** — kein Framework, kein CDN, kein
|
|
Build. Verschlüsselung und Steganografie laufen zu 100 % im Browser
|
|
(client-side): Pad, Klartext und Nachricht verlassen das Gerät nie. Ein Server,
|
|
der die Datei ausliefert, ist ein reiner „dummer" Fileserver und sieht nichts.
|
|
|
|
* **Überall erreichbar, iOS inklusive.** Nutzt nur Safari-Standard-APIs
|
|
(Canvas, FileReader, `crypto.getRandomValues`).
|
|
* **Format-kompatibel zum CLI.** Am Terminal verstecken, am Handy im Browser
|
|
aufdecken — und umgekehrt. Byte-Interop ist per Test abgesichert
|
|
(`web/_interop_test.mjs` lädt exakt den Krypto-Kern aus der ausgelieferten
|
|
`index.html` und prüft beide Richtungen gegen das Python-CLI).
|
|
* **Offset-Tracking** merkt sich pro Pad (via `localStorage`) den nächsten
|
|
freien Offset. Achtung: Der Zähler ist pro Browser/Gerät. Wer abwechselnd über
|
|
mehrere Instanzen sendet, muss den Offset manuell synchron halten — sonst
|
|
droht Pad-Wiederverwendung.
|
|
|
|
### Deployment
|
|
|
|
Es ist eine statische Datei — einfach irgendwo hinlegen:
|
|
|
|
```bash
|
|
# lokal testen
|
|
python3 -m http.server -d web 8000 # -> http://localhost:8000
|
|
|
|
# produktiv: web/index.html hinter einen beliebigen Webserver / Static-Host
|
|
```
|
|
|
|
### ⚠️ 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.
|
|
|
|
## Grenzen / Hinweise
|
|
|
|
- **Steganografie versteckt, authentifiziert aber nicht.** Ein Angreifer, der
|
|
das Format kennt, könnte Bits kippen. Für Manipulationsschutz bräuchte es
|
|
zusätzlich einen MAC (z.B. HMAC mit einem separaten Pad-Bereich).
|
|
- Die Sicherheit steht und fällt mit der **geheimen, sicheren Übergabe des
|
|
Pads**. Das ist der eigentliche Aufwand bei OTP — es gibt kein Schlüssel-
|
|
Austauschprotokoll, das dir das abnimmt.
|
|
- Pads gehören **niemals** in ein Repository. Die `.gitignore` blockt `*.key`
|
|
und `*.offset` bewusst.
|