⚠️ Warning: This project is for educational purposes only. It is not intended to be used in production. ⚠️
- 🔒 End-to-end encryption using OpenPGP
- 🔌 Real-time communication using Socket.io
- 📦 Conversations persisted in MongoDB
- 🌙 Dark mode support
- 🥰 Emoji support in messages
- 📱 Fully responsive
- First you need to install the dependencies:
yarn
- Download Docker and run it.
- Run the following command to start the mongodb (wait for it to be ready for next step):
docker-compose up -d
- You need to generate a key pair in the root of /apps/api/ folder. You can use the following command to generate a key pair:
cd apps/api openssl genrsa -out private.pem 2048 openssl rsa -in private.pem -pubout -out public.pem
- Then you can run the server and frontend in the development mode:
yarn dev
- Open http://localhost:3000 to view it in the browser.
If you encounter login error and not able to login, please try to clear the cookies and local storage.
This turborepo uses Yarn as a package manager. It includes the following packages/apps:
api
: a Express.js app it is the backend of the applicationweb
: a Next.js app it is the frontend of the applicationeslint-config-custom
:eslint
configurations (includeseslint-config-next
andeslint-config-prettier
)tsconfig
:tsconfig.json
s used throughout the monorepointerfaces
: shared interfaces used throughout the monorepo
Each package/app is 100% TypeScript.
The user could first go to the register page “/register”, where they are required to fill in information including username, name, email and password. For the password in the user interface we added a strength meter, which will show the strength of the password, to make sure that the password is strong enough, to avoid brute force attack.
Then, we will use the Bcrypt library to hash passwords, which prevents or mitigates rainbow table attacks, brute force attacks and dictionary attacks.
After the user has been registered, server will issue an JWT token, which is signed by our own private key which is generated by command "openssl genrsa -out private.pem 2048", the token will be stored in the user cookie named "access-token".
Users can go to the login page "/login", where they need to fill in their username and password. After the user fills in the required information and clicks the login button, the information will be sent to the server. The server will first compare the password with the hashed password in the database using the Bcrypt comparison function .It will pre-salt and calculate the hash value and check if it matches the password stored in our database.
If the password is matched, the server will issue a JWT to the user where it is the and store in user cookie.
To implant the chat system, we used the open pgp library, which is a javascript implementation of the open pgp protocol. In addition, we used the socket.io library to enable real-time communication.
Before the user can send a message, the user will have to generate a PGP key pair in the browser. Where, if we detect the user didn't have a key pair in their local storage, we will prompt the user to generate a PGP key pair or to import a PGP key pair.
If the user wants to generate a new PGP key pair, the user must enter a passphrase for encrypting the private key, since the private key will be stored in the browser's local storage and adding the passphrase mitigates the risk of the private key being stolen through attacks such as XSS.
Then the user can click the "Create Key Pair" button, which will generate a armored format of PGP key pair in the browser using the open PGP library, were the key type is RSA, the key size is 4096, and the user id will be using the username and email of the user, also the private key will be encrypted with the passphrase the user has entered.
Then, the generated public key will be sent to the server via a POST request in route “/keys/public-key” with the public key as payload (Fig 17), along with the authorization token - JWT to verify the user identity and associate the public key with that user (decode user id from the JWT), where the server will store the public key in the database. Meanwhile, the generated key pair will be stored in the browser's local storage, and the stored key name will be "crypto-<user_id>" to support account switching.
Due to browser limitations, there is no persistent storage and key pairs are lost if the local storage is cleared or the browser is deleted. Therefore, to solve this problem, we provide a feature to export key pairs so that users can save the keys themselves and import them when they need to use them. Here, we will compress the key pair into .asc format and users can download the file and save it by themselves.
To secure the communication between the client and the server, we have made use of the open PGP library, which is a javascript implementation of the open PGP protocol, which will be explained in detail below.