>_security · encryption · sec.teqh.org
How to share a password without leaving it in chat history forever
Why I built sec.teqh.org: the secret is encrypted right in the browser, the server stores only ciphertext, and the record deletes itself after a set time or number of views.
Server access, an API token, an admin panel password. Almost always they get sent over Telegram, Slack or email. The message arrives in a second and then stays there for years: in the chat history, on every device the other person owns, in backups and in message search. I built sec.teqh.org for exactly these cases.
What's wrong with sending a password over a messenger
The problem isn't that the messenger is unreliable. The secret needs to live for five minutes, and instead it lives forever. A year from now someone gets into the recipient's account, their laptop or a chat export, and gets your password along with everything else. Deleting the message on your side isn't enough: the other side still has a copy.
What you need is a handoff where the secret disappears on its own afterwards, and that nobody along the way can read, including the service it passes through.
How to use it
- Open sec.teqh.org and paste the text: a password, a token, a key, a piece of config. Any data up to 1,000,000 bytes.
- Come up with an encryption password. This isn't a password for the service, it's a separate phrase that only the recipient will know.
- Optionally set an open limit and a lifetime. By default the secret can be opened any number of times and the record lives for one day. The lifetime can be anywhere from 2 minutes to 8 days.
- Click
Encrypt. The service shows the secret's hash, and after 20 seconds the page redirects on its own to a link likesec.teqh.org/s/<hash>. - Send the link over one channel and the password over another. For example, the link in Slack and the password by voice on a call.
- The recipient opens the link, enters the password, clicks
Decryptand sees the text.
The interface is in English, but all that really matters in it is two fields: the text and the password.
What happens under the hood
The core idea: the server can't read what it stores. That's not a promise in a privacy policy, it's a consequence of how the encryption works.
- 01Sender's browserderives a key from the password and encrypts the text
- 02Networkcarries only the ciphertext, salt and iv
- 03Serverstores the ciphertext and has no key
- 04Recipient's browserrebuilds the key from the password and decrypts
- The key is derived from the password with
PBKDF2-SHA256at 600,000 iterations. Right in the tab, before anything goes over the network. - The text is encrypted with
AES-256-GCM. Every secret gets a fresh random salt (16 bytes) and iv (12 bytes). - Only the ciphertext, salt, iv, KDF parameters and the lifetime and limit settings go to the server. The password and the original text never leave the tab.
- The server doesn't store a password hash either. The cipher itself checks the password: with a wrong password the GCM integrity check fails and decryption doesn't happen. Any verification hash on the server would only weaken the scheme.
Which leads to a simple consequence: if the database leaks, all it contains is ciphertext. Without the passwords there's nothing to decrypt it with.
Why the link and the password go over different channels
The link is enough to download the ciphertext. The password is enough to decrypt it. On its own, neither one opens the secret. Whoever intercepted one channel got only half.
It also follows that all the protection rests on the password. Anyone with the link can brute-force the password on their own machine, without the server. 600,000 PBKDF2 iterations make every attempt expensive, but they don't make guessing impossible. So the password needs to be long: a phrase of several random words beats a short word with a digit. The interface shows how strong the password is.
The secret deletes itself
Every record has a lifetime and, if you set one, an open limit. Once the lifetime expires or the last allowed open has happened, the record is deleted for good. Access is cut off exactly at the deadline. Physical cleanup of the database may lag a little, but an expired record can no longer be opened.
A wrong password doesn't use up an open. Neither does viewing the page or downloading the ciphertext. An open counts only after a successful decryption: along with the text, the browser decrypts a random one-time token and sends it to the server. The server stores only the SHA-256 of that token and uses it to know the secret was actually opened.
Small things that matter
- The browser won't offer to save the password. The password field is built so the browser doesn't mistake it for a login form: nobody needs the password to someone else's secret sitting in their password manager.
- A messenger preview doesn't open anything. The secret page doesn't contain the ciphertext. The browser downloads it only after you enter the password and click
Decrypt. A messenger that builds a link preview reads nothing and doesn't use up opens. - The secret record has no IP address, User-Agent or other telemetry. Only the ciphertext, encryption parameters, dates and the open counter.
- Strict headers. Pages are served with
no-storeand a tight Content Security Policy: scripts only from the site itself, no inline JavaScript. - The decrypted text lives only in the tab. After a reload the page asks for the password again.
The limitations, honestly
- The password can't be recovered. It isn't on the server, so a lost password means lost data.
- The open limit controls the record on the server, but it doesn't revoke what has already been downloaded or copied. If the recipient copied the text, they have it from then on.
- A modified client can download the ciphertext and decrypt it without the server, without the open being counted. The limit protects against accidental and repeat opens, not against someone who already has both the link and the password.
- If the server's response gets lost after the open has been counted, the attempt may be used up without the text being shown.
- The encryption code comes from the server, like in any web app. You trust that the page serves honest JavaScript. This is a general limitation of in-browser encryption.
When it comes in handy
- Giving a contractor access to a server, a CMS or an ad account.
- Handing a colleague an API key or a bot token.
- Sending a client the login and password for a service you set up for them.
- Passing along a piece of config with secrets in it without leaving it in a shared chat.
Try it: sec.teqh.org. If you need similar secret sharing built into your product or processes, get in touch.