Anonymous. Encrypted. Ephemeral. A real-time, end-to-end encrypted chat platform built with Django and browser-native RSA/AES cryptography.
Telepathy is a secure, anonymous chat application where privacy is guaranteed by design. Messages are encrypted entirely on the client side using the Web Crypto API before they ever reach the server — meaning the server never sees plaintext. Even if the database were compromised, no readable message content would be exposed.
Two parties join a chat room via a shared 4-digit PIN. Once both connect, they exchange messages secured by hybrid RSA + AES-GCM encryption and verified with RSA-PSS digital signatures. Every encryption step is visible to the user in real-time through a send progress modal and per-message verification badges.
| Feature | Description |
|---|---|
| 🔑 Client-Side Key Generation | Two RSA-2048 key pairs (encryption + signing) are generated in the browser at registration; private keys never leave the client |
| 🔒 Hybrid Encryption | AES-256-GCM encrypts the message body; RSA-OAEP wraps the AES key for both sender and receiver |
| ✍️ Digital Signatures | Every message is signed with RSA-PSS — the receiver sees a clickable ✓ Verified badge with full crypto details |
| 📊 Send Progress Modal | A 6-step animated progress bar shows each encryption operation in real-time when sending a message |
| 💾 Encrypted-at-Rest | Only ciphertext is stored in the database — decryption happens exclusively in the browser |
| 📌 PIN-Based Chat Rooms | No email required; create or join a room using a 4-digit PIN |
| 🛡️ Two-Factor Authentication | Optional TOTP 2FA via QR code and authenticator app (e.g. Google Authenticator) |
| 🚫 Ephemeral History | Message history is automatically deleted when a user leaves the chat |
| 🔐 Token + Session Auth | DRF Token authentication for API calls; Django sessions for page access; server-side logout invalidates tokens |
| 🎨 Premium UI | Dark glassmorphism theme with animated gradients, floating particles, and smooth transitions |
pentour/
├── chat/ # Main Django application
│ ├── models.py # User, Chat, Message models
│ ├── views.py # REST API views (register, login, send/get messages, etc.)
│ ├── serializers.py # DRF serializers for User and Message
│ ├── admin.py # Django admin registrations
│ ├── urls.py # URL routing for the chat app
│ └── templates/
│ ├── index.html # Landing page (glassmorphism hero + particles)
│ ├── auth.html # Register + Login (crypto log panel)
│ ├── usermenu.html # Dashboard (create/join chat, PIN modal)
│ ├── chatbox.html # Chat interface (send modal, verification badges)
│ └── chat/
│ └── 2fa_setup.html # TOTP two-factor authentication setup
├── pc/ # Django project configuration
│ ├── settings.py # App settings (env-driven secrets, DB, security)
│ ├── urls.py # Root URL configuration
│ └── asgi.py # ASGI entrypoint
├── manage.py
└── requirements.txt
SENDER (Browser A) SERVER RECEIVER (Browser B)
────────────────── ────── ────────────────────
1. Generate random AES-256 key
2. Encrypt message with AES-GCM
3. Wrap AES key with Receiver's Stores ONLY:
RSA-OAEP public key ───► • AES-GCM ciphertext
4. Wrap AES key with own • Wrapped keys (2x)
RSA-OAEP public key • Nonce, tag, signature
5. Sign plaintext with RSA-PSS
6. POST all fields to API
7. GET encrypted messages
8. Unwrap AES key (RSA-OAEP)
9. Decrypt message (AES-GCM)
10. Verify signature (RSA-PSS)
11. Display ✓ Verified badge
| Layer | Technology |
|---|---|
| Backend | Django 5.1, Django REST Framework |
| Database | PostgreSQL (UUID-indexed messages) |
| Client Crypto | Web Crypto API (RSA-OAEP, RSA-PSS, AES-256-GCM) |
| Server Crypto | cryptography library (PEM key validation) |
| 2FA | pyotp (TOTP) + qrcode |
| Frontend | Vanilla HTML/CSS/JS with glassmorphism design system |
Prerequisites: Python 3.10+, PostgreSQL 13+, Git
git clone https://github.com/Rarees404/pentour.git
cd pentourbrew install postgresql@17
brew services start postgresql@17sudo apt install postgresql postgresql-contrib
sudo service postgresql startDownload from postgresql.org/download.
# Open a PostgreSQL shell
psql -d postgres # macOS/Linux
psql -U postgres # WindowsThen run:
CREATE ROLE myproject_user
WITH LOGIN PASSWORD 'mysecretpassword'
CREATEDB CREATEROLE INHERIT;
CREATE DATABASE my_database
OWNER = myproject_user
ENCODING = 'UTF8'
TEMPLATE = template0;
\qTip (macOS): If
psqlasks for a password and you don't know it, change/opt/homebrew/var/postgresql@17/pg_hba.conf— replacemd5orscram-sha-256withtrustin all local lines, then runbrew services restart postgresql@17.
python3 -m venv venv
source venv/bin/activate # macOS/Linux
# venv\Scripts\activate # Windows
pip install --upgrade pip
pip install -r requirements.txtpython manage.py migrate
python manage.py runserverOpen http://127.0.0.1:8000/ in your browser.
⚠️ Important: You MUST use two different browsers (e.g. Chrome + Firefox, or two separate incognito/private windows). Each browser needs its ownlocalStorageto store separate user keys and tokens.
- Browser A →
http://127.0.0.1:8000/chat/→ Register asalice→ Login → Create Chat → note the 4-digit PIN - Browser B →
http://127.0.0.1:8000/chat/→ Register asbob→ Login → Join Chat → enter Alice's PIN - Both browsers show "Waiting for partner…" briefly, then the chat opens
- Send a message — a progress modal appears showing all 6 encryption steps in real-time:
- Generating AES-256 session key
- Encrypting message (AES-256-GCM)
- Wrapping key for partner (RSA-OAEP)
- Wrapping key for self (RSA-OAEP)
- Signing message (RSA-PSS)
- Sending encrypted payload
- Receiver sees the message with a ✓ Verified badge — click it to see the individual crypto verification steps (key unwrap, decrypt, signature verify)
| Method | Endpoint | Description | Auth |
|---|---|---|---|
POST |
/chat/register/ |
Create account + upload both public keys | None |
POST |
/chat/login/ |
Authenticate and receive a DRF token | None |
POST |
/chat/logout/ |
Invalidate token and clear server session | Token |
GET |
/chat/usermenu/ |
User dashboard | Session |
POST |
/chat/create-chat/ |
Generate a new 4-digit chat PIN | Token |
POST |
/chat/join-chat/ |
Join an existing chat by PIN | Token |
GET |
/chat/check-chat/ |
Verify chat room exists and participants | Token |
POST |
/chat/send-message/<chat_id>/ |
Send an encrypted message | Token |
GET |
/chat/get-messages/<chat_id>/ |
Retrieve encrypted messages | Token |
GET |
/chat/get-public-key/<user_id>/ |
Fetch user's encryption + signing public keys | Token |
POST |
/chat/upload-public-key/ |
Upload/update caller's public keys | Token |
POST |
/chat/leave-chat/ |
Leave chat (deletes message history) | Token |
GET/POST |
/chat/2fa/setup/ |
Set up TOTP two-factor authentication | Session |
| Field | Type | Description |
|---|---|---|
totp_secret |
CharField |
TOTP secret for 2FA (never exposed via API) |
is_2fa_enabled |
BooleanField |
Whether 2FA is active |
public_key |
TextField |
RSA-OAEP public key for encryption (PEM) |
signing_public_key |
TextField |
RSA-PSS public key for signature verification (PEM) |
| Field | Type | Description |
|---|---|---|
pin |
CharField(4) |
Unique 4-digit room code |
user1 |
FK(User) |
Chat creator |
user2 |
FK(User) |
Chat joiner |
is_active |
BooleanField |
Whether the room is active |
| Field | Type | Description |
|---|---|---|
id |
UUIDField |
UUID primary key |
sender / receiver |
FK(User) |
Message parties |
encrypted_text |
TextField |
AES-GCM ciphertext (Base64) |
encrypted_symmetric_key |
TextField |
AES key wrapped with receiver's RSA public key |
sender_encrypted_symmetric_key |
TextField |
AES key wrapped with sender's own RSA public key |
aes_nonce / aes_tag |
TextField |
AES-GCM IV and authentication tag |
signature |
TextField |
RSA-PSS digital signature (Base64) |
When deploying to production, set these environment variables instead of editing source code:
| Variable | Description | Default |
|---|---|---|
DJANGO_SECRET_KEY |
Django secret key | Insecure dev fallback |
DJANGO_DEBUG |
Set to false for production |
true |
DJANGO_SECURE |
Set to true to enable HTTPS-only cookies, HSTS |
false |
DB_NAME |
PostgreSQL database name | my_database |
DB_USER |
PostgreSQL user | myproject_user |
DB_PASSWORD |
PostgreSQL password | mysecretpassword |
DB_HOST |
Database host | localhost |
DB_PORT |
Database port | 5432 |
Press Ctrl+C in the terminal running manage.py runserver.
# macOS
brew services stop postgresql@17
# Linux
sudo service postgresql stoppsql -d postgres -c "DROP DATABASE IF EXISTS my_database;"
psql -d postgres -c "DROP ROLE IF EXISTS myproject_user;"deactivate # exit the venv first
rm -rf venv/source venv/bin/activate
python manage.py flush --no-inputContributions are welcome! Please read ContributorGuide.md for details on the project structure, where to place static files, and Git workflow conventions.
For bugs, questions, or setup issues:
📧 r.boghean@student.maastrichuniversity.nl
This project is for academic use. Please contact the author before using it in production or redistributing.