My password sharing mini-project
I hate passwords
So much. They’re inefficient, unsafe, and a lot of admin; both as a developer, and as an end-user.
I’ll break down exactly why I hate passwords in another post, but for the purposes of this writeup we can assume the following:
- They are hard to remember.
- They are admin to manage.
- They are difficult to share securely.
This post is going to focus on sharing passwords securely.
Sharing passwords is a major security vulnerability
I’ve employed a few contractors over the past 3 years to work on JustSketchMe. This is both tech and marketing related, so there's a big spectrum of tech-competence. I run into the problem of securely granting access to services to these contractors regularly. I don't feel comfortable sending passwords or API keys via email, Slack, or WhatsApp.
For the rest of this writeup I'll be referring to these important pieces of secret information (passwords, API keys, 2FA recovery codes, etc) as "password", for brevity sake.
The issue with sending a password as plain text is fairly obvious. Firstly, any interception of these messages (like a compromised mail server) allow a malicious actor to gain access to the password. This is fairly unlikely, though, as most messaging goes through fairly secure servers, or are end-to-end encrypted.
The real danger comes with the compromising of the sender or receiver’s mailbox or messaging profile. This could be as complex as someone hacking their email account, or as simple as them sharing their screen while on a Zoom call with the password in view. The duration of the vulnerability is, theoretically, the length of time in which the password is stored in their mailbox.
Finding a solution
The solution for this problem has 3 distinct requirements:
- The password should not be stored on any server (even if it's encrypted).
- The solution should be as friction-free as possible as it is competing with sending the password via WhatsApp.
- It should be useable by both technical and non-technical people.
- It should not require an account by either the sender or receiver.
- The password should only be accessible for a short period of time.
With that in mind we can conceptualise a service which allows a sender to enter the password which is encrypted into an expiring package. This package needs to be transmitted to the receiver for decryption, preferably without having to trust an intermediary service.
Building a little project
I started out by building a simple pen which takes in a password and encrypts it in-browser with a time-based token and a passphrase. The encrypted package is appended as a parameter to a shareable url which is then sent to the receiver who enters the passphrase and, assuming the time-based token hasn’t expired, decrypts the password.
I shared this on the ZATech community, which is an awesome community of developers in South Africa (where I live). It was pointed out that while this was secure-ish, there were a few fundamental issues with it:
- The sender has to always send the encrypted package separately from the passphrase, which increases user-friction.
- URL parameters are sent to the server, meaning if the service owner (me) was a malicious actor, they would have access to all the encrypted packages in the server logs.
- If a malicious actor gained access to a user’s mailbox and they hadn’t sent or received the encrypted package and passphrase separately, the actor would have access to the 3 bits of information necessary to decrypt the shared secret:
- The encrypted package.
- The passphrase.
- The timestamp (which can be reverse-engineered as the time-based token is generated in-browser).
Some work was needed in order to make this a secure system that people would actually use.
The final result
This went through a few iterations, and I’m quite happy with the final result. You’ll note that there is no longer a passphrase, and that the tokens are generated on a server and not in-browser anymore.
- The password is encrypted in-browser using 2 related tokens issued by a token server. These tokens expire after 10 minutes. The first token is a timestamp and the second is a random string.
- A shareable link is generated with the encrypted package as a hash parameter (so it isn't sent to any server) and the first of the two tokens is appended as a query.
- Once the link is shared with the recipient, they open it and a request is made to the token server with the token from the url parameter. This retrieves the second token from the token server and the token pair is used to decrypt the password.
- When the second token is requested, both tokens are deleted from the server (meaning the encrypted package can only be decrypted once).
This allows us to share a secret which can be read only once by the receiver and has a fixed expiry time. This has the added benefit of not needing to trust the service owner (me) as the sender and receiver can open your browser's network tab and monitor what information is transferred.
While there is no such thing as perfect security, every step of friction added to an attacker's workflow is a step in the right direction. This is not a perfect system, but it is better than sending your passwords as messages.
Stay safe out there folks :)