
Peer-to-Peer-verschlüsselter Messenger in Rust mit Noise IK und UDP-NAT-Lochbohren
Ende-zu-Ende-verschlüsselter Peer-to-Peer-Chat über UDP. Keine Konten, kein zentraler Server, der Nachrichten weiterleitet/speichert, kein Mittelsmann. Nur zwei Peers, eine direkte Verbindung und Noise-Protokoll-Verschlüsselung.
https://github.com/user-attachments/assets/939e96d3-45e3-4484-9a27-28c3a0457b05
Zwei Personen führen punchline connect <peer> auf ihren Maschinen aus. Punchline durchbricht ihre NATs, führt einen verschlüsselten Handshake durch und bringt sie in einen privaten Chat – alles in wenigen Millisekunden. Die enthaltenen STUN- und Signal-Server übernehmen die Erkennung und verschwinden dann.

cargo build --release
Starte die Server (auf einer Maschine, die beide Peers erreichen können), oder verwende meine öffentlichen unter 64.225.107.28 (STUN: Port 3478, Signal: Port 8743):
punchline-stund # STUN server - tells peers their public IP
punchline-signald # Signal server - matches peers who want to talk
Auf jedem Peer-Computer:
# Generate your identity (X25519 keypair)
punchline keygen
# Share your public key with your peer
punchline pubkey
# Save their key
punchline peers add alice a1b2c3d4...64_hex_chars
# Connect (both peers run this, targeting each other)
punchline connect alice --stun <server>:3478 --signal <server>:8743
Die TUI startet mit einer Live-Ansicht des Verbindungsfortschritts:
STUN-Erkennung – Ermittlung deiner externen Adresse über punchline-stund
Signal-Server – Verbindung zu punchline-signald
Warten auf Peer – Der Signal-Server bringt beide Peers zusammen
Hole Punch – Herstellung des direkten UDP-Pfads
Noise-Handshake – Verschlüsselter Schlüsselaustausch
Sobald abgeschlossen, bist du im Chat. Tippe und drücke Enter. Drücke Esc, um zu beenden.
Das gesamte System besteht aus drei Binärdateien, die alle in diesem Repository enthalten sind:
| Binary | Rolle | Verwendung |
|---|
Nach der anfänglichen Einrichtung werden die STUN- und Signal-Server nicht mehr kontaktiert. Der gesamte Datenverkehr erfolgt direkt Peer-to-Peer.
punchlineGlobale Flags:
| Flag | Beschreibung |
|---|---|
-v | Erhöht die Protokollausführlichkeit (-v = Debug, -vv = Trace). |
-q, --quiet | Unterdrückt die gesamte Protokollausgabe. |
punchline-stundpunchline-signaldAnstatt jedes Mal --stun und --signal zu übergeben, erstelle ~/.config/punchline/config.toml:
stun_server = "203.0.113.10:3478"
signal_server = "203.0.113.10:8743"
punchline peers # list all
punchline peers add alice a1b2c3d4... # add
punchline peers remove alice # remove
Die Aliase werden in ~/.punchline/known_peers.toml gespeichert. Du kannst auch direkt mit einem rohen 64-stelligen Hex-Schlüssel verbinden.
punchline status
Zeigt deine Identität, Konfiguration, Servererreichbarkeit (sendet eine echte STUN-Abfrage und TCP-Verbindung) und die Anzahl der Peers.
Beide Server unterstützen -v (Debug), -vv (Trace), -q (Lautlos), --address und --port:
punchline-stund -v --port 3478
punchline-signald -v --port 8743
Passe die TUI über ~/.config/punchline/style.toml an.
Im Video verwendete Stile:
[colors]
my_text = "#ebdbb2"
peer_text = "#bdae93"
input_text = "#ebdbb2"
border = "#ebdbb2"
sidebar_key = "#ebdbb2"
sidebar_value = "#bdae93"
[padding]
chat_horizontal = 2
chat_vertical = 1
Alle Farben sind hex RGB. Wenn die Datei fehlt, werden die Standardfarben des Terminals verwendet.
punchline completions bash > ~/.local/share/bash-completion/completions/punchline
punchline completions zsh > ~/.zfunc/_punchline
punchline completions fish > ~/.config/fish/completions/punchline.fish
Vollständiger Protokollname: Noise_IK_25519_ChaChaPoly_SHA256
| Komponente | Rolle |
|---|
Das IK-Muster bedeutet, dass der Initiator den statischen öffentlichen Schlüssel des Responders kennt, bevor der Handshake beginnt. Beide Peers haben bereits die Schlüssel des jeweils anderen (out-of-band oder über die Peer-Registry ausgetauscht), sodass kein Trust-on-First-Use erforderlich ist.
Punchline wählt den Initiator deterministisch aus, indem die ersten 8 Bytes des öffentlichen Schlüssels jedes Peers als Big-Endian-u64 verglichen werden. Der Peer mit dem kleineren Wert wird zum Initiator. Beide Seiten berechnen dies unabhängig.
Die Identität ist ein 32-Byte-X25519-Geheimschlüssel unter ~/.punchline/id_x25519 mit Unix-Berechtigungen 0600. Der öffentliche Schlüssel wird beim Laden abgeleitet. Die Schlüsselerzeugung verwendet x25519-dalek mit OsRng.
Das erste Byte jedes UDP-Pakets identifiziert seinen Typ:
Beide Peers führen gleichzeitig denselben Algorithmus aus:
PROBE (0x00) an die externe Adresse des Peers.PROBE wechsle zum Senden von ACK (0x01).ACK sende ein letztes ACK und erkläre den Erfolg.Nachrichten (0x02) enthalten Noise-verschlüsselte UTF-8-Nutzlasten. Keepalives (0x03) sind verschlüsselte leere Nutzlasten, die alle 10 Sekunden gesendet werden, um die Nonce-Synchronisation der Chiffre aufrechtzuerhalten. 30 Sekunden ohne ein Paket führen zur Trennung.
JSON über WebSocket:
// PairRequest (client -> server)
{ "external_addr": "203.0.113.5:48291", "public_key": "a1b2...", "target_public_key": "d4e5..." }
// PairResponse (server -> client)
{ "target_external_addr": "198.51.100.7:51003", "target_public_key": "d4e5..." }
Folgt RFC 5389 (vereinfacht): Binding-Request/Response mit XOR-MAPPED-ADDRESS. Nur IPv4.
Cargo-Workspace mit vier Crates:
crates/
├── proto/ # Gemeinsame Bibliothek: Krypto, STUN, Signal-Typen, Transport-Trait
├── client/ # P2P-Client: CLI, TUI, Verbindungslogik, Peer-Verwaltung
├── signald/ # Signal-Server: WebSocket-Peer-Zuordnung
└── stund/ # STUN-Server: Erkennung externer Adressen
cargo install punchline # TUI client
cargo install punchline-signald # Signal server
cargo install punchline-stund # STUN server
Voraussetzungen: Rust 2024 Edition (rustc 1.85+)
git clone https://github.com/michal-pielka/punchline.git
cd punchline
cargo build --release
Binärdateien befinden sich in target/release/:
punchlinepunchline-signaldpunchline-stundcargo test
Tests decken kryptografische Operationen, STUN-Codierung/-Decodierung, Signalprotokoll-Serialisierung, Konfigurationsparsing, Peer-Verwaltung, Style-Theming und den Noise-IK-Handshake ab.
MIT – siehe LICENSE.
punchline-stund | STUN-Server (UDP) – antwortet mit der externen IP:Port des Clients | Nur während der Einrichtung |
punchline-signald | Signal-Server (WebSocket) – vermittelt Peers und tauscht Adressen aus | Nur während der Einrichtung |
punchline | Der Messenger selbst – CLI, TUI, Krypto, Hole Punching | Immer |
| Befehl | Beschreibung |
|---|
keygen [--force] [-i Pfad] | Neues X25519-Identitätsschlüsselpaar generieren. Verwende --force, um ohne Nachfrage zu überschreiben. Verwende -i, um den Ausgabepfad anzugeben. |
pubkey [-i Pfad] | Deinen öffentlichen Schlüssel (64 Hex-Zeichen) ausgeben. Verwende -i, um von einer bestimmten Schlüsseldatei abzuleiten. |
connect <peer> [-i Pfad] [--stun Adresse] [--signal Adresse] | Verbindung zu einem Peer per Alias oder rohem Hex-Schlüssel herstellen. Verwende -i, um den Identitätsschlüssel anzugeben. Startet die TUI. |
peers | Alle bekannten Peers auflisten. |
peers add <Name> <Schlüssel> | Öffentlichen Schlüssel eines Peers unter einem Spitznamen speichern. |
peers remove <Name> | Peer per Spitznamen entfernen. |
config path | Pfad der Konfigurationsdatei ausgeben. |
config show | Aktuelle Konfigurationswerte anzeigen. |
status | Identität, Konfiguration, Servererreichbarkeit und Anzahl der Peers anzeigen. |
completions <shell> | Shell-Vervollständigungen generieren (bash, zsh oder fish). |
| Flag | Beschreibung |
|---|
--address <Adresse> | Bindeadresse (Standard: 0.0.0.0). |
--port <Port> | Binde Port (Standard: 3478). |
-v / -vv | Debug / Trace Logging. |
-q | Lautlos-Modus. |
| Flag | Beschreibung |
|---|
--address <Adresse> | Bindeadresse (Standard: 0.0.0.0). |
--port <Port> | Binde Port (Standard: 8743). |
-v / -vv | Debug / Trace Logging. |
-q | Lautlos-Modus. |
| Noise IK | Handshake-Muster – Initiator kennt den öffentlichen Schlüssel des Responders. Abgeschlossen in 2 Nachrichten. |
| X25519 | Elliptische-Kurven-Diffie-Hellman-Schlüsselaustausch (RFC 7748). 128-Bit-Sicherheit, konstante Laufzeit. |
| ChaCha20-Poly1305 | AEAD-Chiffre für Nachrichtenverschlüsselung (RFC 8439). Dieselbe Chiffre wird in TLS 1.3 und WireGuard verwendet. |
| SHA-256 | Wird intern von Noise für Schlüsselableitung und Handshake-Hashing verwendet. |
| Präfix | Typ | Phase | Beschreibung |
|---|
0x00 | PROBE | Hole Punch | Wird alle 200 ms gesendet, um ein NAT-Pinhole zu öffnen |
0x01 | ACK | Hole Punch | Bestätigt den Empfang eines PROBE |
| (keines) | Handshake | Handshake | Rohe Noise-verschlüsselte Handshake-Nutzlast |
0x02 | Message | Transport | Verschlüsselte Chat-Nachricht |
0x03 | Keepalive | Transport | Verschlüsselte leere Nutzlast (Herzschlag) |
| Crate | Zweck |
|---|
snow | Noise-Protokoll-Framework (Handshake + Transportverschlüsselung) |
x25519-dalek | X25519-Schlüsselerzeugung und -Ableitung |
ratatui | Terminal-UI-Framework |
crossterm | Terminal-Ereignisbehandlung |
clap | CLI-Argumentparsing + Shell-Vervollständigung |
tungstenite | WebSocket-Client/Server |
tracing | Strukturiertes Logging |