Files
relicario/README.md
adlee-was-taken 1591fc7825 docs: re-lead positioning on the two-factor-KDF thesis; document the key-file second factor
README: open with the two-independent-secrets-into-KDF thesis; reframe the
second factor as pluggable (reference image or key file); keep the stego
deep-dive as a distinctive option; update all scenario tables, recovery section,
env-var hint, and roadmap line to use "second-factor secret" language.

docs/CRYPTO.md: add "Pluggable second-factor containers" subsection explaining
that the 32-byte input to Argon2id is identical regardless of container;
reference-image / key-file / recovery-QR are interchangeable carriers.
Update org-vs-personal comparison table and entropy labels.

docs/FORMATS.md: document the .relkey armor format (relicario-keyfile-v1 +
base64 line) and the params.json second_factor field ("image"|"keyfile",
absent means "image" for back-compat). Cite crates/relicario-core/src/keyfile.rs
keyfile_encode/keyfile_decode with TODO-pin comments. Update KDF input formula
from image_secret to second_factor_secret.

DESIGN.md: split secrets-map "Reference image bytes" row to distinguish image
and key-file containers; add key-file row noting keyfileBase64 in
chrome.storage.local has the same in-the-clear posture as imageBase64.
Update vault-layout note and KDF conventions row.

docs/SECURITY.md: add "Second-factor storage posture" subsection explicitly
stating .relkey and keyfileBase64 are the second factor in the clear — same
posture as the reference JPEG / imageBase64, not weaker, protected by the
passphrase invariant. Add RELICARIO_KEYFILE to the env-var trust surface table.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014THSV6cA4Gxa7bxFfisHBB
2026-06-25 21:24:06 -04:00

12 KiB

Relicario

Relicario

Audience: users + evaluators. This doc owns the pitch, security-model summary, quick-start commands, reference-image explanation, recovery-QR overview, and roadmap teaser. Goes no deeper — for the system tour see DESIGN.md, for crypto see docs/CRYPTO.md.

A git-backed, self-hostable password manager. The security thesis: two independent secrets feed the Argon2id KDF, the vault server holds only opaque ciphertext, and every mutation is a git commit — a built-in audit log. Compromise of either factor alone is insufficient to decrypt the vault.

The second factor (something you have) is pluggable: at vault creation you choose a reference image (a JPEG that carries a 32-byte secret hidden in its DCT coefficients via steganography) or a key file (the same 32-byte secret in a plain armored text file). Either container enters the KDF identically; the choice is how you store and transport it.

The server only ever sees opaque ciphertext. There is nothing else going on. This README is the security proof.

How it works

Your passphrase (something you know)
        +
Your second factor — reference image or key file (something you have)
        |
        v
   [ Argon2id KDF ]  -->  master_key  -->  [ XChaCha20-Poly1305 ]  -->  encrypted vault
        ^                                                                      |
        |                                                                      v
   Never leaves                                                          Stored in git
   your device                                                         (opaque ciphertext)

At vault creation you choose a second-factor container. The default is a reference image: Relicario generates a random 32-byte secret and embeds it into a carrier JPEG using DCT steganography. The resulting photo lives on your devices — and optionally as a "dead drop" on social media, since the embedding survives JPEG re-encoding and mild cropping. The alternative is a key file: the same 32-byte secret written to a plain armored .relkey file, suited to users who prefer copying a file to a USB drive rather than distributing a photo.

To unlock the vault, you provide your passphrase and your second-factor container (reference image or key file). The client extracts the 32-byte secret, concatenates it with your passphrase, and runs Argon2id to derive the master key. The KDF input is byte-for-byte identical regardless of which container you chose. Everything else follows from there.

Security model

What the server sees

A git repository containing:

  • manifest.enc — opaque binary blob
  • items/*.enc — more opaque binary blobs
  • attachments/<item-id>/*.enc — encrypted attachment blobs
  • settings.enc — encrypted vault settings
  • .relicario/salt — a random 32-byte value (not secret)
  • .relicario/params.json — Argon2id parameters (not secret)
  • .relicario/devices.json — authorized device public keys

That's it. No plaintext. No metadata about what's inside. No keys, no passphrases, no second-factor containers (no reference images, no key files).

What an attacker needs

Scenario Has Needs Result
Server breach Encrypted vault + salt Passphrase AND second-factor secret 256+ bits of entropy. Infeasible.
Server breach + stolen second factor Vault + second-factor secret Passphrase Passphrase entropy through Argon2id. 4 diceware words = ~7 million years.
Shoulder-surfed passphrase Passphrase Second-factor secret 256 bits. Infeasible.
Stolen device (image or key file + vault) Second factor + vault Passphrase Argon2id brute-force. Strong passphrase = safe.

No single point of failure. The two-factor design means the passphrase alone can't decrypt the vault, and the image alone can't decrypt the vault.

Compared to

Server breach entropy KDF factors
LastPass ~40-60 bits (master password only) 1
Bitwarden ~40-60 bits (master password only) 1
1Password password + 128-bit Secret Key 2
Relicario password + 256-bit second-factor secret (image or key file) 2

What we don't protect against

  • A compromised device with active malware. No software password manager can.
  • Weak passphrases with a stolen second factor (reference image or key file). Use 4+ diceware words.
  • Rubber-hose cryptanalysis.

Quick start

# Build from source
cargo build --release

# Create a vault with a reference image (default — stego second factor)
relicario init --image vacation.jpg --output reference.jpg
# Or use a plain key file as the second factor instead
relicario init --key-file /path/to/secret.relkey

# Add a credential
relicario add

# Retrieve it
relicario get github

# List everything
relicario list

# Sync with your git remote
relicario sync

# Pack the vault into a single encrypted backup file
relicario backup export -o vault.relbak

# Print a recovery QR for your second-factor secret (see "Recovery" below)
relicario recovery-qr generate

# Generate a random password
relicario generate -l 32

Environment variable

Set RELICARIO_IMAGE=/path/to/reference.jpg to avoid being prompted for the reference-image path on every command. If you use a key file instead, set RELICARIO_KEYFILE=/path/to/secret.relkey.

The second factor: reference image and key file

Relicario's second factor is a 32-byte secret. The container for that secret is your choice at vault creation.

Reference image (default): The JPEG carrier is generated during relicario init. It looks like a normal photo — because it is one. The 32-byte secret is embedded in the DCT coefficients of the luminance channel using Quantization Index Modulation, with heavy redundancy and Reed-Solomon-style majority voting across multiple copies.

The steganographic embedding survives:

  • JPEG recompression (tested down to quality 85)
  • Up to ~10-15% cropping from any edge
  • Social media re-encoding (Instagram, Discord, etc.)

This means your reference image can live on your Instagram, your personal website, or anywhere else — a "dead drop" approach. It is useless without your passphrase; the stego embedding is its transport mechanism.

Key file (alternative): A plain armored .relkey file holding the same 32-byte secret in base64 — no steganography. Suited to users who prefer copying a small file to a USB drive, a hardware security module, or an offline storage medium. Wire format: see docs/FORMATS.md.

Recovery: what if I lose my second factor?

Without your second-factor container (reference image or key file), the vault is undecryptable — that's the security model. But it also makes a lost or corrupted container a single point of failure.

The mitigation is the recovery QR: a printable QR code that wraps the 32-byte second-factor secret behind a separate recovery passphrase you choose. It applies regardless of which container you chose at vault creation. If you ever lose access to your reference image or key file, scan or transcribe the QR, provide the recovery passphrase, and recover the second-factor secret. Combined with your normal vault passphrase, this restores access to the vault.

# Print a recovery QR (after the vault is unlocked).
# You'll be prompted for a separate recovery passphrase.
relicario recovery-qr generate

# Recover the second-factor secret from a stored QR payload.
relicario recovery-qr unwrap

The QR payload is an XChaCha20-Poly1305 envelope keyed by Argon2id over a domain-separated input (prefixed with b"relicario-recovery-v1\0"), so even if you reuse your vault passphrase as your recovery passphrase, the wrap key cannot collide with a vault master key. Both salt and nonce are freshly randomized per call, so two QRs printed from the same passphrase yield different bytes — the printed copy doesn't leak whether you've printed others.

Recommended practice: print the QR, store it offline (safe, deposit box), and forget about it. The recovery passphrase is what protects the printed copy from being useful to someone who finds it.

Architecture

A short tour of the four codebases and how they fit together lives in DESIGN.md. Crypto pipeline diagrams are in docs/CRYPTO.md; the wire format reference is docs/FORMATS.md; the threat model is docs/SECURITY.md.

relicario-core is the platform-agnostic bytes-in/bytes-out heart — no filesystem, no network. The CLI binary and the browser-extension WASM bridge both consume it. See per-codebase deep-dives in crates/*/ARCHITECTURE.md and extension/ARCHITECTURE.md.

Crypto primitives

Primitive Purpose Why this one
Argon2id (64 MiB, 3 iter, 4 parallel) Key derivation from passphrase + second-factor secret Memory-hard, GPU-resistant, OWASP recommended
XChaCha20-Poly1305 Authenticated encryption of vault entries 192-bit nonce (no collision risk), fast in WASM/ARM without AES-NI
ed25519 Device key signing Per-device commit authorization, revocable without KDF rotation

Encrypted file format

version (1 byte) | nonce (24 bytes) | ciphertext (variable) | auth tag (16 bytes)

Every write generates a fresh random nonce. The version byte allows future format changes.

Vault layout

my-vault.git/
├── manifest.enc          # Encrypted item index (names, URLs, timestamps)
├── settings.enc          # Encrypted vault settings (retention, caps, generator defaults)
├── items/
│   ├── a1b2c3d4e5f6a7b8.enc   # One encrypted item per file
│   └── …
├── attachments/
│   └── <item-id>/
│       └── <aid>.enc     # Content-addressed encrypted attachment blob
└── .relicario/
    ├── salt              # 32-byte random salt (not secret)
    ├── params.json       # KDF parameters
    ├── devices.json      # Authorized device public keys
    └── revoked.json      # Revoked device records (when device auth is enabled)

Item IDs are random 16-char hex strings (64 bits of entropy). Git history is preserved — every add/edit/delete is a commit. "When was this password last rotated?" is answered by git log and by the per-item field history.

Device management

Each device generates its own ed25519 keypair. The public key is stored in .relicario/devices.json (committed to the repo). Device keys are used for commit signing — they do NOT participate in vault decryption.

Revoking a device: remove its key from devices.json and commit. No passphrase or second-factor rotation needed.

relicario device add --name laptop
relicario device list
relicario device revoke laptop

Building

Requires Rust stable (1.70+).

git clone ssh://git@git.adlee.work:2222/alee/relicario.git
cd relicario
cargo build --release
cargo test

The binary is at target/release/relicario.

Roadmap

  • WASM build + Chrome MV3 browser extension (inline crypto, no native messaging)
  • Firefox WebExtension build
  • Typed items: Login, SecureNote, Identity, Card, Key, Document, TOTP
  • Secure document storage (encrypted file attachments)
  • Backup & restore (.relbak encrypted envelope)
  • Recovery QR (paper-printable second-factor secret backup with separate passphrase)
  • LastPass CSV import
  • Device authentication (ed25519 commit signing + pre-receive hook)
  • Import from Bitwarden / 1Password
  • relicario unlock daemon (ssh-agent-style, holds master key for a TTL)
  • Android/iOS clients (Rust core compiles to ARM)
  • Safari extension

License

GPL-3.0-or-later — see LICENSE.


Built by Aaron D. Lee. Design spec and threat model in docs/superpowers/specs/.


Next: DESIGN.md — the system tour.