Home Notes Research CV Projects Outside of Math

Secure Messenger: Technical Summary

How the end-to-end encrypted messenger from my Spring 2026 independent study in applied cryptography is put together: the frontend, the backend, the encryption, and how it is tested and deployed.

Try it live View on GitHub Encryption Methods

At a Glance

LayerTechnologyRole
FrontendReact 19, Vite, JavaScriptThe chat interface, plus every key generation, encryption, and decryption step. Hosted on Vercel.
CryptographyWritten from scratch with JavaScript BigIntRSA, 3-prime RSA, and elliptic curve Diffie–Hellman, built on hand-written modular arithmetic and Miller–Rabin primality testing.
BackendPython, FastAPI, SQLAlchemy, AlembicA REST API that stores accounts, public keys, and ciphertext. Hosted on Render.
DatabasePostgreSQL (SQLite for local development)Users, public keys, conversations, and encrypted messages.
Passwordsbcrypt (via passlib)Passwords are stored only as salted bcrypt hashes.
TestingVitest, pytest, Cypress, ESLint20 cryptography unit tests, 27 backend API tests, and a browser-level end-to-end test.
CIGitHub ActionsRuns the backend tests, linting, the frontend tests, and a production build on every push and pull request.

How a Message Travels

The guiding rule is that plaintext and private keys never leave the browser. The server only ever stores and relays public keys and ciphertext.

Sender's browser

Writes the message, encrypts it with the recipient's public key (or the shared secret), and sends only the ciphertext.

Server

Stores the ciphertext and the users' public keys. It never sees plaintext or private keys.

Recipient's browser

Fetches the ciphertext and decrypts it with a private key that only it holds.

In more detail:

  1. Sign up and log in. The server stores a bcrypt hash of the password, never the password itself.
  2. Start a conversation and choose a method: RSA, 3-prime RSA, or elliptic curve Diffie–Hellman (ECDH). The method is fixed for the life of the conversation.
  3. Generate keys in the browser. For the RSA methods, each user has one key pair per method; the public half is uploaded and the private half stays in the browser. For ECDH, the server creates fresh public curve parameters for the conversation, each participant picks a secret number in the browser, and only the resulting public point is uploaded.
  4. Wait for the other person's key. Until both public keys exist, the conversation shows a waiting state and checks the server every three seconds.
  5. Encrypt and send. With RSA, only the recipient's private key can open a message, so the sender's browser also encrypts a second copy under the sender's own public key; that is what lets you read your own sent history. With ECDH, both sides derive the same shared secret, so a single ciphertext works for both.
  6. Decrypt on arrival. When a conversation loads, each message is decrypted locally as it is displayed.

The mathematics of each method, with small worked examples, is on the Encryption Methods page.

Frontend

The client is a single-page React app built with Vite, with components for logging in, registering, choosing who to talk to, the conversation view, and the message box. All of the cryptography lives in client/src/crypto/:

Because RSA can only encrypt numbers smaller than its modulus, the message box shows a running character count against the maximum length the current keys allow.

Backend

The server is a FastAPI application with a thin route layer over service modules, using SQLAlchemy for the database and Alembic for schema migrations. Its main endpoints are:

EndpointPurpose
POST /users, POST /loginCreate an account; log in and receive your stored public keys.
GET /users, GET /users/{username}Find people to message.
POST /users/{id}/keysUpload a public key for an RSA-family method.
POST /conversationsStart a conversation with a chosen method; for ECDH the server also generates the curve parameters.
POST /conversations/{id}/keysSubmit your public point for an ECDH conversation.
POST /messagesStore an encrypted message (plus the sender's own copy, for RSA).
GET /conversations/{a}/{b}Paginated message history between two users, still encrypted.

The data model has five tables: users, per-method public keys, conversations (with the chosen method and any curve parameters), per-conversation public keys, and messages, which hold only ciphertext. In production the API runs on Render with a managed PostgreSQL database; every deploy applies any pending migrations before the server starts, and cross-origin requests are limited to an allow-list of frontend addresses.

Testing and Deployment

Design Notes

This is a learning project. Every algorithm is implemented from scratch so that the mathematics is visible, which also means it is not hardened the way production messengers are:

A production version would use audited libraries, for example X25519 key agreement with AES-GCM encryption.