Trust the code
A service about your family's crypto should not ask for blind trust. So we are building this one so that you can check it.
What we commit to
- The browser tools are plain, readable files. The quiz and the letter demo are ordinary HTML, CSS and JavaScript. There is no build step, no minifying, and no outside libraries. What you see with “view source” is what runs.
- No outside scripts, fonts, trackers, or analytics. The letter page also tells your browser to block all network connections (a Content Security Policy), so even a mistake could not send your text anywhere.
- MIT licensed. The code is released under the MIT license: you may use, copy, change and share it. The complete source is in the public repository on GitHub, and README.md explains the layout. A checksum list is below so you can confirm nothing changes quietly.
- Open formats, so you never depend on us. The locked letter format is documented below, and you can open one with standard tools.
What this does not mean
Open code is not the same as audited code. These tools have not had an independent security review. We will say so plainly until one is done and published. Please report problems through the contact in security.txt.
Check it yourself
- Open your browser's developer tools, go to the Network tab, and use the letter demo. You should see no requests after the page loads.
- Read
js/letter.jsandjs/quiz.js. They are short. - Compare the files to the SHA-256 checksum list.
- Read the tests (
node tests/run.js, Node 18 or later). They cover the secret splitting, the locking, and the check-in schedule.
The locked letter format (version 1)
The block between the BEGIN and END lines is Base64 text of a JSON object:
{"v":1,"alg":"AES-256-GCM","kdf":"PBKDF2-SHA256",
"iter":600000,"salt":"<base64, 16 bytes>",
"iv":"<base64, 12 bytes>","ct":"<base64 ciphertext + 16-byte tag>"}
The key is PBKDF2-HMAC-SHA256 of your passphrase (Unicode NFKC, UTF-8) with the salt and iteration count shown, 32 bytes long. The cipher is AES-256-GCM with the given 12-byte IV and the associated data wmc-letter-v1. To open one on your own computer with Python and the cryptography package:
import base64, json, sys, unicodedata, getpass
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
text = open(sys.argv[1]).read()
body = "".join(l for l in text.splitlines() if not l.startswith("-----"))
o = json.loads(base64.b64decode(body))
pw = unicodedata.normalize("NFKC", getpass.getpass()).encode()
key = PBKDF2HMAC(hashes.SHA256(), 32, base64.b64decode(o["salt"]), o["iter"]).derive(pw)
print(AESGCM(key).decrypt(base64.b64decode(o["iv"]), base64.b64decode(o["ct"]), b"wmc-letter-v1").decode())
Why not Argon2?
Argon2 is stronger against guessing machines, but browsers do not include it, and we do not want to ship a large third-party library for a first version. PBKDF2 with 600,000 or more rounds is what browsers support natively. A long passphrase matters more than any setting here.
Splitting the passphrase (Shamir)
The plan demo cuts the passphrase into shares with Shamir's Secret Sharing over the field GF(256) (the same one AES uses). Any k of the n shares rebuild it; fewer give no information at all. A share looks like WMC1.<plan id>.<k>.<n>.<x>.<data>.<checksum>. The checksum catches typing mistakes, and the plan id catches shares mixed up from two plans. The code is in js/shamir.js, and a separate Python script puts shares together without us. This is our own implementation of a well-known algorithm and has not been independently reviewed. For serious use, prefer an established standard such as SLIP-39 and a reviewed tool.
Files and checksums
checksums.txt lists a SHA-256 hash for each file served from this site.
We will never ask for your seed phrase or private keys, in any tool, on any page, now or later.