Trust but verify
The security model
deadkey.net is built so that we cannot read your secret — not because we promise not to look, but because the pieces needed to reconstruct it are never in one place we control at the same time. This page describes the actual design: what's encrypted, what's hashed, what's held only briefly and why, and what we do not have the ability to see, even under compulsion. For shorter, practical answers, see the FAQ.
In short
We never see your secret or your trusted person's codebook. We store only encrypted coordinates and cryptographic hashes. Your registered contact details are encrypted except during an active 10-day countdown. A bot check runs on every state-changing form. And the 10-day window with an instant cancel link exists specifically so a trigger — mistaken or malicious — can't release anything without giving you a real chance to stop it first.
The zero-knowledge design
Every deadkey.net record is really two halves, never combined anywhere but a browser. Your trusted person holds a printed codebook. Our servers hold an encrypted sequence of coordinates into that codebook. Neither half means anything alone: the codebook without the coordinates is a page of random digits or characters; the coordinates without the codebook are a list of numbers that reveal, at most, how long your secret is. Only when your trusted person's browser combines both, at the moment of release, does your secret exist in one place again — briefly, in their browser, and never on our servers.
All of the cryptography that makes this possible — deriving keys, encrypting, decrypting — runs client-side, in code executing in your browser or your trusted person's, not on our infrastructure. The sections below describe exactly how.
The cryptography, briefly
You don't need to understand any of this to use the service, but we publish it because "trust us" isn't a security model. Every step below happens in your browser or your trusted person's, using their browser's own built-in cryptography (WebCrypto), not a third-party script.
- Key derivation — Argon2id. Your trusted person's registered email and their 8-character code, combined, are run through Argon2id (64 MiB of memory, 3 iterations), a memory-hard key-derivation function designed to make brute-forcing slow even with specialized hardware. The server never has enough of either value on its own to attempt this derivation, and a stolen database gives an attacker only ciphertext and hashes to start from — not a key.
- Key expansion — HKDF. A single Argon2id output is expanded, via HKDF, into several independent-purpose keys — one to authenticate a trigger request, one to protect the release data, one to wrap the record's internal key — each tied to its own fixed label. Compromising how one derived key is used does not expose what the others protect.
- Encryption — AES-256-GCM. The same authenticated-encryption standard used throughout modern web security. Every ciphertext is cryptographically bound to the specific record and field it belongs to, so a ciphertext copied from one context cannot be replayed into another.
- Hashing and verification — SHA-256 and HMAC-SHA-256. Used to create the one-way commitments and lookup identifiers described below. The server compares hashes using constant-time comparison, so timing differences can't leak information about a correct guess.
- Randomness. Every key, salt, codebook, and token is generated with a cryptographically secure random number generator — never a predictable source.
On the numbers: a trusted person's code is 8 characters drawn from a 32-character alphabet, roughly 40 bits of raw entropy. Combined with Argon2id's cost per attempt, brute-forcing the full code space against a stolen database is estimated at somewhere around 35,000 years on a single core, or about 3,500 years spread across ten cores. That is expensive enough to rule out casual attacks, but we say plainly that it is not designed to resist a well-resourced nation-state adversary with dedicated hardware — see "Threat model and accepted risks" below.
The codebook: how your secret is actually split
At setup, you protect a secret you already have — a PIN, an access code, or a password — not one deadkey.net generates for you.
In assisted mode, your browser builds a random codebook (100 positions for a numeric PIN, or one position per character for a password) and works out which codebook position corresponds to each digit or character of your secret. Numeric codebooks place each digit 0–9 at exactly 10 of the 100 positions; character codebooks place every character of your specific password at its own distinct random position, so repeated characters still get distinct coordinates and don't leak a repetition pattern. Either way, the codebook and your secret exist only in your browser's memory during setup — never sent anywhere.
In tin foil hat mode, you print a blank codebook template, fill in the 100 positions yourself by hand, look up your own secret against it, and type in only the resulting coordinates. Your secret and your codebook never touch a computer, at any point. In exchange, the site cannot check your handwritten codebook for mistakes — building it correctly is on you, with printable instructions provided.
Either mode produces the same split: your trusted person's printed Recovery Sheet carries the codebook; our server stores only the encrypted coordinates. A coordinate reveals nothing about which digit or character it maps to — only the printed codebook does that — and the encrypted coordinate sequence reveals nothing to us beyond its own length. Each codebook protects exactly one secret, one time: within a single codebook, a position is never assigned to two different digits or characters, so someone who sees only the coordinates cannot infer which parts of your secret repeat.
One honest caveat, worth repeating here: the codebook changes who can read your secret, not how easy it is to guess. A weak PIN is just as weak behind a codebook as it is on a sticky note. Choose what you're protecting with the same care you'd give it anywhere else.
What the server stores — and what it never sees
The table below is a plain description of what actually sits in our database, drawn directly from the technical design. Where we say "never," we mean the server has no key, no path, and no stored value that would ever let it decrypt that field — not merely a policy against looking.
| Data | What we store | Plaintext? |
|---|---|---|
| Your actual credentials (passwords, vault contents, etc.) | Nothing. These are never sent to us in the first place. | Never |
| Your secret (a PIN, code, or password) and the codebook that encodes it | Nothing readable, ever. Both are generated and used entirely in your browser (assisted mode) or entirely on paper (tin foil hat mode). | Never |
| The coordinate sequence (what the codebook produces) | Encrypted ciphertext. Decryptable only by your trusted person's browser, using their email and code. Reveals, at most, the length of your secret. | Never |
| Your release note (up to 140 characters) | Encrypted alongside the coordinate sequence. | Never |
| The cancel-link token | Not created until a countdown actually starts. We generate a one-time, high-entropy token and store only its SHA-256 hash — never the token itself — then email/text the token to you as a clickable link. Deleted once the countdown ends. | Never |
| Your registered email and phone number (normal, non-triggered state) | Encrypted under a key derived from your trusted person's own email and code — the same key family that protects the coordinate sequence. We hold ciphertext we cannot decrypt. | Never |
| Your registered email and phone number (during an active 10-day countdown only) | Held in plaintext only long enough to send the countdown reminders and the cancel link, and only after the submitted contact is checked against a commitment made at setup. Deleted immediately on cancel, on release, or at expiry. | Only during an active trigger |
| Your email at setup (one-time) | Submitted once, in plaintext, solely to send a one-time "your deadkey is set up" confirmation email. Never written to any database field, backup, or log — discarded from memory as soon as the send completes. | Only transiently, at setup |
| Release recipient contact and label | Encrypted, set at trigger time. Decrypted only during the trigger window to address the release email and personalize notifications. | Only during the trigger window |
| Your trusted person's email and code | Never stored individually. Only a one-way hash of the two combined (used as a lookup id) and a one-way hash of a derived authentication value are stored. | Never |
| Record state and expiration date | Plain values: active, triggered,
released, cancelled, or expired, plus the
date three years after creation. |
Yes — neither value is sensitive on its own |
Owner contact information: transient by design
Your registered email (and phone, if you added one) exist in plaintext on our servers in exactly two situations, both disclosed here on purpose.
First, briefly, once, at setup. Your browser submits your email in plaintext one time, solely so we can send a one-time confirmation that your record was actually created. No reply or click is required. We do not store this address in any field, table, backup, or log — it is held in memory only long enough to hand off to our email provider for that single send, then discarded. This is a deliberate, narrow relaxation of the zero-knowledge design, made for a practical reason: without it, you have no way to know your setup actually succeeded and your contact address was captured correctly, short of waiting for a real emergency to find out.
Second, for the duration of an active 10-day countdown. We need a readable address to actually send you the countdown-warning reminders and the cancel link. The moment that window closes — you cancel, the release completes, or the record expires — we delete the plaintext contact details immediately. Outside of those two moments, your contact information sits on our servers only as ciphertext we cannot open.
The trigger, the countdown, and the cancel link
To trigger the switch, a trusted person enters only the email and code printed on their
Recovery Sheet, at /trigger. Behind that simple form:
- The email and code are combined through Argon2id to derive keys that exist only for that request. The server never sees the raw code. It briefly receives one single-purpose value derived from it — over an encrypted connection, solely to check it against a stored hash from setup — then discards that value immediately: never logged, never written to the database, never reused. A fresh one-time challenge issued for that attempt also prevents someone from recording and replaying an old trigger request.
- Before starting the countdown, the server also checks the trusted person's decrypted copy of your registered contact details against a one-way commitment we recorded at setup. If they don't match — for example, if a trusted person tried to submit a different contact so your cancel warning wouldn't reach you — the server refuses to start the countdown at all. No countdown is armed, and nothing is stored. This check exists specifically to stop that kind of tampering with your warning channel.
- Only once that check passes does the server generate the one-time cancel-link token described above, storing only its hash, and begin sending the reminder email (and text, if you opted in) roughly every 3 days for the 10-day window.
- Clicking the cancel link is the entire cancellation process — no separate code or password. The link is single-use and tied to that specific countdown; once it's used, or once the 10 days pass, it stops working.
- If the countdown completes without a cancellation, the server sends your trusted person a one-time release link, valid for 48 hours, to retrieve the encrypted coordinates. Their browser decrypts them locally, using the same email and code, and they decode the secret by hand against their printed codebook.
The 10-day window cannot be shortened, and there is no operator override on it — it is the same fixed delay for every record.
Bot protection
Setup, trigger, and cancel — the three forms that change server state — run a Cloudflare Turnstile check before the server accepts the request, to distinguish human visitors from automated bots. Turnstile is a client-side challenge: your browser exchanges a short interaction with Cloudflare, which returns a token we validate server-side. Cloudflare can see that a request was made and the browser signals it uses for bot detection; it cannot see the values you entered in the form, your secret, or any record content.
Threat model and accepted risks
No design is risk-free. Here is what we've considered, in plain terms, including the risks we accept rather than pretend to have solved.
- Our entire database is stolen. An attacker gets ciphertext and hashes — Argon2id and AES-256-GCM stand between that and your actual secret.
- A trusted person's Recovery Sheet is photographed or copied without their knowledge. Whoever holds it could trigger the switch, but the 10-day window and the immediate notification to you exist specifically to catch this — you cancel, and nothing is released.
- A trusted person's paper is used after you have actually died or become unreachable. This is the system working as intended.
- A trusted person triggers the switch by mistake, or as a bad joke. Same protection as above: the 10-day window, the notification, and a one-click cancel.
- Any single trusted person, after a genuine release, holds enough to read your secret alone. By design, naming a second trusted person on the same record adds resilience — either can act if the other is unreachable — but it does not add cryptographic protection. This version does not split the secret further across trusted people (no cryptographic quorum); each one who completes a release holds everything needed to read it.
- A well-resourced attacker steals the database, already knows a trusted person's registered email, and tries to brute-force their 8-character code. Expensive, per the estimate above, but not designed to resist a nation-state-level adversary with dedicated hardware. If what you're protecting could plausibly draw that level of attention, this service alone is not sufficient, and we say so directly rather than implying otherwise.
- The operator (us) turns out to be dishonest. There is inherent residual trust in any hosted service: in principle, we could alter records or suppress a notification. Our mitigations are to publish exactly what we store (this page), keep as little as we possibly can, support more than one contact channel (email and, optionally, SMS) so a single suppressed message is less likely to go unnoticed, and intend to publish our source code so this design can be checked directly. We disclose this limitation openly rather than gloss over it.
- Someone tries to guess or enumerate record identifiers or cancel tokens. Both are high-entropy random values behind rate limits, and lookups return the same generic response whether or not something exists, so a failed guess doesn't confirm anything.
- Automated bots hammer the setup, trigger, or cancel forms. Covered above under bot protection.
Trust but verify
We use that phrase on our home page, and we mean both halves of it. The "trust" part is real: as long as any part of this system runs on servers we operate, there is some irreducible trust placed in us, and we've said so plainly in the threat model above rather than pretending otherwise. The "verify" part is why this page exists in this much detail — every claim above is a specific, checkable design decision, not a marketing promise. We intend to publish our source code so the design can be verified directly rather than taken on our word; until then, this page, the FAQ, and our support inbox are where we hold ourselves accountable to what's written here.
Read the FAQ for shorter, practical answers, or create a deadkey to see the setup flow yourself.