
Chat da terminale sicura. Crittografia E2E, server di inoltro cieco con zero metadati. PyNaCl XSalsa20-Poly1305 + Ed25519 + segretezza in avanti. Python multipiattaforma.
Chat di gruppo crittografata end-to-end, messaggi privati e trasferimento file nel tuo terminale. Il server è un forwarder cieco: non può leggere i tuoi messaggi, non conosce il tuo nome utente, non conosce la stanza in cui ti trovi e non può collegare due messaggi alla stessa persona, anche se completamente compromesso.
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 è uno strumento di chat da terminale in Python per piccoli gruppi fidati. Il server non decrittifica mai nulla e non vede mai chi sei - gestisce solo token opachi e inoltra byte crittografati.
Tu generi la chiave, la condividi out-of-band e il server non viene a sapere nulla delle tue conversazioni.
Utile per piccoli gruppi fidati che vogliono comunicazioni crittografate senza fidarsi di alcun server di terze parti, per chi vuole auto-ospitare una chat privata con vera crittografia end-to-end, o per chiunque voglia capire esattamente cosa un server può e non può vedere.
python ui/setup.py
python ui/launch.py
`ui/launch.py` ti guida nell'avvio di un server o nella connessione a uno.
---
### Opzione B - Se Python non è ancora installato
| Piattaforma | Esegui prima questo |
|---|---|
| Linux / macOS / Termux / iSH | `sh install/install.sh` |
| Windows | `install\install.bat` |
Entrambi gli script installano Python se manca, poi passano automaticamente a `setup.py`.
---
### Opzione C - Manuale```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
Importante: Ogni utente deve avere il proprio file di identità. Due client che condividono lo stesso file di identità ricevono lo stesso token della casella di posta e il server rifiuterà il secondo come sessione duplicata. Il file di identità viene generato automaticamente al primo avvio, basta passare un
--identity-pathunivoco per ogni utente.
Scarica Termux da F-Droid (consigliato): https://f-droid.org/packages/com.termux/
Mantieni viva la sessione - installa tmux così NoEyes continua a funzionare quando cambi app:```bash pkg install tmux -y tmux python ui/launch.py
**Permessi di archiviazione** - il trasferimento di file fallirà senza questo:```bash
termux-setup-storage
▶.Ogni metà scorre in modo indipendente. Premi ^P per nascondere il pannello e avere una visuale della chat a tutta larghezza.
Prefissa qualsiasi messaggio con un !tag per colorarlo per tutti e attivare un suono di notifica. I tag viaggiano dentro il payload cifrato, il server non li vede mai.
Esempi:``` !danger server is going down in 5 minutes !ok deployment successful !req can someone review my PR?
I suoni vengono riprodotti dalla cartella `sfx/`. Inserisci i file `.wav`, `.mp3`, `.ogg`, `.aiff`, `.flac` o `.m4a` denominati in base al tag (es. `sfx/danger.wav`). Se non viene trovato, ripiega sul campanello del terminale. Usa `/notify off` per disabilitare tutti i suoni.
---
## Architettura
> 🗺️ **[Mappa di sicurezza interattiva dal vivo](https://ymsniper.github.io/NoEyes/)** — ripartizione visiva dell'intera architettura di cifratura, del modello di minaccia e del routing a zero metadati in un diagramma interattivo.```
┌──────────────────────────────────────────────────────────────────────┐
│ 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
Ogni client calcola localmente due token opachi prima di connettersi:``` inbox_token = blake2s(identity_vk_bytes, digest_size=16) room_token = blake2s((room_name + group_key_hex).encode(), digest_size=16)
Il server instrada tutti i frame esclusivamente tramite questi token. Non memorizza mai nomi visualizzati, nomi delle stanze o chiavi pubbliche. L'identità del mittente viaggia **all'interno** del payload cifrato (mittente sigillato), non nell'header di instradamento.
### Catena di derivazione delle chiavi```
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
Ogni file di identità riceve un salt casuale univoco, le tabelle arcobaleno sono inutili.
---
## Riepilogo della sicurezza
| Layer | Meccanismo | Note |
|---|---|---|
| Segretezza in avanti (ratchet) | Sender Keys — KDF a catena BLAKE2b + XSalsa20-Poly1305 per messaggio | Chiave univoca per messaggio, avanzamento rapido per messaggi mancati |
| Chat di gruppo | XSalsa20-Poly1305 (secretbox PyNaCl) | Chiave per stanza tramite BLAKE2b |
| Messaggi privati | XSalsa20-Poly1305 con chiave a coppie X25519 | Firmato Ed25519, verificato TOFU |
| Trasferimento file | ChaCha20-Poly1305 | Chiave per trasferimento tramite BLAKE2b, firmato Ed25519, pausa/ripresa tra le riconnessioni |
| Identità del mittente | Sealed sender | Nome utente + firma all'interno del payload crittografato, mai nell'intestazione di instradamento |
| Identità | Coppia di chiavi Ed25519 | File di identità per utente, crittografato con password tramite BLAKE2b + salt casuale |
| Derivazione delle chiavi | BLAKE2b (PyNaCl) | Separato per dominio tramite parametro di personalizzazione, niente tabelle arcobaleno |
| Instradamento del server | Token blake2s opachi | Il server non memorizza mai nomi utente, nomi delle stanze o chiavi pubbliche |
| Trasporto | TLS (attivo per impostazione predefinita) | Pinning dei certificati TOFU, impronta non corrispondente interrompe la connessione |
| Integrità DH | Chiavi pubbliche DH firmate Ed25519 | Previene MITM sullo scambio di chiavi a coppie |
| Protezione dalla ripetizione | Deque di ID messaggio per stanza | I frame riprodotti vengono scartati silenziosamente |
| Protezione DoS | Limite di connessioni + timeout di join + limitazione della velocità | Massimo 200 connessioni, timeout di join di 10 s |
| Isolamento delle stanze | `BLAKE2b(master_key, room_name)` | Isolata crittograficamente per stanza |
### Modello di minaccia
NoEyes è progettato per **piccoli gruppi fidati**. Offre una forte protezione contro:
- Osservatori passivi di rete - tutto il traffico è crittografato TLS + E2E
- Relay bore.pub compromesso - il relay vede solo byte crittografati e tempi di connessione
- Macchina server compromessa - il server è zero-knowledge, nulla di utile in RAM
- MITM sulla connessione - pinning dei certificati TLS + chiavi DH firmate Ed25519
- Qualcuno che ruba il tuo dispositivo - la chiave di identità è crittografata con password a riposo
- Attacchi di replica - protezione anti-replay per stanza basata su MID
---
## Eseguire un server online (bore pub)
Quando avvii un server NoEyes a casa, la tua macchina ottiene un IP locale. Per consentire a qualcuno al di fuori della tua rete di connettersi, normalmente dovresti inoltrare una porta sul router, cosa che spesso fallisce a causa di CGNAT o blocchi a livello di operatore.
**bore pub** risolve questo problema con un tunnel sicuro dalla tua macchina a un relay pubblico, dando al tuo server un indirizzo pubblico immediato senza toccare il router.
**bore** è uno strumento open-source di tunneling TCP di [Eric Zhang (@ekzhang)](https://github.com/ekzhang/bore). Quando esegui il server NoEyes, si avvia automaticamente:```
bore local 5000 --to bore.pub
Il relay assegna una porta casuale e stampa un indirizzo come bore.pub:12345. Condividilo con il tuo gruppo:```bash
python noeyes.py --connect bore.pub --port 12345 --key-file ./chat.key --username alice --identity-path ~/.noeyes/identity_alice.key
Everything is still end-to-end encrypted, bore only forwards raw bytes.
### Riconnessione automatica in caso di modifica della porta di bore
bore.pub assegna una **porta casuale a ogni riavvio del server**. Normalmente ciò significherebbe dover ricondividere l'indirizzo con tutti ogni volta. NoEyes gestisce tutto automaticamente con tre livelli di recupero:
**1. Evento Migrate (immediato)**
Quando bore riassegna una porta, il server trasmette un evento firmato `migrate` a tutti i client connessi con il nuovo numero di porta. I client si disconnettono silenziosamente, aggiornano la porta e si riconnettono automaticamente. Una finestra di quiete di 15 secondi sopprime il rumore di entrata/uscita, così la schermata della chat non lampeggia.
**2. Servizio di discovery (client che hanno perso il migrate)**
Se un client era offline quando la porta è cambiata, interroga un servizio chiave-valore anonimo e gratuito (`keyvalue.immanuel.co`) a ogni tentativo di riconnessione. Il server pubblica lì la nuova porta di bore automaticamente ogni volta che bore si riavvia. La chiave di lookup è derivata dalla chiave del tuo gruppo; non servono account o registrazione, è completamente anonimo.
**3. Porta in `auth_ok` (recupero da crash)**
Se un client ha perso tutto (il server è andato in crash, il broadcast di migrate non è mai stato inviato), il server include la porta corrente di bore nella risposta di handshake `auth_ok`. Il client si auto-corregge alla successiva connessione riuscita.
Le modifiche alla porta di bore.pub sono trasparenti per gli utenti. La chat continua automaticamente entro pochi secondi e i trasferimenti file si mettono in pausa e riprendono da dove erano rimasti.
Per disabilitare il discovery (configurazione air-gapped o relay privato):```bash
python noeyes.py --connect bore.pub --port 12345 --key-file ./chat.key --no-discovery
| Limitazione | Dettagli |
|---|---|
| Nessuna garanzia di uptime | bore.pub è un servizio volontario, può andare giù |
| La porta è casuale | Ogni avvio del server ottiene una porta diversa, ricondividi l'indirizzo |
| Non per la produzione | Per una configurazione permanente, usa un VPS con --no-bore |
Per più di ~10 utenti, uptime 24/7, o un hostname stabile, usa un VPS economico (Hetzner €4/mese, DigitalOcean $4/mese, Oracle Cloud piano gratuito):```bash python noeyes.py --server --port 5000 --no-bore
### Note sul firewall
Non è **necessaria** una regola firewall quando si utilizza bore tunnel. Serve solo per connessioni dirette (LAN, IP statico, inoltro manuale delle porte):```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
---
## Struttura del Progetto```
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 (thread recv + input + sender per client), asyncio sul servertermios per l'input da tastiera raw⚠️ Solo per uso di ricerca e didattico - progetto sperimentale.
| Feature | Details |
|---|
| Server a metadati zero | Il server non vede mai nomi utente, nomi delle stanze o chiavi pubbliche, solo token opachi |
| Mittente sigillato | L'identità del mittente vive nel payload crittografato, mai nell'header di instradamento |
| Server di inoltro cieco | Zero decrittazione, il server inoltra blob crittografati che non può leggere |
| Forward secrecy | /ratchet start — protocollo Sender Keys, ogni messaggio è crittografato con una chiave derivata unica, i messaggi passati sono al sicuro anche se la chiave corrente viene compromessa |
| Chat di gruppo | Chiavi XSalsa20-Poly1305 per stanza derivate tramite BLAKE2b, stanze isolate crittograficamente |
| Messaggi privati | Handshake X25519 DH al primo contatto, chiave di coppia detenuta solo dalle due parti |
| Trasferimento file | Streaming ChaCha20-Poly1305, qualsiasi dimensione, basso utilizzo di RAM, pausa/ripresa tra le riconnessioni |
| Identità Ed25519 | Chiave di firma generata automaticamente, tutti i messaggi e i file sono firmati |
| TOFU | Le chiavi viste per la prima volta sono considerate affidabili; le discrepanze delle chiavi attivano un avviso di sicurezza visibile |
| Salt PBKDF2 casuale | Ogni installazione riceve un salt casuale unico, le tabelle arcobaleno sono inutili |
| TLS + cert pinning | Trasporto crittografato, certificato del server bloccato al primo contatto tramite TOFU |
| Protezione anti-replay | Deque degli ID messaggio per stanza, i frame riprodotti vengono scartati silenziosamente |
| Pannello laterale diviso | Stanze (in alto) e utenti (in basso) sempre visibili, ogni metà scorre in modo indipendente |
| Animazione di avvio CRT | Effetto fosforico a schermo intero con audio all'avvio |
| Animazione di attivazione del ratchet | Effetto CRT a schermo intero con arte a ingranaggi in braille, sfarfallio glitch, spazzata del riflettore, SFX sincronizzati e transizione della TUI verso il rosso |
| Launcher guidato | Interfaccia a menu con tasti freccia, nessuna esperienza con la riga di comando richiesta |
| Installer automatico delle dipendenze | Rileva la tua piattaforma, installa ciò che manca, chiede prima di modificare qualsiasi cosa |
| Comando | Descrizione |
|---|
/help | Mostra tutti i comandi |
/quit | Disconnetti ed esci |
/clear | Cancella i messaggi dallo schermo |
/users | Elenca gli utenti nella stanza corrente |
/join <room> | Passa a una stanza (avvisa se si è in un ratchet attivo) |
/leave | Torna alla stanza generale (avvisa se si è in un ratchet attivo) |
/msg <user> <text> | Invia un messaggio privato cifrato E2E |
/send <user> <file> | Invia un file cifrato |
/whoami | Mostra la tua impronta di identità |
/trust <user> | Fida della nuova chiave di un utente dopo una reinstallazione |
/notify on|off | Attiva/disattiva i suoni di notifica |
/ratchet start | Propone chiavi rotanti a segretezza avanzata a tutti i membri della stanza (tutti devono confermare) |
/ratchet invite <u> | Re-invita un utente al ratchet dopo che si è ricongiunto (provoca un riavvio completo — nessuna chiave di catena viene inoltrata) |
/proceed | Durante l'attesa di migrazione, vota per abbandonare un peer offline e riprendere |
| Tasto | Azione |
|---|
↑ / ↓ | Scorri la chat in alto / in basso |
PgUp / PgDn | Scorri la chat di una pagina |
^P (Ctrl+P) | Mostra / nascondi il pannello laterale |
^C | Esci |
| Tag | Colore | Utilizzo |
|---|
!ok <msg> | 🟢 Verde | Successo, confermato, fatto |
!warn <msg> | 🟡 Giallo | Avviso, attenzione |
!danger <msg> | 🔴 Rosso | Critico, urgente, emergenza |
!info <msg> | 🔵 Blu | Aggiornamento di stato, per tua informazione |
!req <msg> | 🟣 Viola | Richiesta, richiede un'azione |
!? <msg> | 🩵 Ciano | Domanda, richiesta di input |
| Piattaforma | Gestore pacchetti utilizzato |
|---|
| 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 (installato automaticamente se assente) |
| Android (Termux) | pkg |
| Windows | winget / Chocolatey / Scoop |