achenkunju.com Crypt
AES-256-GCM · zero storage

Encrypt anything.
You keep the key.

Crypt locks your text, hands you the key, and forgets it immediately. No account, no reset link. Send a one time link and it destroys itself the moment someone reads it.

Your text never leaves this tab. WebCrypto does the work locally.

Lock it with

A fresh 256 bit key, random every time. Strongest option, and you have to keep the key somewhere safe.

Your key

This is the only copy. Crypt never wrote it down, so nobody can send it to you again.

Locked token

The API

A Netlify Function used the wrong way round: as a one-shot cryptography oracle. It mints a key, encrypts with it, returns the key in the response body, and keeps nothing. No storage, no accounts, no server side secret. Tokens made here open in the app above, and the other way round.

POST/api/crypt/encrypt

Send text. Get a token plus the key that opens it.

# a key Crypt generates for you
curl -s https://achenkunju.com/api/crypt/encrypt \
  -H 'content-type: application/json' \
  -d '{"text":"meet me at the usual place"}'

# {
#   "token": "AC1.k.4f1c….9a2b…",
#   "key": "u7Hk…",        <- yours, and only yours
#   "stored": false
# }
POST/api/crypt/decrypt

Send the token and the key, or the token and the passphrase.

curl -s https://achenkunju.com/api/crypt/decrypt \
  -H 'content-type: application/json' \
  -d '{"token":"AC1.k.…","key":"u7Hk…"}'

# { "text": "meet me at the usual place", "mode": "key" }
POST/api/crypt/key

Just a fresh 256 bit key, nothing encrypted. Bring your own key to /encrypt with "key", or use "passphrase" instead and Crypt derives one with PBKDF2.

curl -s -X POST https://achenkunju.com/api/crypt/key

# { "key": "u7Hk…", "bits": 256, "alg": "AES-256-GCM" }
POST/api/crypt/once

One time messages, the one place Crypt stores anything. Send a token you already made and the plaintext and key never reach the server at all. Send text instead and the key comes back the usual way.

curl -s https://achenkunju.com/api/crypt/once \
  -H 'content-type: application/json' \
  -d '{"token":"AC1.k.…","expiresIn":3600}'

# {
#   "id": "y49ue9vtg3hv",
#   "url": "https://achenkunju.com/crypt/?o=y49ue9vtg3hv",
#   "stored": "ciphertext only",
#   "reads": 1
# }
GET/api/crypt/once/:idPOST/api/crypt/once/:id

GET peeks and changes nothing, so a link preview or a prefetch cannot spend someone else's message. POST burns it: the first caller gets the token, everyone after gets a 410. Two callers at the same instant resolve to exactly one winner.

# still there?
curl -s https://achenkunju.com/api/crypt/once/y49ue9vtg3hv
# { "waiting": true, "mode": "key", "expiresAt": "…" }

# open it, which destroys it
curl -s -X POST https://achenkunju.com/api/crypt/once/y49ue9vtg3hv
# { "token": "AC1.k.…", "burned": true }

# and again
# { "error": "That message is gone. It was a one time link." }
GET/api/crypt

The whole contract as JSON: endpoints, token format, limits.

Cipher
AES-256-GCM
Key derivation
PBKDF2-SHA256 ×310k
Token
AC1.k.iv.ct
Max text
64 KB per call
One time link
1 read, then gone
Stored
nothing, bar one time ciphertext
Recovery
none

How it works

Both engines speak the same token format, so nothing is locked to this page.

AC1.k.<iv>.<ciphertext>            a random 256 bit key
AC1.p.<salt>.<iv>.<ciphertext>     a passphrase, stretched with PBKDF2

every segment is base64url, no padding
12 byte IV, random per message, GCM tag appended by WebCrypto
the tag authenticates the token: edit one character and it refuses to open
instead of handing back garbage