Files
relicario/docs/CRYPTO.md
adlee-was-taken 5e7023fcc1 docs: add scope headers + Next: footers to all tour docs
Each of the eight tour docs (README, DESIGN, docs/CRYPTO,
docs/FORMATS, docs/SECURITY, crates/relicario-core/ARCHITECTURE,
crates/relicario-cli/ARCHITECTURE, extension/ARCHITECTURE) now
declares its scope in a blockquote under its H1 and ends with a
single-line "Next:" pointer to the next doc in the canonical
reading order: README → DESIGN → CRYPTO → FORMATS → SECURITY →
core → cli → extension.

Also trimmed README's mid-section "Architecture" stub to a one-
paragraph pointer at DESIGN.md (was duplicating cross-codebase
content and referencing a non-existent docs/architecture/ tree).

Renamed docs/CRYPTO.md's H1 from "Relicario — Architecture" to
"Relicario — Crypto Pipeline" to match the file's renamed scope.

Spec: docs/superpowers/specs/2026-05-30-doc-structure-redesign-design.md
2026-05-30 15:36:46 -04:00

283 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Relicario — Crypto Pipeline
> **Audience:** anyone evaluating or auditing the crypto. This doc owns Argon2id parameters and rationale, XChaCha20-Poly1305 rationale, vault creation/unlock flow diagrams, DCT-steganography embed and extract flows, and the high-level encrypted-file-format diagram. **Does NOT own:** byte-level schemas or JSON shapes (see [FORMATS.md](FORMATS.md)), attacker scenarios (see [SECURITY.md](SECURITY.md)), or per-module crypto implementation (see [../crates/relicario-core/ARCHITECTURE.md](../crates/relicario-core/ARCHITECTURE.md)).
## System Overview
```
┌──────────────────────────────────────────────────────────────────┐
│ CLIENT DEVICE (trusted) │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌──────────────────────┐ │
│ │ Reference │ │ Passphrase │ │ relicario-cli │ │
│ │ JPEG │ │ (typed) │ │ or browser ext │ │
│ │ (on disk) │ │ │ │ │ │
│ └──────┬───────┘ └──────┬───────┘ └──────────┬───────────┘ │
│ │ │ │ │
│ ▼ │ │ │
│ ┌──────────────┐ │ │ │
│ │ imgsecret │ │ │ │
│ │ ::extract() │ │ │ │
│ └──────┬───────┘ │ │ │
│ │ │ │ │
│ ▼ ▼ │ │
│ ┌──────────────────────────────┐ │ │
│ │ Argon2id KDF │ │ │
│ │ password = passphrase ‖ │ │ │
│ │ image_secret │ │ │
│ │ salt = vault_salt │ │ │
│ │ → master_key (32 bytes) │ │ │
│ └──────────────┬───────────────┘ │ │
│ │ │ │
│ ▼ │ │
│ ┌──────────────────────────────┐ │ │
│ │ XChaCha20-Poly1305 │◄──────────────────┘ │
│ │ encrypt / decrypt │ │
│ │ (192-bit nonce, 256-bit │ │
│ │ key, 128-bit auth tag) │ │
│ └──────────────┬───────────────┘ │
│ │ │
└─────────────────┼──────────────────────────────────────────────────┘
│ git push / pull (HTTPS or SSH)
┌──────────────────────────────────────────────────────────────────┐
│ GIT SERVER (untrusted) │
│ │
│ relicario-vault.git/ │
│ ├── manifest.enc ← opaque ciphertext │
│ ├── settings.enc ← opaque ciphertext │
│ ├── items/ │
│ │ ├── a1b2c3d4e5f6a7b8.enc ← opaque ciphertext │
│ │ └── … │
│ ├── attachments/ │
│ │ └── <item-id>/<aid>.enc ← opaque ciphertext │
│ └── .relicario/ │
│ ├── salt ← 32 bytes (not secret) │
│ ├── params.json ← KDF params (not secret) │
│ ├── devices.json ← device public keys (not secret) │
│ └── revoked.json ← revoked device records (not secret) │
│ │
│ The server sees NOTHING useful. No keys, no plaintext, │
│ no metadata about what's inside. │
└──────────────────────────────────────────────────────────────────┘
```
## Vault Creation Flow
```
User provides: System generates:
├── carrier JPEG (any photo) ├── image_secret (256-bit random)
└── passphrase (memorized) └── vault_salt (256-bit random)
┌──────────────────┐
carrier JPEG ──────►│ imgsecret │──────► reference.jpg
image_secret ──────►│ ::embed() │ (looks like a normal photo,
│ DCT stego │ carries hidden secret)
└──────────────────┘
┌──────────────────┐
passphrase ────────►│ │
│ Argon2id │──────► master_key (32 bytes)
image_secret ──────►│ (64 MiB, 3 it) │ (held in memory only)
vault_salt ────────►│ │
└──────────────────┘
┌──────────────────┐
master_key ────────►│ XChaCha20- │──────► manifest.enc
empty manifest ────►│ Poly1305 │ settings.enc
default settings ──►│ encrypt (×2) │ (parallel artifacts;
└──────────────────┘ independent nonces)
┌──────────────────┐
│ git init │──────► vault repo
│ git commit │ (ready to push)
└──────────────────┘
```
Item creation, the typed-item envelope (`Item` + per-type `ItemCore`),
attachment encryption, and field-history tracking are not shown above —
they are described in [`crates/relicario-core/ARCHITECTURE.md`](../crates/relicario-core/ARCHITECTURE.md).
The flow above covers only the crypto-pipeline shape that vault init
establishes; the per-item lifecycle reuses the same `master_key` +
XChaCha20-Poly1305 primitives against `items/<id>.enc` and
`attachments/<item-id>/<aid>.enc`.
## Unlock Flow (every vault operation)
```
┌──────────────────┐
reference.jpg ─────►│ imgsecret │──────► image_secret
│ ::extract() │ (256 bits)
└──────────────────┘
┌──────────────────┐
passphrase ────────►│ │
image_secret ──────►│ Argon2id │──────► master_key
vault_salt ────────►│ │
└──────────────────┘
┌──────────────────┐
master_key ────────►│ XChaCha20 │──────► plaintext entries
*.enc files ───────►│ ::decrypt() │ (in memory only)
└──────────────────┘
```
## imgsecret DCT Embedding
```
Input JPEG
┌──────────────────┐
│ Decode to RGB │
│ Extract Y │
│ (luminance) │
└────────┬─────────┘
┌──────────────────────────────┐
│ Full Image │
│ ┌────────────────────────┐ │
│ │ 15% ┌────────────┐ │ │
│ │margin│ │15% │ │
│ │ │ Central │mar-│ │
│ │ │ 70% │gin │ │
│ │ │ EMBEDDING │ │ │
│ │ │ REGION │ │ │
│ │ └────────────┘ │ │
│ │ 15% margin │ │
│ └────────────────────────┘ │
└──────────────────────────────┘
┌──────────────────┐
│ Divide into │
│ 8×8 blocks │
│ Apply 2D DCT │
└────────┬─────────┘
┌──────────────────┐
│ For each │
│ selected block: │
│ │
│ QIM embed bits │
│ in zig-zag │
│ positions 6-17 │
│ (mid-frequency) │
│ │
│ Repeat secret │
│ MIN_COPIES (5) │
│ to 50 times, │
│ by capacity │
└────────┬─────────┘
┌──────────────────┐
│ Inverse DCT │
│ Reconstruct RGB │
│ Save JPEG (Q92) │
└──────────────────┘
Output: reference.jpg
(visually identical,
carries 256-bit secret)
```
The redundancy count is chosen at embed time based on available DCT capacity: `num_copies = (total_blocks / BLOCKS_PER_COPY).min(50)`, with `BLOCKS_PER_COPY = 22` and a floor of `MIN_COPIES = 5` (`crates/relicario-core/src/imgsecret.rs:78,530-537`). Images that cannot fit at least 5 copies are rejected before embed. Majority voting across these copies at extract time requires ≥ 60 % confidence per bit.
## Extraction (with crop recovery)
```
Input JPEG (possibly re-encoded or cropped)
┌─────────────────────────┐
│ Try canonical alignment │──── Success ──► image_secret
│ (offset 0,0) │
└─────────┬───────────────┘
│ Fail
┌─────────────────────────┐
│ Search crop offsets │
│ dx, dy: -15% to +15% │
│ step: 8 pixels │
│ (~16,800 candidates) │──── Success ──► image_secret
│ │
│ For each: extract bits, │
│ majority vote, check │
│ confidence ≥ 60% │
└─────────┬───────────────┘
│ All fail
ExtractionFailed error
```
## Encrypted File Format
```
┌─────────┬────────────────────────┬──────────────────┬──────────────────┐
│ version │ nonce │ ciphertext │ auth tag │
│ 1 byte │ 24 bytes │ N bytes │ 16 bytes │
│ 0x02 │ random per write │ XChaCha20 stream │ Poly1305 MAC │
└─────────┴────────────────────────┴──────────────────┴──────────────────┘
```
`VERSION_BYTE = 0x02` (`crates/relicario-core/src/crypto.rs:59`). Blobs starting with any other byte are rejected with `UnsupportedFormatVersion { found, expected: 0x02 }`. The legacy `0x01` format from the pre-typed-items era is no longer supported.
## Crate Architecture
```
┌────────────────────────────────────────────────────────────┐
│ relicario-cli │
│ Filesystem, git (shelling out), terminal I/O, clipboard │
│ │
│ Depends on: relicario-core, clap, anyhow, rpassword, arboard │
└──────────────────────┬─────────────────────────────────────┘
│ uses
┌────────────────────────────────────────────────────────────┐
│ relicario-core │
│ Platform-agnostic: bytes in, bytes out │
│ No filesystem, no network, no git │
│ │
│ ┌──────────┐ ┌──────────┐ ┌─────────┐ ┌────────────┐ │
│ │ crypto │ │ imgsecret│ │ item + │ │ vault │ │
│ │ │ │ │ │ types │ │ │ │
│ │ KDF │ │ DCT │ │ Item │ │ encrypt_ │ │
│ │ encrypt │ │ embed │ │ Manifest│ │ item() │ │
│ │ decrypt │ │ extract │ │ Settings│ │ decrypt_ │ │
│ │ │ │ QIM │ │ Backup │ │ manifest() │ │
│ │ │ │ │ │ Device │ │ ... │ │
│ └──────────┘ └──────────┘ └─────────┘ └────────────┘ │
│ │
│ Consumed by: relicario-cli, relicario-wasm (extension), │
│ relicario-server (pre-receive hook). │
│ Future: JNI/Swift wrappers for Android/iOS. │
└────────────────────────────────────────────────────────────┘
```
## Entropy at Each Attack Scenario
```
Server breach only: ████████████████████████████ 256+ bits (infeasible)
passphrase + image_secret
Server + stolen image: ████░░░░░░░░░░░░░░░░░░░░░░ ~51 bits (4 diceware words)
passphrase through Argon2id ~7 million years
Shoulder-surfed passphrase: ████████████████████████████ 256 bits (infeasible)
image_secret
Stolen device: ████░░░░░░░░░░░░░░░░░░░░░░ ~51 bits (4 diceware words)
passphrase through Argon2id ~7 million years
Both factors compromised: game over (same as every password manager)
```
---
**Next:** [FORMATS.md](FORMATS.md) — the byte-level wire formats.