SPS3: Poly1305-Einmal-MAC + Public-Ready README/LICENSE
Authentifizierung für CLI und Web: - Poly1305 (RFC 8439) pur in Python (bigint) und JS (BigInt), keine neue Dependency - MAC-Schluessel pro Nachricht frisch aus dem Pad (len+32), encrypt-then-MAC ueber Header + Ciphertext; reveal lehnt manipulierte/desynchrone Bilder hart ab - Stego-Format SPS2 -> SPS3 (16-B-Tag im Header); alte SPS2-Bilder werden noch gelesen, aber als unauthentifiziert markiert - Service-Worker-Cache auf v3 gebumpt - Interop-Runner (web/_interop_run.sh): RFC-Vektor cross-impl, beide Richtungen, Tamper-Erkennung README fuer Public ueberarbeitet (ehrliches Bedrohungsmodell, Voraussetzungen, Projektstruktur, Tests) + MIT-LICENSE. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
123
README.md
123
README.md
@@ -1,16 +1,39 @@
|
||||
# schattenpost
|
||||
|
||||
Vertrauliche Nachrichten via **One-Time-Pad** + **LSB-Steganografie**.
|
||||
**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.
|
||||
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.
|
||||
|
||||
Es gibt zwei Frontends mit **identischem Dateiformat**:
|
||||
Zwei Frontends, **ein identisches Dateiformat**:
|
||||
|
||||
* `schattenpost.py` — Kommandozeilen-Tool (Python + Pillow).
|
||||
* `web/` — eine client-side Web-App / PWA (läuft im Browser, auch iOS).
|
||||
* **`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](#opsec-empfehlung)). 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
|
||||
|
||||
@@ -25,6 +48,20 @@ Regel 3 ist der klassische Todesstoß (`two-time-pad`). schattenpost erzwingt si
|
||||
ü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
|
||||
@@ -51,6 +88,12 @@ Gerät. Dann existiert jeder `send.key` (mit seinem Offset) **physisch nur einma
|
||||
— 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](https://python-pillow.org/) (`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
|
||||
|
||||
```bash
|
||||
@@ -99,8 +142,10 @@ der die Dateien ausliefert, sieht nichts.
|
||||
* **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.
|
||||
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)
|
||||
|
||||
@@ -112,16 +157,14 @@ Ein Browser darf **nicht** in eine Datei auf dem Stick zurückschreiben. Deshalb
|
||||
`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:
|
||||
### Lokal starten
|
||||
|
||||
```bash
|
||||
python3 -m http.server -d web 8000 # lokal: http://localhost:8000
|
||||
python3 -m http.server -d web 8000 # http://localhost:8000
|
||||
```
|
||||
|
||||
Produktiv hinter einen beliebigen Static-Host / Webserver (HTTPS nötig, damit
|
||||
der Service Worker / die PWA-Installation greift).
|
||||
Produktiv hinter einen beliebigen Static-Host / Webserver. **HTTPS ist nötig**,
|
||||
damit Service Worker und PWA-Installation greifen.
|
||||
|
||||
### ⚠️ iOS-Eigenheit
|
||||
|
||||
@@ -143,24 +186,62 @@ und würde die Nachricht zerstören.
|
||||
| *(padding)* | 2 B | |
|
||||
| material | n B | echtes Zufalls-Pad |
|
||||
|
||||
**Stego-Container im Bild** (`SPS2`, 25 B Header, dann Ciphertext, LSB MSB-first
|
||||
**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 | `SPS2` |
|
||||
| version | 1 B | `2` |
|
||||
| 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
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
* **Steganografie versteckt, authentifiziert aber nicht.** Wer das Format kennt,
|
||||
könnte Bits kippen. Für Manipulationsschutz bräuchte es zusätzlich einen MAC.
|
||||
* **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](LICENSE) — mach damit, was du willst, aber ohne Gewähr.
|
||||
|
||||
Reference in New Issue
Block a user