Paranoid-grade encryption utility. It tars a directory (no compression) and encrypts/decrypts it with Argon2id (RFC 9106 guidance) + XChaCha20-Poly1305. See ANALYSIS.md for full details.
- Rust (latest stable version)
Debug build:
cargo buildBinary will be at: target/debug/obsidenc (or target/debug/obsidenc.exe on Windows)
Release build (optimized):
cargo build --releaseBinary will be at: target/release/obsidenc (or target/release/obsidenc.exe on Windows)
Run directly (without installing):
cargo run -- encrypt <vault_dir> <output_file>
cargo run --release -- encrypt <vault_dir> <output_file>Windows (Command Prompt):
run.bat encrypt <vault_dir> <output_file>Linux/Unix:
chmod +x run.sh
./run.sh encrypt <vault_dir> <output_file>- Attacker has full access to the encrypted file.
- Attacker has unlimited offline time.
- Attacker does not have runtime access to the machine during encryption/decryption.
obsidenc encrypt <vault_dir> <output_file> [--keyfile <path>] [--force]
obsidenc decrypt <input_file> <output_dir> [--keyfile <path>] [--force]Notes:
- Encryption prompts for the password twice (confirmation).
- Minimum password length is 20 characters.
- If
--keyfileis used, both password and keyfile are required. - Decryption refuses to overwrite an existing output directory unless
--forceis supplied.
For automation (and the bundled GUI), passwords can be provided via stdin:
# Encrypt: 2 lines on stdin (password + confirmation)
printf '%s\n%s\n' "$PW" "$PW" | obsidenc --password-stdin encrypt <vault_dir> <output_file>
# Decrypt: 1 line on stdin (password)
printf '%s\n' "$PW" | obsidenc --password-stdin decrypt <input_file> <output_dir>The repository includes a minimal Tauri desktop UI that drives obsidenc as a bundled sidecar binary. The GUI does not implement crypto; it spawns obsidenc and passes the password via stdin (--password-stdin).
From gui/:
# Dev (builds sidecar + starts a local UI server)
cargo tauri dev
# Release bundle (builds sidecar from source and embeds it)
cargo tauri buildInstall and run:
cargo install cargo-audit
cargo auditThere is an automated smoke test that:
- Encrypts the files in
tests/mockintotests/test.oen - Decrypts
tests/test.oenintotests/decrypt - Verifies that all decrypted files exactly match the originals in
tests/mock
To run it:
# From the project root
cargo test --test roundtripThe test never modifies the original tests/mock data; it only creates and deletes tests/test.oen and tests/decrypt/.
The project includes fuzzing infrastructure to verify robustness against malformed input. Fuzzing helps ensure that the decryption parser never panics on invalid data.
Platform Support: Fuzzing is only available on Linux/Unix. The fuzzing targets are automatically disabled on Windows (libfuzzer-sys doesn't support Windows). The main obsidenc binary works perfectly on Windows - only the fuzzing infrastructure is platform-limited.
Prerequisites:
- Linux system (or WSL on Windows)
- Rust nightly toolchain (fuzzing requires nightly features)
- LLVM/Clang (required for libfuzzer)
Step 1: Install Rust nightly toolchain
# Install nightly (if not already installed)
rustup toolchain install nightly
# Use nightly for fuzzing (you can switch back to stable after)
rustup default nightly
# OR use nightly just for this project:
rustup override set nightlyStep 2: Install cargo-fuzz
cargo install cargo-fuzzStep 3: Run the fuzzing target
# From the project root directory
cargo fuzz run fuzz_decryptNote: If you want to keep stable as your default toolchain, you can use rustup override set nightly in the project directory instead of rustup default nightly. This sets nightly only for this project.
Step 3: Let it run
- The fuzzer will run indefinitely, generating random inputs
- Press Ctrl+C to stop
- If a panic is found, the fuzzer will save the input that caused it to
fuzz/artifacts/fuzz_decrypt/ - Check the output for any crashes or panics
Advanced options:
# Run with a timeout (e.g., 60 seconds)
cargo fuzz run fuzz_decrypt -- -max_total_time=60
# Run for a specific number of iterations
cargo fuzz run fuzz_decrypt -- -runs=10000
# Run with corpus (saved interesting inputs)
cargo fuzz run fuzz_decrypt -- -merge=1What the fuzzing target tests:
- Header parsing with malformed input (wrong magic bytes, invalid version, etc.)
- Chunk parsing with invalid lengths and data
- Buffer handling edge cases (empty chunks, oversized chunks, etc.)
- Ensures all errors are returned as
Result::Err, never panics
Improving Fuzzing Coverage:
The fuzzer automatically constructs valid headers to test chunk parsing logic, but you can improve coverage by adding seed corpus files. Seed corpus files are real encrypted files that help the fuzzer discover valid input patterns.
Creating a Seed Corpus:
-
Create some test encrypted files:
# Create a test directory mkdir -p /tmp/test_vault echo "test content" > /tmp/test_vault/test.txt # Encrypt it ./target/release/obsidenc encrypt /tmp/test_vault /tmp/test.oen # Copy to seed corpus cp /tmp/test.oen fuzz/corpus/fuzz_decrypt/seed_001.oen
-
The fuzzer will automatically use files in
fuzz/corpus/fuzz_decrypt/as starting points. -
You can add multiple seed files with different characteristics:
- Small files (empty or single byte)
- Large files (multi-chunk)
- Files with various directory structures
- Files encrypted with different Argon2 parameters
Fuzzing Strategy:
The improved fuzzer uses a dual-mode approach:
- Mode 1: Tests raw header parsing to find edge cases in error handling
- Mode 2: Constructs valid headers with random data to test chunk parsing logic
This ensures coverage of both error paths and the actual decryption code paths.
Windows Users: If you need to run fuzzing, use WSL (Windows Subsystem for Linux) or a Linux VM. The main encryption/decryption functionality works natively on Windows.