Back to Projects

End-to-End Encrypted Messaging Application

A zero-knowledge messaging platform implementing the Signal Protocol for end-to-end encryption, with real-time delivery via Socket.IO and Redis, and cross-platform mobile support through Capacitor.

Vue 3 TypeScript Node.js Express Socket.IO PostgreSQL Redis libsignal-protocol Capacitor Pinia Argon2

Overview

A privacy-first messaging application where the server has zero knowledge of message content. Messages are encrypted on the sender’s device using the Signal Protocol and can only be decrypted by the intended recipient. The server relays opaque ciphertext and manages key distribution — it never has access to plaintext.

The application runs as a responsive web app with a WhatsApp-style interface, and is Capacitor-ready for native iOS and Android deployment with access to platform-level secure storage.

Signal Protocol Implementation

The encryption layer uses @privacyresearch/libsignal-protocol-typescript, implementing the full Signal Protocol stack:

  • X3DH key agreement — Extended Triple Diffie-Hellman for asynchronous session establishment, allowing messages to be sent to offline recipients using pre-key bundles
  • Double Ratchet Algorithm — forward secrecy and break-in recovery with every message exchange, meaning a compromised key cannot decrypt past or future messages
  • Pre-key management — clients generate and upload batches of one-time pre-keys to the server; senders consume these to initiate new sessions without the recipient being online
  • TOFU identity verification — Trust On First Use with automatic detection and security alerts when a contact’s identity key changes, flagging potential man-in-the-middle attacks

Key material is generated during registration and stored exclusively on the client device. On native platforms, Capacitor’s secure storage writes keys to the iOS Keychain or Android KeyStore. On web, keys are encrypted with AES-GCM before storage in IndexedDB.

Encrypted Backup and Recovery

Users can create encrypted backups of their private keys and message history, protected with AES-256-GCM derived from a user-chosen password via PBKDF2. Backups can be restored on a new device, re-establishing all encryption sessions without the server ever handling unencrypted key material.

A key recovery modal guides users through the restoration process if they lose access to their device.

Real-Time Message Relay

Socket.IO handles real-time transport with JWT authentication on the WebSocket handshake:

  • Message delivery — encrypted payloads are pushed immediately to connected recipients
  • Offline queuing — messages for disconnected users are queued in Redis with a thirty-day TTL, delivered when the recipient reconnects
  • Presence tracking — online/offline status tracked in Redis sets, broadcast to contacts on state change
  • Rate limiting — sixty messages per minute and ten connections per minute per user, preventing abuse
  • Horizontal scaling — Redis pub/sub adapter allows multiple Socket.IO server instances to share connections, with messages routed to the correct node regardless of which server a client is attached to

Backend Architecture

The Express server handles four concerns: authentication, message relay, key distribution, and contact management.

  • Authentication — Argon2id password hashing with JWT access tokens and refresh token rotation stored in PostgreSQL
  • Key distribution — pre-key bundle endpoints for session establishment, with the server storing only public keys
  • Message relay — encrypted payloads stored in PostgreSQL and forwarded via Socket.IO
  • Database — PostgreSQL with connection pooling, storing users, pre-keys, and session metadata (never plaintext messages)

Contact Exchange

New contacts are added through two methods, both designed to verify identity keys and prevent interception:

  • QR code scanning — the app generates a QR code containing the user’s username and public identity key; scanning with the device camera verifies the key matches the server’s record, preventing man-in-the-middle substitution
  • Contact codes — a manual alternative in the format @username#FINGERPRINT (last eight characters of the identity key), allowing verification when QR scanning is not practical

There is no user directory or search functionality — this is a deliberate privacy decision. Users must exchange contact information directly.

Frontend

The Vue 3 frontend uses the Composition API throughout, with Pinia stores managing conversation state, encryption sessions, and connection lifecycle:

  • Chat view — responsive layout with a conversation list sidebar and message panel, sliding to a single-column view on mobile
  • Message rendering — decrypted messages displayed with timestamps and encryption status indicators
  • Settings — backup creation and restoration, account information, and security warnings for key changes

The interface follows a WhatsApp-style pattern: conversation list with last-message previews on the left, active conversation on the right, with smooth transitions between views on mobile devices.