When it came time to implement 2FA in my open-source project Mentat, I wanted to try something a little different. As an end-to-end encrypted chat app, asymmetric encryption was already an important aspect of the platform, and was easy enough to implement using OpenPGP.js. When a user signs up for the platform, a keypair is generated and the public key is saved in the database as part of that user's identity. But an issue arises when the user wants to sign into a different device: how can the user's private key be transmitted in a way that doesn't reveal their credentials to the server? As it turns out, I was able to solve this issue and add a second authentication factor in the same step.

Signing in on Mentat starts with the user inputting their email and password on a new device. When this occurs, the device will generate a brand new keypair and send the public key to the server. The server will check if this public key matches the one stored for the user, and this check will fail because a keypair has already been added as part of the signup process. The user is then shown a wall explaining that the device needs to be authenticated:

Auth request

Meanwhile, a request will be sent to all of the user's previously-authenticated devices. The request will contain the public key of the new device and will ask if this request should be accepted:

Auth request

If the request is accepted, the authenticated device will encrypt the user's private key using the new device's public key and transmit this packet to the new device. The new device will decrypt the user private key and replace its keypair with the valid keys, thus authenticating this device and receiving the user keypair at the same time. With the valid keys, the new device is able to decrypt group chat messages received from the server and send new messages under a single identity between devices.

Some work still needs to be done to increase the security of this process. For example, the server (or another device) should verify that the new device truly owns the private key before lifting the 2FA gate on the new device. This can be achieved by simply signing a message and having this signature verified. Additionally, the request could list some details, including model or OS, of the new device requesting access, in case a fraudulent request was sent.

Feedback or security concerns? Let me know in the comments. Wanna try the platform? Sign up here!