Most people don't appreciate anonymity before they lose it, and you only get to lose it once. Susurri will help you keep it as long as possible.
Secure Peer-to-Peer Chat with DHT-Based Discovery and Onion Routing
Susurri is a decentralized, privacy-focused chat application that combines Kademlia DHT for peer discovery with Tor-like onion routing for anonymous communication. No central servers, no metadata leaks, complete privacy.
- Decentralized Architecture - No central servers; peers discover each other via DHT
- Kademlia DHT - Distributed hash table for peer discovery and public key distribution
- Onion Routing - Three-layer encryption for sender anonymity (Tor-like)
- End-to-End Encryption - X25519 key exchange + ChaCha20-Poly1305 AEAD
- Deterministic Identity - PBKDF2-based key generation from passphrase + salt
- Message Padding - Fixed 16KB message blocks to resist traffic analysis attacks
- Group Messaging - Encrypted group chats with shared symmetric key distribution
- Passphrase Generation - Built-in BIP39 word list generator (12-24 words)
- Mandatory Signatures - Ed25519 signatures required on all messages
- HKDF Domain Separation - Unique context strings per cryptographic operation (RFC 5869)
- Credential Caching - Optional encrypted local storage for credentials
- Offline Messages - DHT stores encrypted messages for offline recipients; retrieval requires signed proof-of-ownership
- Cross-Platform CLI - Interactive terminal interface for Linux/macOS/Windows
- Desktop Integration - Application menu entries and GUI launcher (Linux)
cd installers/arch
./install.sh# Terminal interface
susurri
# GUI launcher
susurri-gui# Generate a secure passphrase (first time setup)
susurri > generate
=== Generated Passphrase ===
abandon ability able about above absent absorb abstract absurd abuse access accident
[!] IMPORTANT: Write this down and store it securely offline!
# Login with your passphrase (6+ words required)
susurri > login alice
Passphrase: ****************************************************
Save credentials locally for future logins? [y/N]: y
Enter a password to protect cached credentials (8+ chars): ********
[+] Logged in as 'alice'.
[+] Credentials saved locally (encrypted).
# Start DHT node
susurri > dht start
[+] DHT node started on port 7070.
# Check status
susurri > status
User: alice (logged in)
DHT node: RUNNING
# Group chat commands
susurri > group create "Friends"
[+] Group created. Group ID: a1b2c3d4-...
susurri > group list
Your groups:
- Friends (a1b2c3d4-...) - OwnerSusurri follows a modular monolith architecture with clear separation of concerns:
susurri/
├── src/
│ ├── Bootstrapper/ # Application entry points
│ │ ├── Susurri.CLI/ # Cross-platform CLI (Linux/macOS/Windows)
│ │ └── Susurri.Bootstrapper/ # WPF GUI (Windows only)
│ ├── Modules/
│ │ ├── DHT/ # Distributed Hash Table & Networking
│ │ ├── IAM/ # Identity & Access Management
│ │ └── Users/ # User persistence
│ └── Shared/ # Common abstractions & infrastructure
├── tests/ # Unit tests
└── installers/ # Platform-specific installers
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ IAM Module │────▶│ Message │────▶│Users Module │
│ (Identity) │ │ Broker │ │(Persistence)│
└─────────────┘ └─────────────┘ └─────────────┘
│
▼
┌─────────────┐
│ DHT Module │
│ (Network) │
└─────────────┘
| Component | Technology |
|---|---|
| Runtime | .NET 10.0 |
| Cryptography | NSec (libsodium wrapper) |
| Key Derivation | PBKDF2-SHA256 (600k iterations) + HKDF-SHA256 |
| Encryption | X25519 + ChaCha20-Poly1305 |
| Signing | Ed25519 |
| Database | PostgreSQL + Entity Framework Core |
| GUI (Windows) | WPF |
| GUI (Linux) | Zenity/YAD dialogs |
| Testing | xUnit + Shouldly + NSubstitute |
Your identity is derived from a passphrase and a random salt:
Passphrase + Salt (32 bytes, random)
│
▼ PBKDF2-SHA256 (600,000 iterations)
64-byte seed
│
├──▶ Bytes [0-32] → Ed25519 Signing Key
│
└──▶ Bytes [32-64] → X25519 Encryption Key
The random salt ensures that identical passphrases produce different keys. The salt is stored alongside the derived key material. Passphrase generation uses the BIP39 word list (2048 words) for high-entropy, human-readable passphrases.
Susurri implements a full Kademlia DHT with:
- 256-bit Node IDs - SHA256 hash of public key
- XOR Distance Metric - Determines routing proximity
- K-Buckets - 256 buckets, 20 nodes each, LRU eviction
- Iterative Lookups - α=3 parallel queries, k=20 results
Protocol Messages:
| Message | Purpose |
|---|---|
| PING/PONG | Node liveness |
| FIND_NODE | Iterative node lookup |
| FIND_VALUE | Key-value retrieval |
| STORE | DHT storage with TTL |
DHT Storage:
- Public Key Distribution -
username → publicKeymapping - Offline Messages - Encrypted messages for offline users (max 100/user)
- Automatic Expiration - TTL-based cleanup every 5 minutes
Messages are wrapped in three encryption layers:
┌────────────────────────────────────────────────────────────┐
│ Layer 3 (Relay 1's key) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Layer 2 (Relay 2's key) │ │
│ │ ┌────────────────────────────────────────────────┐ │ │
│ │ │ Layer 1 (Relay 3's key) │ │ │
│ │ │ ┌──────────────────────────────────────────┐ │ │ │
│ │ │ │ Message (Recipient's key) │ │ │ │
│ │ │ │ - Sender Public Key │ │ │ │
│ │ │ │ - Content │ │ │ │
│ │ │ │ - Timestamp │ │ │ │
│ │ │ └──────────────────────────────────────────┘ │ │ │
│ │ └────────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
Path: Sender → Relay1 → Relay2 → Relay3 → Recipient
Each relay only knows the previous and next hop. The recipient knows the sender (from the decrypted message), but relays cannot determine the communication endpoints.
Encryption per layer:
- Generate ephemeral X25519 keypair
- ECDH key agreement with relay's public key
- HKDF-SHA256 key derivation with domain-separated context strings
- ChaCha20-Poly1305 authenticated encryption
Reply path: Each message includes encrypted reply tokens that allow the recipient to send ACKs and replies back through the same relay chain without knowing the sender's network address.
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
│ Sender │───▶│ Relay1 │───▶│ Relay2 │───▶│ Relay3 │───▶│Recipient│
└────────┘ └────────┘ └────────┘ └────────┘ └────────┘
│ │ │ │ │
│ Decrypt │ Decrypt │ Decrypt │ Decrypt │
│ Layer 3 │ Layer 2 │ Layer 1 │ Message │
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
[Encrypted] [Next Hop] [Next Hop] [Next Hop] [Plaintext]
# Quick install (builds from source)
cd installers/arch
./install.sh
# Or using PKGBUILD
makepkg -si -p PKGBUILD.local
# System-wide install
sudo ./install.sh --systemSee installers/arch/README.md for details.
# Ensure .NET 10.0+ is installed
# Then use the Arch installer (works on most distros)
cd installers/arch
./install.shPrerequisites:
- .NET SDK 10.0+
- libsodium
# Clone repository
git clone https://github.com/susurri/susurri.git
cd susurri
# Build CLI
dotnet build src/Bootstrapper/Susurri.CLI/Susurri.CLI.csproj -c Release
# Run
dotnet run --project src/Bootstrapper/Susurri.CLI/Susurri.CLI.csprojdotnet build src/Bootstrapper/Susurri.Bootstrapper/Susurri.Bootstrapper.csproj -c Releasesusurri ____ _
/ ___| _ _ ___ _ _ _ __ _ __(_)
\___ \| | | / __| | | | '__| '__| |
___) | |_| \__ \ |_| | | | | | |
|____/ \__,_|___/\__,_|_| |_| |_|
Secure P2P Chat with DHT & Onion Routing
susurri > help
Available Commands:
login [username] - Login with username and passphrase
logout - Logout current user
status - Show current status
dht <command> - DHT node management (see 'dht help')
ping <host> <port> - Ping a DHT node
clear - Clear screen
version - Show version info
help - Show this help
exit - Exit the application
susurri > dht help
DHT Commands:
dht start [port] - Start DHT node (default port: 7070)
dht stop - Stop DHT node
dht status - Show DHT node statussusurri-guiOr search for "Susurri" in your application menu.
Configuration file: ~/.config/susurri/appsettings.json
{
"Messaging": {
"UseBackgroundDispatcher": true
},
"DHT": {
"DefaultPort": 7070,
"BootstrapNodes": [
{ "Host": "bootstrap.susurri.io", "Port": 7070 }
]
},
"Logging": {
"LogLevel": {
"Default": "Warning",
"Susurri": "Information"
}
}
}| Setting | Default | Description |
|---|---|---|
DefaultPort |
7070 | UDP/TCP port for DHT |
BootstrapNodes |
[] | Initial nodes for joining network |
Alpha |
3 | Parallel lookup queries |
K |
20 | Bucket size / lookup results |
RefreshInterval |
60 min | Bucket refresh period |
| Purpose | Algorithm |
|---|---|
| Key Exchange | X25519 (Curve25519 ECDH) |
| Symmetric Encryption | ChaCha20-Poly1305 (AEAD) |
| Key Encryption at Rest | AES-256-GCM |
| Digital Signatures | Ed25519 |
| Hashing | SHA-256 |
| Key Derivation | HKDF-SHA256, PBKDF2-SHA256 (600k iterations) |
| Property | Mechanism |
|---|---|
| Sender Anonymity | Onion routing (3-7 hops) |
| Content Privacy | E2E encryption with ephemeral keys |
| Forward Secrecy | New ephemeral X25519 keypair per message |
| Metadata Protection | DHT obfuscates lookup patterns |
| Message Integrity | Mandatory Ed25519 signatures on all messages |
| Identity Portability | PBKDF2 derivation from passphrase + stored salt |
The following attack vectors are defended against:
| Attack | Defense |
|---|---|
| Key Theft (at rest) | Private keys encrypted with AES-256-GCM using passphrase-derived keys (PBKDF2, 600k iterations) |
| Brute Force (key derivation) | High iteration count (600,000) makes offline attacks computationally expensive |
| Timing Attacks | Constant-time comparisons via CryptographicOperations.FixedTimeEquals() |
| Nonce Reuse | Fresh random nonce generated for every encryption operation |
| Key Recovery | Ephemeral keys ensure forward secrecy; compromised key doesn't reveal past messages |
| Weak Entropy | All random data from RandomNumberGenerator (CSPRNG) |
| Key Confusion | HKDF domain separation with unique context strings per operation (onion layer, direct message, group key wrap, symmetric encryption) per RFC 5869 |
| Attack | Defense |
|---|---|
| Buffer Overflow | Strict size limits on all deserialized data (max 64KB messages, 32KB values) |
| Memory Exhaustion (DoS) | Maximum sizes enforced: values (32KB), messages (64KB), strings (1KB), nodes (20 per response) |
| Malformed Data | Length validation before reading; invalid data throws InvalidDataException |
| Username Injection | Usernames validated: 3-32 chars, alphanumeric + underscore/hyphen only |
| Path Traversal | No user-controlled file paths; fixed secure directories |
| Attack | Defense |
|---|---|
| Protocol Injection | Binary protocol with typed messages; unknown types rejected |
| Oversized Payloads | Size limits on all protocol messages prevent memory exhaustion |
| Invalid Public Keys | Public key size validated (exactly 32 bytes) before cryptographic operations |
| Malformed IP Addresses | IP address length validated (max 16 bytes) during deserialization |
| Offline Message Harvesting | Retrieval requires Ed25519-signed request with timestamp replay protection (±5 min window) |
| Message Forgery | Ed25519 signatures mandatory on all messages; unsigned messages rejected |
| Signature Stripping | Missing signatures treated as invalid (not backward-compatible); messages without signatures are dropped |
| Attack | Defense |
|---|---|
| Memory Disclosure | Sensitive data (keys, passphrases) zeroed with CryptographicOperations.ZeroMemory() |
| Credential Leakage | Credentials stored as byte arrays (clearable) instead of immutable strings |
| Key Material in Memory | Automatic disposal of cryptographic keys via using statements |
| Secure File Deletion | Key files overwritten with zeros before deletion |
| Attack | Defense |
|---|---|
| Unauthorized Access | Unix file permissions set to user-only (700) on key directories |
| Plaintext Credentials | All stored credentials encrypted with AES-256-GCM |
| Weak PIN Protection | Minimum 8-character password required (not 4-digit PIN) |
| Attack | Defense |
|---|---|
| Message Size Correlation | Fixed 16KB message padding - all messages appear identical in size |
| Content Length Analysis | Random padding bytes (not zeros) prevent compression-based inference |
| Timing Correlation | Onion routing with multiple hops obscures timing patterns |
| Attack | Defense |
|---|---|
| Weak Passphrases | Minimum 6 words required (enforced); recommended 12-24 BIP39 words |
| Passphrase Guessing | BIP39 wordlist provides 2048 words; 12 words = 128 bits entropy |
| User-Generated Weakness | Built-in cryptographic passphrase generator using CSPRNG |
| Passphrase Reuse | Random 32-byte salt ensures identical passphrases produce different keys |
| Attack | Defense |
|---|---|
| Group Key Theft | Group keys wrapped (encrypted) individually for each member |
| Unauthorized Access | Members must have valid wrapped key to decrypt group messages |
| Key Compromise | Key rotation support allows generating new group key |
| Member Tracking | Group messages padded to fixed size like direct messages |
┌─────────────────────────────────────────────────────────────┐
│ Application Layer │
├─────────────────────────────────────────────────────────────┤
│ Input Validation │ Username/Message validation, size limits │
├─────────────────────────────────────────────────────────────┤
│ Crypto Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐│
│ │ X25519 ECDH │ │ ChaCha20 │ │ PBKDF2 Key Derivation ││
│ │ Key Exchange│ │ Poly1305 │ │ (600k iterations) ││
│ └─────────────┘ └─────────────┘ └─────────────────────────┘│
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐│
│ │ Ed25519 │ │ HKDF-SHA256 │ │ Domain-separated key ││
│ │ Signatures │ │ (RFC 5869) │ │ derivation contexts ││
│ └─────────────┘ └─────────────┘ └─────────────────────────┘│
├─────────────────────────────────────────────────────────────┤
│ Storage Layer │
│ ┌─────────────────────────────────────────────────────────┐│
│ │ AES-256-GCM encrypted keys │ Secure memory wiping ││
│ └─────────────────────────────────────────────────────────┘│
├─────────────────────────────────────────────────────────────┤
│ Network Layer │
│ ┌─────────────────────────────────────────────────────────┐│
│ │ Size-limited messages │ Validated deserialization ││
│ └─────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────┘
| Risk | Mitigation Status |
|---|---|
| Traffic Analysis | Partial - onion routing hides endpoints; no cover traffic yet |
| Global Adversary | Partial - timing correlation still possible |
| Bootstrap Trust | Initial nodes must be trusted; consider multiple bootstrap sources |
| Sybil Attacks | DHT vulnerable; consider proof-of-work or reputation systems |
src/
├── Bootstrapper/
│ ├── Susurri.CLI/ # Cross-platform CLI
│ │ └── Program.cs # Interactive command loop
│ └── Susurri.Bootstrapper/ # WPF GUI (Windows)
│
├── Modules/
│ ├── DHT/
│ │ └── Susurri.Modules.DHT.Core/
│ │ ├── Kademlia/ # DHT implementation
│ │ │ ├── KademliaId.cs
│ │ │ ├── KBucket.cs
│ │ │ ├── RoutingTable.cs
│ │ │ ├── KademliaNode.cs
│ │ │ ├── KademliaDhtNode.cs
│ │ │ ├── Storage/
│ │ │ └── Protocol/ # PING, FIND_NODE, STORE, etc.
│ │ ├── Network/ # Transport layer
│ │ │ ├── UdpTransport.cs
│ │ │ ├── RelayService.cs
│ │ │ └── ConnectionManager.cs
│ │ ├── Onion/ # Onion routing
│ │ │ ├── OnionBuilder.cs
│ │ │ ├── OnionRouter.cs
│ │ │ ├── OnionLayer.cs
│ │ │ └── ChatMessage.cs
│ │ └── Services/
│ │ └── ChatService.cs
│ │
│ ├── IAM/
│ │ └── Susurri.Modules.IAM.Core/
│ │ ├── Crypto/
│ │ │ ├── CryptoKeyGenerator.cs # PBKDF2 key derivation
│ │ │ └── KeyPair.cs
│ │ └── Keys/
│ │ ├── KeyStorage.cs
│ │ └── CredentialsCache.cs
│ │
│ └── Users/
│ └── Susurri.Modules.Users.Core/
│ ├── DAL/
│ │ ├── UsersDbContext.cs
│ │ └── Repositories/
│ └── Entities/
│ └── User.cs
│
└── Shared/
├── Susurri.Shared.Abstractions/
│ ├── Commands/ # ICommand, ICommandHandler
│ ├── Events/ # IEvent, IEventHandler
│ ├── Queries/ # IQuery, IQueryHandler
│ └── Modules/ # IModule interface
└── Susurri.Shared.Infrastructure/
└── Messaging/
└── InMemoryMessageBroker.cs
- Create project in
src/Modules/YourModule/ - Implement
IModuleinterface:
public class YourModule : IModule
{
public string Name => "Your Module";
public void Register(IServiceCollection services)
{
// Register services
}
public void Initialize(IServiceProvider serviceProvider)
{
// Post-registration setup
}
}- Reference in bootstrapper project
- Module will be auto-discovered and loaded
# Run all tests
dotnet test
# Run specific test project
dotnet test tests/Susurri.Tests.Unit/
# Run with coverage
dotnet test --collect:"XPlat Code Coverage"| Component | Coverage |
|---|---|
| Kademlia (DHT) | KademliaId, KBucket, RoutingTable, DhtStorage |
| Onion Routing | OnionBuilder, OnionLayer, OnionRouter |
| Cryptography | CryptoKeyGenerator, OnionCrypto |
| Network | UdpTransport, RelayProtocol, ConnectionManager |
| Services | ChatService |
- NAT Traversal - STUN-based public endpoint discovery, NAT type detection, UDP hole punching
- Message Signatures - Ed25519 signature verification (mandatory on all messages)
- Encrypted Key Storage - Private keys protected with AES-256-GCM at rest
- Input Validation - Comprehensive validation against injection and DoS
- Secure Memory Handling - Sensitive data wiped from memory after use
- Group Chat - Multi-party encrypted conversations
- File Transfer - Chunked file sharing over onion routes with SHA-256 integrity verification
- Mobile Apps - iOS and Android clients
- Cover Traffic - Dummy messages to prevent traffic analysis
- Bridge Nodes - Bypass network restrictions
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit changes (
git commit -m 'Add amazing feature') - Push to branch (
git push origin feature/amazing-feature) - Open a Pull Request
Please ensure:
- All tests pass (
dotnet test) - Code follows existing style
- New features have tests
- Update documentation as needed
MIT License - See LICENSE for details.
- Kademlia - Petar Maymounkov and David Mazières
- Tor Project - Onion routing inspiration
- BIP39 - Word list for human-readable passphrase generation
- libsodium - Cryptographic primitives
- .NET Community - Excellent tooling and libraries
Susurri - Whisper in the network
