
Sicherer Terminal-Chat. E2E verschlüsselt, Server mit Null-Metadaten und Blind-Weiterleitung. PyNaCl XSalsa20-Poly1305 + Ed25519 + Vorwärtssicherheit. Plattformübergreifendes Python.
Ende-zu-Ende-verschlüsselter Gruppenchat, private Nachrichten und Dateiübertragung in deinem Terminal. Der Server ist ein blinder Weiterleiter: Er kann deine Nachrichten nicht lesen, kennt deinen Benutzernamen nicht, kennt den Raum nicht, in dem du dich befindest, und kann keine zwei Nachrichten derselben Person zuordnen, selbst wenn er vollständig kompromittiert ist.
https://github.com/user-attachments/assets/d9faabfb-73bd-46dd-92b2-23f63daf5b06
https://github.com/user-attachments/assets/e8e0220d-cd7d-45a2-9443-9a5f20b57f12
https://github.com/user-attachments/assets/15fb383d-a02a-433e-bbd9-8ebadecf9481
https://github.com/user-attachments/assets/bca10cb1-6959-425d-96d6-fc1fbf845538
NoEyes ist ein Python-Terminal-Chat-Tool für kleine vertrauenswürdige Gruppen. Der Server entschlüsselt niemals etwas und sieht nie, wer du bist – er verarbeitet nur undurchsichtige Token und leitet verschlüsselte Bytes weiter.
Du generierst den Schlüssel, teilst ihn out-of-band, und der Server erfährt nichts über deine Unterhaltungen.
Nützlich für kleine vertrauenswürdige Gruppen, die verschlüsselte Kommunikation ohne Vertrauen in einen Drittanbieter-Server wünschen, für das Selbsthosten eines privaten Chats mit echter Ende-zu-Ende-Verschlüsselung oder für jeden, der genau verstehen möchte, was ein Server sehen kann und was nicht.
python ui/setup.py
python ui/launch.py
`ui/launch.py` führt dich durch das Starten eines Servers oder das Verbinden mit einem.
---
### Option B - Falls Python noch nicht installiert ist
| Plattform | Führe dies zuerst aus |
|---|---|
| Linux / macOS / Termux / iSH | `sh install/install.sh` |
| Windows | `install\install.bat` |
Beide Skripte installieren Python, falls es fehlt, und übergeben dann automatisch an `setup.py`.
---
### Option C - Manuell```bash
# 1. Install dependencies
pip install cryptography PyNaCl
# 2. On the server machine — generate the access key
python noeyes.py --generate-access-key
# Prints an access code hex string — share with clients via USB
# 3. On a client machine — generate chat.key from the access code
python noeyes.py --generate-chat-key <ACCESS_CODE_HEX> --key-file ./chat.key
# Distribute chat.key to all other clients via USB. Never put it on the server.
# 4. Start the server (does NOT need the key file)
python noeyes.py --server --port 5000
# Start without bore tunnel (LAN / static IP / custom tunnel)
python noeyes.py --server --port 5000 --no-bore
# Start without adding a firewall rule (not needed when using bore tunnel)
python noeyes.py --server --port 5000 --no-firewall
# 5. Connect clients - each person needs their own identity file
python noeyes.py --connect SERVER_IP --port 5000 --username alice --key-file ./chat.key --identity-path ~/.noeyes/identity_alice.key
python noeyes.py --connect SERVER_IP --port 5000 --username bob --key-file ./chat.key --identity-path ~/.noeyes/identity_bob.key
Wichtig: Jeder Benutzer muss seine eigene Identitätsdatei besitzen. Wenn zwei Clients dieselbe Identitätsdatei teilen, erhalten sie denselben Posteingangs-Token und der Server lehnt den zweiten als doppelte Sitzung ab. Die Identitätsdatei wird beim ersten Start automatisch generiert; geben Sie einfach einen eindeutigen
--identity-pathpro Benutzer an.
Laden Sie Termux von F-Droid (empfohlen) herunter: https://f-droid.org/packages/com.termux/
Halten Sie die Sitzung aktiv - installieren Sie tmux, damit NoEyes weiterläuft, wenn Sie Apps wechseln:```bash pkg install tmux -y tmux python ui/launch.py
**Speicherberechtigungen** - ohne diese schlägt die Dateiübertragung fehl:```bash
termux-setup-storage
▶ markiert.Jede Hälfte scrollt unabhängig. Drücken Sie ^P, um die Leiste für eine Vollbild-Chatansicht auszublenden.
Stellen Sie jeder Nachricht ein !tag voran, um sie für alle farbig zu markieren und einen Benachrichtigungston auszulösen. Tags reisen innerhalb der verschlüsselten Nutzlast, der Server sieht sie nie.
Beispiele:``` !danger server is going down in 5 minutes !ok deployment successful !req can someone review my PR?
Töne werden aus dem Ordner `sfx/` abgespielt. Lege `.wav`, `.mp3`, `.ogg`, `.aiff`, `.flac` oder `.m4a`-Dateien, die nach dem Tag benannt sind, dort ab (z. B. `sfx/danger.wav`). Falls nicht gefunden, wird auf den Terminalton zurückgegriffen. Verwende `/notify off`, um alle Töne zu deaktivieren.
---
## Architektur
> 🗺️ **[Live Interactive Security Map](https://ymsniper.github.io/NoEyes/)** — Visuelle Aufschlüsselung der vollständigen Verschlüsselungsarchitektur, des Bedrohungsmodells und des Routings ohne Metadaten in einem interaktiven Diagramm.```
┌──────────────────────────────────────────────────────────────────────┐
│ Alice ──────────────────────────────────────────── Bob │
│ │ Encrypted payload (opaque) │ │
│ │ │ │ │
│ └────────────► SERVER ─┴◄──────────────────────────┘ │
│ │ │
│ Zero-metadata blind forwarder: │
│ routes by opaque inbox tokens only │
│ { "to": "3f9a1c...", "type": "privmsg" } │
│ forwards encrypted bytes verbatim │
└──────────────────────────────────────────────────────────────────────┘
WHAT THE SERVER SEES: WHAT THE SERVER NEVER SEES:
· Encrypted bytes it can't read · Usernames or display names
· Opaque inbox tokens (blake2s) · Room names
· Opaque room tokens (blake2s) · Who is messaging whom
· Frame byte length · Message content
· Connection timing · File contents
· Ed25519 public keys
· DH key exchange values
Jeder Client berechnet lokal zwei undurchsichtige Token, bevor er eine Verbindung herstellt:``` inbox_token = blake2s(identity_vk_bytes, digest_size=16) room_token = blake2s((room_name + group_key_hex).encode(), digest_size=16)
Der Server leitet alle Frames nur anhand dieser Token weiter. Er speichert niemals Anzeigenamen, Raumnamen oder öffentliche Schlüssel. Die Identität des Absenders reist **innerhalb** der verschlüsselten Nutzlast (versiegelter Absender), nicht im Routing-Header.
### Schlüsselableitungskette```
chat.key (shared secret)
│
├─ BLAKE2b("general") ──► room_key["general"] (isolated per room)
├─ BLAKE2b("dev") ──► room_key["dev"]
└─ BLAKE2b("ops") ──► room_key["ops"]
X25519 DH (per user pair, automatic on first /msg)
alice_ephemeral + bob_ephemeral ──► shared_secret
│
BLAKE2b
│
pairwise_key (private messages)
│
BLAKE2b(transfer_id) ──► chacha20_key (files)
password + random_salt (32 bytes, os.urandom) │ └─ BLAKE2b(password, key=salt, person="identity_v2") │ derived_key ──► encrypts Ed25519 signing key at rest
Every identity file gets a unique random salt, rainbow tables are useless.
---
## Sicherheitszusammenfassung
| Schicht | Mechanismus | Anmerkungen |
|---|---|---|
| Vorwärtssicherheit (Ratchet) | Sender Keys — BLAKE2b-Ketten-KDF + XSalsa20-Poly1305 pro Nachricht | Pro Nachricht eindeutiger Schlüssel, schneller Vorlauf für verpasste Nachrichten |
| Gruppenchat | XSalsa20-Poly1305 (PyNaCl secretbox) | Raumschlüssel via BLAKE2b |
| Private Nachrichten | XSalsa20-Poly1305 mit X25519-paarweisem Schlüssel | Ed25519 signiert, TOFU verifiziert |
| Dateiübertragung | ChaCha20-Poly1305 | Schlüssel pro Übertragung via BLAKE2b, Ed25519 signiert, Pause/Fortsetzung bei Wiederverbindungen |
| Absenderidentität | Versiegelter Absender | Benutzername + Signatur innerhalb des verschlüsselten Payloads, nie im Routing-Header |
| Identität | Ed25519-Schlüsselpaar | Pro Benutzer Identitätsdatei, passwortverschlüsselt mit BLAKE2b + zufälligem Salt |
| Schlüsselableitung | BLAKE2b (PyNaCl) | Bereichsgetrennt via Personalisierungsparameter, keine Rainbow-Tabellen |
| Server-Routing | Undurchsichtige blake2s-Token | Server speichert niemals Benutzernamen, Raumnamen oder öffentliche Schlüssel |
| Transport | TLS (standardmäßig aktiviert) | TOFU-Zertifikats-Pinning, Fingerabdruck-Konflikt bricht Verbindung ab |
| DH-Integrität | Ed25519-signierte DH-öffentliche Schlüssel | Verhindert MITM beim paarweisen Schlüsselaustausch |
| Replay-Schutz | Pro Raum Nachrichten-ID-Deque | Wiederholte Frames werden stillschweigend verworfen |
| DoS-Schutz | Verbindungslimit + Beitritts-Timeout + Ratenbegrenzung | Max. 200 Verbindungen, 10 s Beitritts-Timeout |
| Raumisolierung | `BLAKE2b(master_key, room_name)` | Kryptografisch isoliert pro Raum |
### Bedrohungsmodell
NoEyes ist für **kleine vertrauenswürdige Gruppen** ausgelegt. Es bietet starken Schutz gegen:
- Passive Netzwerkbeobachter – der gesamte Verkehr ist TLS + Ende-zu-Ende verschlüsselt
- Kompromittierten bore.pub-Relay – der Relay sieht nur verschlüsselte Bytes und Verbindungszeitpunkte
- Kompromittierten Server – der Server ist wissenslos, nichts Nützliches im RAM
- MITM bei der Verbindung – TLS-Zertifikats-Pinning + Ed25519-signierte DH-Schlüssel
- Jemanden, der Ihr Gerät stiehlt – der Identitätsschlüssel ist im Ruhezustand passwortverschlüsselt
- Replay-Angriffe – MID-basierter Replay-Schutz pro Raum
---
## Einen Online-Server betreiben (bore pub)
Wenn Sie einen NoEyes-Server zu Hause starten, erhält Ihr Rechner eine lokale IP-Adresse. Damit jemand außerhalb Ihres Netzwerks eine Verbindung herstellen kann, müssten Sie normalerweise einen Port an Ihrem Router freigeben, was aufgrund von CGNAT oder carrier-seitiger Blockierung oft fehlschlägt.
**bore pub** löst dies mit einem sicheren Tunnel von Ihrem Rechner zu einem öffentlichen Relay und gibt Ihrem Server sofort eine öffentliche Adresse, ohne den Router zu berühren.
**bore** ist ein Open-Source-TCP-Tunnel-Tool von [Eric Zhang (@ekzhang)](https://github.com/ekzhang/bore). Wenn Sie den NoEyes-Server starten, wird er automatisch gestartet:```
bore local 5000 --to bore.pub
Das Relais weist einen zufälligen Port zu und gibt eine Adresse wie bore.pub:12345 aus. Teile diese mit deiner Gruppe:```bash
python noeyes.py --connect bore.pub --port 12345 --key-file ./chat.key --username alice --identity-path ~/.noeyes/identity_alice.key
Alles ist immer noch Ende-zu-Ende verschlüsselt, bore leitet nur Rohbytes weiter.
### Automatisches Wiederverbinden bei Änderungen des bore-Ports
bore.pub weist bei jedem Serverneustart einen **zufälligen Port** zu. Normalerweise müsste man die Adresse jedes Mal mit allen teilen. NoEyes handhabt dies automatisch mit drei Wiederherstellungsebenen:
**1. Migrate-Ereignis (sofort)**
Wenn bore einen Port neu zuweist, sendet der Server ein signiertes `migrate`-Ereignis an alle verbundenen Clients mit der neuen Portnummer. Clients trennen sich stillschweigend, aktualisieren ihren Port und verbinden sich automatisch neu. Ein 15-sekündiges Ruhefenster unterdrückt Join/Leave-Geräusche, sodass der Chat-Bildschirm nicht flackert.
**2. Discovery-Dienst (Clients, die das Migrate verpasst haben)**
Wenn ein Client zum Zeitpunkt der Portänderung offline war, fragt er bei jedem Wiederverbindungsversuch einen kostenlosen anonymen Key-Value-Dienst (`keyvalue.immanuel.co`) ab. Der Server trägt dort automatisch jedes Mal den neuen bore-Port ein, wenn bore neu startet. Der Suchschlüssel wird aus Ihrem Gruppenschlüssel abgeleitet, kein Konto oder Registrierung erforderlich, vollständig anonym.
**3. Port in `auth_ok` (Absturz-Wiederherstellung)**
Wenn ein Client alles verpasst hat (Server abgestürzt, Migrate-Broadcast nie gesendet), fügt der Server den aktuellen bore-Port in die `auth_ok`-Handshake-Antwort ein. Der Client korrigiert sich beim nächsten erfolgreichen Verbindungsaufbau selbst.
bore.pub-Portänderungen sind für Benutzer transparent. Der Chat wird innerhalb von Sekunden automatisch fortgesetzt, und Dateiübertragungen pausieren und werden ab der unterbrochenen Stelle fortgesetzt.
Um die Erkennung zu deaktivieren (Air-Gapped-Setup oder privater Relay):```bash
python noeyes.py --connect bore.pub --port 12345 --key-file ./chat.key --no-discovery
| Einschränkung | Details |
|---|---|
| Keine Betriebszeitgarantie | bore.pub ist ein Freiwilligendienst, er kann ausfallen |
| Port ist zufällig | Jeder Serverstart erhält einen anderen Port, teilen Sie die Adresse erneut |
| Nicht für den Produktionseinsatz | Für eine dauerhafte Einrichtung verwenden Sie eine VPS mit |
Bei mehr als ~10 Benutzern, 24/7-Betriebszeit oder einem stabilen Hostnamen, führen Sie es auf einer günstigen VPS aus (Hetzner €4/Monat, DigitalOcean $4/Monat, Oracle Cloud kostenlose Stufe):```bash python noeyes.py --server --port 5000 --no-bore
### Firewall-Hinweise
Sie benötigen **keine** Firewall-Regel bei Verwendung des Bore-Tunnels. Sie benötigen nur eine für direkte Verbindungen (LAN, statische IP, manuelle Portweiterleitung):```bash
python noeyes.py --server --port 5000 --no-firewall # bore tunnel, skip firewall rule
python noeyes.py --server --port 5000 --no-bore --no-firewall # VPS, manage firewall separately
python noeyes.py --generate-access-key
python noeyes.py --generate-chat-key <ACCESS_CODE_HEX> --key-file ./chat.key
python ui/launch.py # → Generate Key
cp ~/.noeyes/identity.key /backup/identity.key
cat ~/.noeyes/tofu_pubkeys.json
## Projektstruktur```
NoEyes/
├── noeyes.py Entry point and CLI argument parser
├── requirements.txt pip dependencies (just: cryptography)
│
├── core/
│ ├── encryption.py All crypto: XSalsa20-Poly1305, ChaCha20-Poly1305, X25519, Ed25519, BLAKE2b
│ ├── ratchet.py Sender Keys forward secrecy: SenderChain + RatchetState
│ ├── animation.py CRT boot and ratchet activation animations with SFX
│ ├── sounds.py Cross-platform sound playback (WAV/MP3, Linux/macOS/Windows)
│ ├── identity.py Ed25519 keypair generation and TOFU pubkey store
│ ├── utils.py Terminal output, ANSI colours, TUI chrome
│ └── config.py Configuration loading and CLI parsing
│
├── network/
│ ├── server.py Async zero-metadata blind-forwarder server
│ ├── client.py Terminal chat client (E2E, DH, TOFU, file transfer)
│ ├── client_ratchet.py RatchetMixin — /ratchet command flow, migration wait
│ ├── client_dh.py X25519 DH handshake mixin
│ ├── client_send.py Outgoing message encryption (static + ratchet paths)
│ ├── client_recv.py Incoming frame routing and decryption
│ └── client_commands.py Input loop, command dispatch, help
│
├── ui/
│ ├── launch.py Guided launcher, arrow-key menu UI
│ └── setup.py Dependency wizard, auto-installs what's needed
│
├── install/
│ ├── install.sh Bootstrap for Linux / macOS / Termux / iSH
│ ├── install.bat Bootstrap for Windows (CMD and PowerShell)
│ ├── install.py Cross-platform Python installer
│ └── uninstall.py Remove all NoEyes dependencies for clean reinstall
│
├── docs/
│ ├── README.md This file
│ └── CHANGELOG.md Version history
│
├── update.py Self-updater, pulls latest from GitHub
└── sfx/ Notification sounds
PyNaCl (XSalsa20-Poly1305, BLAKE2b) + cryptography (ChaCha20-Poly1305, X25519, Ed25519, TLS)threading (Empfangs-, Eingabe- und Sende-Threads pro Client), asyncio auf dem Servertermios für rohe Tasteneingabe⚠️ Nur für Forschungs- und Bildungszwecke - experimentelles Projekt.
| Feature | Details |
|---|
| Null-Metadaten-Server | Server sieht niemals Benutzernamen, Raumnamen oder öffentliche Schlüssel, nur undurchsichtige Tokens |
| Versiegelter Absender | Die Identität des Absenders befindet sich im verschlüsselten Payload, niemals im Routing-Header |
| Blind-Weiterleitungs-Server | Keine Entschlüsselung, der Server leitet verschlüsselte Blobs weiter, die er nicht lesen kann |
| Vorwärtssicherheit | /ratchet start — Sender-Keys-Protokoll, jede Nachricht mit einem eindeutig abgeleiteten Schlüssel verschlüsselt, vergangene Nachrichten bleiben sicher, selbst wenn der aktuelle Schlüssel kompromittiert wird |
| Gruppenchat | Raumweise XSalsa20-Poly1305-Schlüssel, abgeleitet über BLAKE2b, Räume kryptografisch isoliert |
| Private Nachrichten | X25519-DH-Handshake beim ersten Kontakt, paarweiser Schlüssel, den nur die beiden Parteien besitzen |
| Dateiübertragung | ChaCha20-Poly1305-Streaming, beliebige Größe, geringer RAM-Verbrauch, Pause/Fortsetzen bei erneuter Verbindung |
| Ed25519-Identität | Automatisch generierter Signaturschlüssel, alle Nachrichten und Dateien sind signiert |
| TOFU | Erstmals gesehene Schlüssel werden vertraut; Schlüsselkonflikte lösen eine sichtbare Sicherheitswarnung aus |
| Zufälliges PBKDF2-Salz | Jede Bereitstellung erhält ein eindeutiges zufälliges Salz, Rainbow-Tabellen sind nutzlos |
| TLS + Zertifikat-Pinning | Transport verschlüsselt, Serverzertifikat wird beim ersten Kontakt per TOFU gepinnt |
| Replay-Schutz | Raumweise Nachrichten-ID-Deque, wiederholte Frames werden stillschweigend verworfen |
| Geteiltes Seitenpanel | Räume (oben) und Benutzer (unten) immer sichtbar, jede Hälfte scrollt unabhängig |
| CRT-Startanimation | Vollbild-Phosphor-Effekt mit Ton beim Start |
| Ratchet-Aktivierungsanimation | Vollbild-CRT-Effekt mit Braille-Zahnrad-Kunst, Glitch-Flimmern, Spotlight-Durchlauf, synchronisierten Soundeffekten und TUI-Chrome-Übergang zu Rot |
| Geführter Launcher | Pfeiltasten-Menü-Benutzeroberfläche, keine Befehlszeilenerfahrung erforderlich |
| Automatischer Abhängigkeitsinstaller | Erkennt deine Plattform, installiert Fehlendes, fragt vor jeder Änderung |
| Befehl | Beschreibung |
|---|
/help | Alle Befehle anzeigen |
/quit | Verbindung trennen und beenden |
/clear | Nachrichten vom Bildschirm löschen |
/users | Benutzer im aktuellen Raum auflisten |
/join <room> | In einen Raum wechseln (warnt, wenn aktiver Ratchet läuft) |
/leave | In den allgemeinen Raum zurückkehren (warnt, wenn aktiver Ratchet läuft) |
/msg <user> <text> | Eine E2E-verschlüsselte private Nachricht senden |
/send <user> <file> | Eine verschlüsselte Datei senden |
/whoami | Ihren Identitäts-Fingerprint anzeigen |
/trust <user> | Den neuen Schlüssel eines Benutzers nach einer Neuinstallation vertrauen |
/notify on|off | Benachrichtigungstöne umschalten |
/ratchet start | Allen Raummitgliedern Vorwärts-Verschlüsselungs-Rollkeys vorschlagen (alle müssen bestätigen) |
/ratchet invite <u> | Einen Benutzer erneut zum Ratchet einladen nach Rückkehr (löst vollständigen Neustart aus – keine Chain-Keys werden weitergeleitet) |
/proceed | Während der Migrationswartezeit abstimmen, einen offline-Peer fallen zu lassen und fortzufahren |
| Taste | Aktion |
|---|
↑ / ↓ | Chat nach oben / unten scrollen |
PgUp / PgDn | Chat eine Seite scrollen |
^P (Strg+P) | Seitenleiste ein-/ausblenden |
^C | Beenden |
| Tag | Farbe | Verwendung für |
|---|
!ok <msg> | 🟢 Grün | Erfolg, bestätigt, erledigt |
!warn <msg> | 🟡 Gelb | Warnung, Achtung |
!danger <msg> | 🔴 Rot | Kritisch, dringend, Notfall |
!info <msg> | 🔵 Blau | Statusupdate, zur Info |
!req <msg> | 🟣 Lila | Anfrage, erfordert Aktion |
!? <msg> | 🩵 Cyan | Frage, Bitte um Input |
--no-bore| Plattform | Verwendeter Paketmanager |
|---|
| Ubuntu / Debian / Mint | apt-get |
| Fedora / RHEL / CentOS | dnf / yum |
| Arch / Manjaro | pacman |
| Alpine / iSH (iOS) | apk |
| openSUSE | zypper |
| Void Linux | xbps-install |
| macOS | Homebrew (auto-installed if missing) |
| Android (Termux) | pkg |
| Windows | winget / Chocolatey / Scoop |