Hvisk

How it is secured

Short version: your browser encrypts the secret with a random key, the key goes into the link after the #, and that part of a link is never sent to any server. Hvisk stores random bytes it cannot read.

When you make a link

  1. Your browser makes a random 256-bit key and a random salt. If you set a password, it is stretched with PBKDF2-SHA256 (600 000 rounds) and mixed into the key with HKDF.
  2. The secret is encrypted with AES-256-GCM. GCM adds an authentication tag, so a changed ciphertext or a wrong key is detected instead of shown as garbage.
  3. The browser sends only the ciphertext, the iv and the salt. The server answers with a random id and a burn token.
  4. The link is /s/‹id›#‹key›. Everything after # is the fragment. Browsers never send the fragment in a request, so the key does not reach Hvisk, its logs, or Cloudflare.

When the link is opened

  1. The page first asks the server if the secret is still there. That does not delete anything, so a chat app that previews the link does no harm.
  2. When the receiver presses Reveal and destroy, one database statement deletes the row and returns it. Two people opening the same link at the same moment cannot both get it.
  3. The browser decrypts with the key from the fragment, then removes the key from the address bar so it does not stay in history.
  4. A password that does not fit can be tried again while the page is open. The server copy is already gone, so a reload shows nothing.

What the server stores

One row per secret, in Cloudflare D1:

ColumnWhat it is
idRandom 128-bit id, the part of the link before #
ciphertextAES-256-GCM output. Random bytes without the key
ivRandom 96-bit nonce for GCM
saltRandom 128-bit salt for HKDF and PBKDF2
has_passphraseYes or no, so the receive page can ask for it
burn_hashSHA-256 of the burn token, so the sender can delete early
created_at, expires_atUnix timestamps

Not stored, ever: the key, the plain text, the password, your IP address, your browser, cookies or sessions.

Someone with the database

Say the database leaks, or an admin looks:

  1. They can see how many secrets exist, how large they are, and when they expire. They can delete them.
  2. They cannot read a secret. The ciphertext is AES-256-GCM output under a key they do not have, and guessing a 256-bit key is not a realistic attack.
  3. They cannot brute-force a password. The password is mixed with the link key, so without the link there is nothing to test a guess against.

What Hvisk does not protect against

  1. Someone who gets the link before the receiver. The link is the key. Send it over a channel you trust, or add a password and send that another way.
  2. A compromised device or browser on either end. Encryption in the browser is only as good as the browser.
  3. The site itself serving different code. Hvisk is open source, but you still trust hvisk.dev to serve the code it says it does. Read the source or run it yourself if that matters to you.

Limits and housekeeping

  1. A secret can be up to 32 kB of text.
  2. Lifetimes run from 1 hour to 30 days. Expired secrets are refused on read and deleted every hour by a scheduled job.
  3. Create, peek, open and burn requests are rate limited per IP address.
  4. There is no tracking and there are no ads: no analytics script, no cookies, no third-party request of any kind. The Content-Security-Policy forbids them, so even a mistake could not add one.
  5. Every response carries a strict Content-Security-Policy with no inline scripts and no third-party sources, a no-referrer policy, no framing, and the receive pages are never cached.
  6. It runs on Cloudflare Workers with D1. The same build runs locally in workerd through wrangler, and in Docker.

Source on GitHub