docs: rewrite README with complete setup, security, and file transfer guide
Clear, concise documentation covering installation, hosting, connection security (Tailscale/LAN/public), password sharing, file transfer protocol, CLI reference, helper scripts, and architecture overview. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
70ddca8a1f
commit
dc1b5e5ccf
279
README.MD
279
README.MD
|
|
@ -1,133 +1,216 @@
|
|||
<div align="center">
|
||||
|
||||
# 🤐 CMD-CHAT
|
||||
# CMD-CHAT
|
||||
|
||||
### encrypted terminal chat. no servers. no logs. ram only.
|
||||
### end-to-end encrypted terminal chat with file transfer
|
||||
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](https://www.python.org/downloads/)
|
||||
[](https://www.python.org/downloads/)
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
peer-to-peer encrypted chat that runs in your terminal. you host, you control. close the window — everything's gone.
|
||||
Encrypted chat that runs in your terminal. You host the server, you control the room. Close the window — everything's gone. Messages and files are encrypted client-side before the server ever sees them.
|
||||
|
||||
## why
|
||||
## Features
|
||||
|
||||
every "secure" messenger still stores metadata somewhere. this doesn't. it's just two terminals talking over an encrypted tunnel. nothing written to disk, ever.
|
||||
- **End-to-end encrypted** — messages encrypted with Fernet (AES-128-CBC + HMAC) before leaving your machine
|
||||
- **TLS by default** — auto-generated self-signed certs, or bring your own
|
||||
- **SRP authentication** — password never sent over the network (zero-knowledge proof)
|
||||
- **Encrypted file transfer** — `/send`, `/accept`, `/reject` with SHA-256 verification
|
||||
- **RAM only** — nothing written to disk on the server
|
||||
- **Rate limiting** — brute-force protection on auth endpoints
|
||||
- **No IP leaks** — client IPs never broadcast to other users
|
||||
- **Password hidden** — prompted securely via `getpass`, never visible in `ps` or shell history
|
||||
|
||||
## how it works
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ SRP AUTHENTICATION │
|
||||
├──────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ CLIENT SERVER │
|
||||
│ │ │ │
|
||||
│ │─────── POST /srp/init {username, A} ───────► │ │
|
||||
│ │ (A = client public ephemeral) │ │
|
||||
│ │ │ │
|
||||
│ │◄──── {user_id, B, salt, room_salt} ───────── │ │
|
||||
│ │ (B = server public ephemeral) │ │
|
||||
│ │ (room_salt = E2E key derivation) │ │
|
||||
│ │ │ │
|
||||
│ │ [client derives room_key via HKDF: │ │
|
||||
│ │ room_key = HKDF(password, room_salt)] │ │
|
||||
│ │ │ │
|
||||
│ │ [both sides compute SRP session key │ │
|
||||
│ │ using password + ephemeral values] │ │
|
||||
│ │ │ │
|
||||
│ │─────── POST /srp/verify {user_id, M} ──────► │ │
|
||||
│ │ (M = client proof) │ │
|
||||
│ │ │ │
|
||||
│ │◄────────── {H_AMK, session_key} ──────────── │ │
|
||||
│ │ (H_AMK = server proof) │ │
|
||||
│ │ │ │
|
||||
│ │ [password never transmitted] │ │
|
||||
│ │ [MITM can't derive session key] │ │
|
||||
│ │ │ │
|
||||
├──────────────────────────────────────────────────────────────────┤
|
||||
│ E2E ENCRYPTED CHAT │
|
||||
├──────────────────────────────────────────────────────────────────┤
|
||||
│ │ │ │
|
||||
│ │═══════ WebSocket /ws/chat?user_id ═════════► │ │
|
||||
│ │ (authenticated session) │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ ┌─┴─┐ ┌──┴──┐ │
|
||||
│ │ C │──── encrypt(msg, room_key) ───────────►│ S │ │
|
||||
│ │ L │ │ E │ │
|
||||
│ │ I │◄─── ciphertext (broadcast) ────────────│ R │ │
|
||||
│ │ E │ │ V │ │
|
||||
│ │ N │ decrypt(ciphertext, room_key) │ E │ │
|
||||
│ │ T │ │ R │ │
|
||||
│ └─┬─┘ └──┬──┘ │
|
||||
│ │ │ │
|
||||
│ │ [server stores ONLY ciphertext] │ │
|
||||
│ │ [server CANNOT read messages] │ │
|
||||
│ │ [all clients with same password │ │
|
||||
│ │ derive identical room_key] │ │
|
||||
│ │ │ │
|
||||
│ │ Encryption: Fernet (AES-128-CBC + HMAC) │ │
|
||||
│ │ Key derivation: HKDF-SHA256 │ │
|
||||
│ │ │ │
|
||||
│ │ [on disconnect: keys wiped from RAM] │ │
|
||||
│ │ │ │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ KEY HIERARCHY │
|
||||
├──────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ password ──┬──► SRP ──► session_key (per-user, auth only) │
|
||||
│ │ │
|
||||
│ └──► HKDF(password, room_salt) ──► room_key (shared) │
|
||||
│ │
|
||||
│ room_salt: generated once at server start │
|
||||
│ room_key: deterministic, same for all clients with same pwd │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**SRP (Secure Remote Password)** — password is never sent over the network. both sides prove they know it via zero-knowledge proof, then derive identical session keys.
|
||||
|
||||
## install
|
||||
## Install
|
||||
|
||||
```bash
|
||||
python -m venv venv && source venv/bin/activate && pip install -r requirements.txt
|
||||
git clone https://github.com/diorwave/cmd-chat.git
|
||||
cd cmd-chat
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
windows:
|
||||
## Quick Start
|
||||
|
||||
**Host a chat room:**
|
||||
|
||||
```bash
|
||||
python -m venv venv ; .\venv\Scripts\activate ; pip install -r requirements.txt
|
||||
python3 cmd_chat.py serve 0.0.0.0 3000
|
||||
```
|
||||
|
||||
## usage
|
||||
You'll be prompted for a room password (hidden input). An admin token and TLS cert path will print to the console.
|
||||
|
||||
start server:
|
||||
**Connect to a chat room:**
|
||||
|
||||
```bash
|
||||
python cmd_chat.py serve 0.0.0.0 3000 --password mysecret
|
||||
python3 cmd_chat.py connect SERVER_IP 3000 yourname --insecure
|
||||
```
|
||||
|
||||
connect:
|
||||
`--insecure` is needed for self-signed certs. You'll be prompted for the room password.
|
||||
|
||||
## Securing Your Connection
|
||||
|
||||
### Tailscale (recommended)
|
||||
|
||||
Both parties install [Tailscale](https://tailscale.com). Traffic goes through an encrypted WireGuard tunnel. No port forwarding, works across NATs.
|
||||
|
||||
```bash
|
||||
python cmd_chat.py connect SERVER_IP 3000 username mysecret
|
||||
# Host
|
||||
python3 cmd_chat.py serve 0.0.0.0 3000
|
||||
|
||||
# Friend connects using your Tailscale IP
|
||||
python3 cmd_chat.py connect 100.x.x.x 3000 theirname --insecure
|
||||
```
|
||||
|
||||

|
||||
Find your Tailscale IP: `tailscale ip -4`
|
||||
|
||||
## features
|
||||
### LAN (same network)
|
||||
|
||||
- **ram only** — nothing touches disk
|
||||
- **rsa + aes** — key exchange + symmetric encryption
|
||||
- **no central server** — direct p2p connection
|
||||
- **srp auth** — password never sent over network
|
||||
Use your local IP. Both devices must be on the same WiFi/network.
|
||||
|
||||
## license
|
||||
```bash
|
||||
python3 cmd_chat.py connect 192.168.1.x 3000 theirname --insecure
|
||||
```
|
||||
|
||||
### Public Internet
|
||||
|
||||
Requires port forwarding on your router (TCP port 3000 to your machine). Use `--cert` and `--key` with a real certificate for production use.
|
||||
|
||||
```bash
|
||||
python3 cmd_chat.py connect PUBLIC_IP 3000 theirname --insecure
|
||||
```
|
||||
|
||||
Find your public IP: `curl ifconfig.me`
|
||||
|
||||
## Sharing the Room Password
|
||||
|
||||
The password must be shared outside the chat. Never send it over an unencrypted channel.
|
||||
|
||||
1. **In person** — tell them verbally
|
||||
2. **Signal** — disappearing message set to 30 seconds
|
||||
3. **One-time link** — [onetimesecret.com](https://onetimesecret.com) (self-destructs after one view)
|
||||
4. **Split it** — send half via Telegram, half via SMS
|
||||
|
||||
## Chat Commands
|
||||
|
||||
| Command | Action |
|
||||
|---------|--------|
|
||||
| `/send <filepath>` | Propose a file transfer to the room |
|
||||
| `/accept` | Accept a pending file offer |
|
||||
| `/reject` | Decline a pending file offer |
|
||||
| `q` | Disconnect |
|
||||
|
||||
### File Transfer
|
||||
|
||||
Files are chunked (64KB), encrypted with the room key, and relayed through the server as opaque ciphertext. The server never sees file names, contents, or metadata.
|
||||
|
||||
```
|
||||
alice> /send report.pdf
|
||||
bob> "alice wants to send report.pdf (1.2 MB) — /accept or /reject"
|
||||
bob> /accept
|
||||
Receiving: 100% (1.2 MB/1.2 MB)
|
||||
File saved: ./downloads/report.pdf — SHA-256 verified
|
||||
```
|
||||
|
||||
- Max file size: 50 MB
|
||||
- Files saved to `./downloads/` relative to where the client was launched
|
||||
- SHA-256 integrity check on every transfer
|
||||
|
||||
## CLI Reference
|
||||
|
||||
### Server
|
||||
|
||||
```bash
|
||||
python3 cmd_chat.py serve <bind_ip> <port> [options]
|
||||
```
|
||||
|
||||
| Flag | Purpose |
|
||||
|------|---------|
|
||||
| `--password`, `-p` | Room password (prompted if omitted) |
|
||||
| `--cert` | Path to TLS certificate |
|
||||
| `--key` | Path to TLS private key |
|
||||
| `--no-tls` | Disable TLS (local dev only) |
|
||||
|
||||
### Client
|
||||
|
||||
```bash
|
||||
python3 cmd_chat.py connect <server_ip> <port> <username> [options]
|
||||
```
|
||||
|
||||
| Flag | Purpose |
|
||||
|------|---------|
|
||||
| `--password`, `-p` | Room password (prompted if omitted) |
|
||||
| `--insecure`, `-k` | Skip TLS cert verification (self-signed certs) |
|
||||
| `--no-tls` | Connect without TLS |
|
||||
|
||||
### Environment Variable
|
||||
|
||||
Set `CMD_CHAT_PASSWORD` to skip the password prompt for both server and client.
|
||||
|
||||
## Helper Scripts
|
||||
|
||||
### `./lab/host-chat.sh`
|
||||
|
||||
One-command server setup. Detects your IPs (Tailscale, LAN, public), prints the exact connect command your friend needs, then starts the server.
|
||||
|
||||
```bash
|
||||
./lab/host-chat.sh # TLS on port 4000
|
||||
./lab/host-chat.sh --port 5000 # custom port
|
||||
./lab/host-chat.sh --no-tls # disable TLS
|
||||
```
|
||||
|
||||
### `./lab/setup-lab.sh`
|
||||
|
||||
Spins up a tmux session with the server and two chat clients side-by-side for local testing.
|
||||
|
||||
```bash
|
||||
./lab/setup-lab.sh # default lab
|
||||
./lab/setup-lab.sh --no-tls --port 4001 # plain HTTP
|
||||
./lab/setup-lab.sh --user1 alice --user2 bob
|
||||
./lab/setup-lab.sh --teardown # clean up
|
||||
```
|
||||
|
||||
Attach with `tmux attach -t cmd-chat-lab`. Switch panes with `Ctrl+B` then arrow keys.
|
||||
|
||||
## How It Works
|
||||
|
||||
```
|
||||
CLIENT SERVER CLIENT
|
||||
│ │ │
|
||||
│── POST /srp/init {A} ──────────► │ │
|
||||
│◄── {B, salt, room_salt} ──────── │ │
|
||||
│ │ │
|
||||
│ derive room_key = HKDF(password, room_salt) │
|
||||
│ │ │
|
||||
│── POST /srp/verify {M} ────────► │ │
|
||||
│◄── {H_AMK, ws_token} ─────────── │ │
|
||||
│ │ │
|
||||
│══ WSS /ws/chat?ws_token ════════► │ ◄══════════════════════════════│
|
||||
│ │ │
|
||||
│ encrypt(msg, room_key) ────────► │ ──── ciphertext ────────────► │
|
||||
│ │ decrypt(ciphertext, room_key)
|
||||
│ │ │
|
||||
│ server stores ONLY ciphertext │ │
|
||||
│ server CANNOT read messages │ │
|
||||
```
|
||||
|
||||
**SRP (Secure Remote Password)** — both sides prove they know the password without transmitting it. A network observer learns nothing.
|
||||
|
||||
**Room Key** — derived independently by each client via `HKDF(password, room_salt)`. All clients with the same password get the same key. The server never has the key.
|
||||
|
||||
**WebSocket Auth** — HMAC-SHA256 token issued after SRP verification. Prevents session hijacking.
|
||||
|
||||
## Admin
|
||||
|
||||
The server prints an admin token at startup. Use it to clear message history:
|
||||
|
||||
```bash
|
||||
curl -k -X DELETE https://SERVER:3000/clear \
|
||||
-H "Authorization: Bearer <admin-token>"
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user