Skip to content

Latest commit

 

History

History
278 lines (210 loc) · 11.7 KB

File metadata and controls

278 lines (210 loc) · 11.7 KB

Recovering a Keymaker file without Keymaker

Print this page and keep it with your backups.

If you are reading it because the Keymaker site is gone, the repository has disappeared, or you no longer trust the copy in front of you — that is what this page is for. Your data is not tied to the app that produced it.

Everything below uses one small Python file and two mainstream libraries. No browser, no Node, no npm.

One honest caveat about "no network". The decryption needs none — the script and your container are enough. Getting the two libraries onto the machine is the step that normally reaches a package index, and step 1 says how to do it without one. If you want a genuinely offline kit, download the wheels now, while you can, and store them beside the backup.

If nothing has gone wrong yet and you are reading to find out how the whole thing works, WALKTHROUGH.md is the illustrated version — a first encryption through to this page's procedure. This one is the emergency card: shorter, printable, and assuming the worst.


What you need

The encrypted file something.keym, or the text form starting keym2: or KEYM1:
The password Exactly as typed, including spaces and accents
The key file Only if one was used — the container will tell you
Python 3.10 or newer
Two libraries cryptography and argon2-cffi
The recovery scripts reference/keym2.py and reference/keym.py

Nothing else. In particular you do not need this repository's application, its dependencies, or any part of the JavaScript.

There are two scripts because there are two generations of the format. keym2.py reads everything Keymaker writes today: v3, the current default, and v2 before it. keym.py reads v1, which came first. All three stay readable forever; neither script reads the other's generation, and step 2 tells you which one you have. Keep both — an old backup needs the old script, and old backups are the ones most likely to need this page.

You do not need to work out whether you have v2 or v3. keym2.py reads both and says which it found; the distinction matters to the format, not to you.

If you enrolled a passkey, it will not help you here. A passkey is quick access, not a backup. It only answers at the website it was created on, so if you are reading this page it is already unavailable — and no script can stand in for it, because the secret lives in the authenticator rather than in the file. Use the password, or the recovery shares. Keymaker refuses to write a container that a passkey is the only way into, precisely so that this paragraph always has something to point you at.


Step 1 — Install the two libraries

pip install cryptography argon2-cffi

The versions these scripts are tested against are in requirements.txt, which ships beside them in the recovery kit:

pip install -r requirements.txt

Any recent version of either library should work — the container format does not depend on them, which the conformance suite checks by opening frozen fixtures under whatever version is installed. The pinned versions are simply the ones that were actually run.

Offline. Both libraries ship wheels you can download in advance:

pip download -r requirements.txt -d wheels        # now, while you have a network
pip install --no-index --find-links wheels -r requirements.txt   # later, when you do not

Storing that wheels folder beside your backup is the difference between a procedure that needs no network and one that only claims to. The recovery kit carries the scripts and the requirements file; it does not carry the wheels, because they are platform-specific and would have to be chosen for a machine nobody can predict.

Step 2 — Find out which version you have

If your backup is text, the first six characters say it outright:

Starts with Version Script
keym2: v3 or v2 keym2.py
KEYM1: v1 keym.py

keym2: covers both v3 and v2 on purpose. The prefix names the generation, not the revision, so a backup written today and one written before v3 existed look identical here — and both open with the same script. If you want to know which one you are holding, keym2.py inspect says so.

Note the case. keym2: and KEYM1: differ by one letter and it is deliberate — see Why the prefixes look almost the same below.

If your backup is a file, ask each script in turn. Neither needs a password and neither can damage the file:

python3 keym2.py inspect --in backup.keym     # v2
python3 keym.py  inspect --in backup.keym     # v1

The right one prints a description. The wrong one refuses. That is the whole test — you do not need a hex editor.

Step 3 — Read what the container says about itself

For a v3 container — what the app writes today:

KEYM v3
  cipher      AES-256-GCM
  slots       1
  slot 0       type 0x00 (passphrase)
    kdf         Argon2id, t=3 m=65536KiB p=4
    key file    not used
    salt        0f1e2d3c...
  chunks      1 (75 plaintext bytes)

For a v1 container:

format          KEYM v1
key derivation  Argon2id
                time cost 3, memory 64 MiB, parallelism 4
cipher          AES-256-GCM then ChaCha20-Poly1305 (chained)
key file        not used
salt            32 bytes
nonces          2 x 12 bytes
header          71 bytes (authenticated as AAD)
ciphertext      75 bytes

Either way this needs no password. The container states its own parameters — that is the point of the format — so you can confirm you have the right file, and whether a key file is required, before doing anything else.

Those values are authenticated: if decryption later succeeds, they were not tampered with. Until then, treat them as claims the file makes about itself.

About slots. A v2 container can hold up to eight ways of unlocking the same data, and any one of them opens it. Containers written by the app have exactly one — your password. If yours says more, any of the secrets listed will work, and you only need one of them.

Step 4 — Decrypt

python3 keym2.py decrypt --in backup.keym --out recovered.txt     # v2
python3 keym.py  decrypt --in backup.keym --out recovered.txt     # v1

You will be prompted for the password. Add --key-file mykey.bin if step 3 reported one was required. Omit --out to print to the terminal.

Do not pass --password on the command line unless you have a reason to. It lands in your shell history and is visible to every other user on the machine while the key derivation runs — seconds, for Argon2id.


If the web app said the backup was too large

That limit belongs to the web app, not to your backup and not to the format. A browser tab has to hold the container and the recovered file in memory at once, so the app stops at 100 MB. keym2.py does not: it has no size limit, and the steps above are the whole procedure regardless of how big the file is.

Measured on an ordinary laptop, a 150 MiB backup decrypts in about two seconds and needs roughly 640 MB of RAM while it runs. Larger files scale from there — budget several times the file size in memory, and use a machine with room for it rather than the smallest one to hand.

Nothing else changes. Same command, same password, same output.

If it does not work

The error is deliberately vague: "decryption failed" covers a wrong password, a wrong key file, and a corrupted file alike. That is intentional — a tool that distinguished them would help an attacker who has your file but not your password. It does mean you have to diagnose it yourself.

Work through these in order:

  1. Are you running the right script? A v1 file in keym2.py, or the reverse, fails exactly like a wrong password. Re-run step 2.
  2. Is a key file expected? Run inspect. If it says required and you do not have it, the password alone will never work.
  3. Is the password exactly right? Case, spaces, and trailing newlines all matter. If you stored it in a password manager, copy rather than retype.
  4. Unicode in the password? Keymaker normalises to NFC before deriving the key, and so do both scripts, so a password containing accented characters works across platforms. A password typed on a different keyboard layout may still differ in ways that look identical on screen.
  5. Is the file intact? Compare its size against the copy you originally made. A truncated or re-encoded file — one that passed through a text editor, or a chat app that "helpfully" fixed its encoding — will fail authentication even with the correct password.
  6. Text form pasted correctly? The prefix and every character after it must be present. Line breaks are fine; missing characters are not.

If inspect fails under both scripts, the file is not a KEYM container — check you have the right one, and that it was not renamed from something else.

Why the prefixes look almost the same

KEYM1: and keym2: differ by more than the digit, and the lowercase k is the part that matters. A binary container starts with the four bytes KEYM, so v1's text prefix is indistinguishable from a raw container in its first four bytes — software that checked only those got it wrong, and did so confusingly. keym2: starts with lowercase k, which no binary container ever does, so one byte separates the two encodings.

The consequence for you: keym2: is case-sensitive. A backup rewritten as KEYM2: by something that "tidied" it will not be recognised. Change it back to lowercase and it will.


Why this works, and why it should keep working

Keymaker's formats are specified byte by byte — v1 in FORMAT.md, v2 in FORMAT-V2-DESIGN.md. Each recovery script was written from its specification alone, without reference to the application's source. Both are tested against the real implementation on every push — v2 by comparing the bytes the two produce, not merely by checking they can read each other — so they cannot silently diverge.

That gives you two independent implementations of each documented format. If one is unavailable or untrustworthy, the other decrypts your data — and if both vanished, the specification is enough to write a third.

This is the property that matters for a backup you may not open for a decade: your data depends on a documented format, not on a particular program continuing to exist.


The primitives, for anyone writing their own

Shared by both versions:

Layer Standard
Key derivation Argon2id (RFC 9106) or PBKDF2-HMAC-SHA-256 (RFC 8018)
Ciphers AES-256-GCM (NIST SP 800-38D), ChaCha20-Poly1305 (RFC 8439)
Subkey split, chained mode HKDF-SHA-256 (RFC 5869)
Password encoding NFC-normalised, UTF-8

Where they differ:

v1 v2
HKDF labels keymaker-aes, keymaker-chacha keymaker-v2-aes, keymaker-v2-chacha; the slot wrap uses keymaker-v2-slot-aes and keymaker-v2-slot-chacha
Key file Raw bytes appended after the password bytes SHA-256 of "keymaker.v2.keyfile" ‖ bytes, then length-prefixed alongside the password
Payload key Derived from the password directly A random 32-byte master key, carried in each slot sealed under a key derived from that slot's secret
Payload One AEAD invocation over the whole plaintext 1 MiB chunks, counter nonces, the last chunk flagged
Authentication The whole header is AAD on every AEAD layer The 8-byte core header is AAD for every chunk; each slot's wrap adds its own 48-byte record

All standard, all implementable from public specifications.


Keymaker is a fork of IttyBitz, GPL-3. This page and the recovery scripts may be copied freely.