QShield
Post-Quantum Hybrid Encryption for Files, Directories, and Whole Disks
QShield encrypts files, directories, and raw disk images / block devices into .qsv archives using a hybrid post-quantum key encapsulation scheme. A single password protects everything—no PKI infrastructure, no key servers, no certificate management.
╔═══════════════════════════════════════════════════╗
║ QShield — Post-Quantum Storage Encryption ║
║ ML-KEM-768 + X25519 │ AES-256-GCM ║
╚═══════════════════════════════════════════════════╝
| Concern | QShield's Answer |
|---|---|
| Quantum threat | ML-KEM-768 (NIST FIPS 203, Security Level 3) — resistant to Shor's algorithm on future quantum computers |
| Classical fallback | X25519 ECDH in hybrid mode — if ML-KEM is ever broken classically, X25519 still protects the key |
| Password-only | Argon2id stretches a single password into all key material — no key files, no certificates |
| Whole-disk support | encrypt-disk / decrypt-disk stream raw bytes with zero memory buffering — handles multi-TB external drives |
| Single binary | Statically linked Rust binary, ~3 MB, no runtime dependencies |
| Cross-platform | macOS (Apple Silicon + Intel), Linux x86_64, Windows x64 |
QShield is designed to protect data at rest on portable storage media (external HDDs, USB drives, backup images) against:
- Harvest-now, decrypt-later attacks — adversaries who capture encrypted data today and attempt decryption with future quantum computers
- Offline brute-force — Argon2id with 64 MiB memory cost makes GPU/ASIC attacks prohibitively expensive
- Key compromise of a single primitive — hybrid KEM ensures that both ML-KEM-768 and X25519 must be broken simultaneously
Out of scope: QShield does not provide real-time full-disk encryption (like LUKS/BitLocker), plausible deniability, or protection against an adversary with live access to a running system.
Download from the releases page:
| Platform | Architecture | File | SHA-256 |
|---|---|---|---|
| macOS | Apple Silicon (M1/M2/M3/M4) | qshield-macos-aarch64.tar.gz |
.sha256 |
| macOS | Intel x86_64 | qshield-macos-x86_64.tar.gz |
.sha256 |
| Linux | x86_64 (glibc) | qshield-linux-x86_64.tar.gz |
.sha256 |
| Windows | x86_64 | qshield-windows-x86_64.zip |
.sha256 |
# macOS / Linux
tar xzf qshield-macos-aarch64.tar.gz
sudo mv qshield /usr/local/bin/
qshield --version
# Windows (PowerShell)
Expand-Archive qshield-windows-x86_64.zip -DestinationPath .
.\qshield.exe --versionRequires Rust 1.74 or later:
git clone https://github.com/jbaelaw/qshield.git
cd qshield
cargo build --release
# Binary at target/release/qshieldEach release includes SHA-256 checksums:
shasum -a 256 -c qshield-macos-aarch64.tar.gz.sha256Launch the terminal UI by running qshield without arguments:
qshieldThe TUI provides a menu-driven interface with:
| Feature | Description |
|---|---|
| Encrypt File/Folder | Archive and encrypt files or directories |
| Decrypt File/Folder | Decrypt and extract .qsv archives |
| Encrypt Disk (Raw) | Encrypt disk images or block devices byte-for-byte |
| Decrypt Disk (Raw) | Restore encrypted disk images to exact original |
| Verify | Check archive integrity without extracting |
| Info | Display cryptographic metadata |
All operations show real-time progress bars and masked password input.
# Encrypt a file (interactive password prompt)
qshield encrypt secret.pdf
# Encrypt a directory with explicit output path
qshield encrypt ./confidential/ -o confidential.qsv
# Overwrite existing output
qshield encrypt ./docs/ -o docs.qsv --force
# Decrypt to a specific directory
qshield decrypt docs.qsv -o ./restored/
# Non-interactive mode (scripting / automation)
qshield encrypt data.zip -p "strong-passphrase-here" -o data.qsv
qshield decrypt data.qsv --password "strong-passphrase-here" -o ./output
# Verify integrity without extracting
qshield verify data.qsv --password "strong-passphrase-here"
# Inspect cryptographic metadata
qshield info data.qsvEncrypt raw disk images and block devices with streaming I/O — no tar wrapping, no in-memory buffering, byte-exact restore:
# Encrypt a disk image file
qshield encrypt-disk /path/to/backup.img -o backup.qsv
# macOS: encrypt an external drive (UNMOUNT FIRST)
diskutil unmountDisk /dev/disk4
sudo qshield encrypt-disk /dev/disk4 -o external_hdd.qsv
# Linux: encrypt a USB drive
sudo umount /dev/sdb
sudo qshield encrypt-disk /dev/sdb -o usb_drive.qsv
# Decrypt to an image file
qshield decrypt-disk backup.qsv -o restored.img --force
# Decrypt directly to a block device (CAUTION: overwrites entire device)
sudo qshield decrypt-disk external_hdd.qsv -o /dev/disk4 --force
# Verify and inspect
qshield verify backup.qsv --password "passphrase"
qshield info backup.qsvDisk mode safety features:
| Feature | Description |
|---|---|
| Block device detection | Automatic identification via OS file type metadata |
| Unmount warning | Prompts user to unmount before reading/writing devices |
| Interactive confirmation | Requires explicit y before device operations |
| Byte-exact restore | Original size stored in header — no padding artifacts |
| Platform-native sizing | macOS diskutil, Linux blockdev, seek-to-end fallback |
| Cross-command guard | decrypt rejects raw disk files (suggests decrypt-disk) |
QShield encrypts any file, directory, or block device. Automatic type detection provides human-readable descriptions:
| Category | Extensions / Types |
|---|---|
| Disk images | .iso, .img, .dmg, .vhd, .vhdx, .vmdk, .qcow2 |
| Archives | .zip, .tar, .gz, .bz2, .xz, .zst, .7z, .rar |
| Documents | .pdf, .doc(x), .xls(x), .ppt(x), .txt, .csv |
| Media | .jpg, .png, .gif, .mp4, .mkv, .mp3, .flac, .wav |
| Data | .json, .xml, .html, .sql, .db, .sqlite |
| Executables | .exe, .dll, .so, .dylib, .bin |
| Crypto | .key, .pem, .crt, .p12, .gpg |
| Directories | Any folder (recursively archived via tar) |
| Block devices | /dev/diskN (macOS), /dev/sdX (Linux) — via encrypt-disk |
All inputs are treated as opaque byte streams — QShield never parses or modifies file contents.
┌─────────────┐
│ Password │
└──────┬──────┘
│
Argon2id (64 MiB, 3 iter, 4 lanes)
│
┌──────▼──────┐
│ Master Key │──────┐
│ (256-bit) │ │
└──────┬──────┘ │
│ Encrypts KEM
┌────────────┴────────────┐ private keys
│ │ (AES-256-GCM)
┌──────▼──────┐ ┌───────▼───────┐
│ ML-KEM-768 │ │ X25519 ECDH │
│ (FIPS 203) │ │ (RFC 7748) │
└──────┬──────┘ └───────┬───────┘
│ shared secret │ shared secret
└────────────┬────────────┘
│
HKDF-SHA3-256 (SP 800-56C)
│
┌──────▼──────┐
│ Volume Key │
│ (256-bit) │
└──────┬──────┘
│
AES-256-GCM (SP 800-38D)
1 MiB streaming chunks
│
┌──────▼──────┐
│ Encrypted │
│ Data │
└─────────────┘
| Layer | Algorithm | Standard | Security Level | Rationale |
|---|---|---|---|---|
| PQ Key Encapsulation | ML-KEM-768 | NIST FIPS 203 | Level 3 (AES-192 equivalent) | First NIST-standardized lattice-based KEM; balanced security/performance |
| Classical Key Agreement | X25519 | RFC 7748 | ~128-bit classical | Widely deployed, constant-time, no patents; hybrid fallback if lattice assumptions fail |
| Key Combination | HKDF-SHA3-256 | SP 800-56C | 256-bit | Domain-separated extraction ensures independence of KEM contributions |
| Password Stretching | Argon2id | RFC 9106 | Memory-hard | 64 MiB memory cost defeats GPU/ASIC parallelism; hybrid side-channel resistance |
| Bulk Encryption | AES-256-GCM | SP 800-38D | 256-bit | Hardware-accelerated (AES-NI), authenticated encryption with per-chunk nonces |
The hybrid construction follows the combiner approach recommended by NIST for transitional post-quantum deployments:
- Independent key generation — ML-KEM-768 and X25519 keypairs are generated independently
- Independent encapsulation — each KEM produces its own shared secret
- Domain-separated combination — HKDF-SHA3-256 combines both secrets with distinct context labels
- Defense-in-depth — compromise of either primitive alone does not reveal the volume key
This ensures security under the assumption that at least one of the two KEM primitives remains secure.
Bulk data is encrypted in 1 MiB chunks with per-chunk authenticated encryption:
For each chunk i:
nonce[0..8] = i as u64 (little-endian counter)
nonce[8..12] = random (4 bytes from OS CSPRNG)
ciphertext_i = AES-256-GCM(volume_key, nonce, plaintext_chunk_i)
The counter component prevents nonce reuse across chunks; the random component provides additional uniqueness across encryptions with the same key.
The TUI features:
| Feature | Description |
|---|---|
| Step indicators | Visual breadcrumb showing current position in the workflow |
| Password strength meter | Real-time strength evaluation (Weak/Fair/Good/Strong) with visual bar |
| Disk confirmation dialog | Explicit Yes/No prompt before any block device operation |
| Animated progress | Spinning indicator with elapsed time during encryption/decryption |
| Scrollable info | Scroll through cryptographic metadata with ↑↓ keys |
| Context-sensitive help | Bottom bar changes based on current screen |
| Vim-style navigation | j/k keys work alongside ↑↓ throughout the interface |
Offset Size Field
────── ────── ──────────────────────────────────────────
0x00 4 B Magic number: "QSV1" (0x51 0x53 0x56 0x31)
0x04 1 B Format version (currently 0x01)
0x05 1 B KEM algorithm ID (0x01 = ML-KEM-768 + X25519)
0x06 1 B Content type (0x00 = Tar Archive, 0x01 = Raw Disk)
0x07 8 B Original plaintext size (u64 LE) — for byte-exact restore
0x0F 32 B Argon2id salt
0x2F var [u16 LE length] ML-KEM encapsulation key (1184 B for ML-KEM-768)
var [u16 LE length] ML-KEM decapsulation key (encrypted, ~2416 B + GCM overhead)
32 B X25519 public key
var [u16 LE length] ML-KEM ciphertext (1088 B for ML-KEM-768)
32 B X25519 ephemeral public key
var [u16 LE length] X25519 secret key (encrypted, 32 B + GCM overhead)
────────────────────────────────────────────────────────────
Data stream (repeating):
4 B Encrypted chunk length (u32 LE); 0x00000000 = end sentinel
12 B Nonce (8 B counter + 4 B random)
var Ciphertext + 16 B GCM authentication tag
Header size: approximately 4,817 bytes for ML-KEM-768 + X25519.
Crypto-agility: The KEM algorithm ID byte allows future versions to introduce new algorithms (e.g., ML-KEM-1024, HQC) without breaking backward compatibility.
| Property | Mechanism |
|---|---|
| Confidentiality | AES-256-GCM with unique per-chunk nonces |
| Integrity | GCM authentication tag on every 1 MiB chunk |
| Authenticity | Password-derived master key authenticates the encryptor |
| Forward secrecy | Ephemeral KEM keypair per encryption — no long-term private key stored |
| Key zeroization | All sensitive key material (master_key, volume_key, passwords) zeroed immediately after use via the zeroize crate |
| Anti-DoS | Decryption rejects chunks exceeding 1 MiB + 64 bytes |
| Path traversal protection | Tar extraction validates all entry paths against ../, absolute paths, and prefix components |
| Symlink safety | Symlinks recorded as links (not followed); symlink inputs rejected |
- Not a real-time FDE: QShield encrypts files/images into
.qsvcontainers. It does not provide transparent block-layer encryption like LUKS, BitLocker, or FileVault. - No key escrow: If the password is lost, data is irrecoverable. There is no recovery mechanism by design.
- Unaudited cryptographic dependencies: The underlying RustCrypto crates (
ml-kem,aes-gcm,argon2,x25519-dalek) have not been independently audited. No formal side-channel analysis has been performed. - Single-threaded encryption: Bulk encryption uses a single thread. Performance scales linearly with data size.
- GCM nonce space: With 8-byte counter + 4-byte random nonces, the theoretical limit is 2^64 chunks (~16 EiB) per encryption. In practice, this is not a constraint.
Measured on Apple M2 Pro (macOS 14, --release build):
| Operation | Data Size | Time | Throughput |
|---|---|---|---|
| Encrypt file | 100 MiB | ~1.0s | ~100 MiB/s |
| Decrypt file | 100 MiB | ~1.0s | ~100 MiB/s |
| Encrypt disk | 1 GiB | ~10s | ~100 MiB/s |
| Key derivation (Argon2id) | — | ~0.4s | — |
Throughput is dominated by AES-256-GCM with hardware AES-NI acceleration. The Argon2id key derivation adds a fixed ~0.4s overhead regardless of data size.
qshield/
├── src/
│ ├── main.rs # CLI entry point, command handlers
│ ├── crypto/
│ │ ├── kem.rs # ML-KEM-768 + X25519 hybrid encapsulation
│ │ ├── kdf.rs # Argon2id password stretching, HKDF key combination
│ │ └── symmetric.rs # AES-256-GCM single-shot and streaming encryption
│ ├── volume/
│ │ └── format.rs # QSV header serialization and parsing
│ ├── tui/
│ │ ├── app.rs # Interactive terminal UI state machine
│ │ └── widgets.rs # Ratatui UI components
│ ├── error.rs # Error types
│ └── ui.rs # CLI progress bars and formatted output
├── .github/workflows/
│ ├── ci.yml # Test + Clippy + Format on every push/PR
│ └── release.yml # Cross-platform binary builds on tag push
├── CHANGELOG.md # All notable changes per version
├── SECURITY.md # Vulnerability reporting policy
├── CONTRIBUTING.md # Development setup and contribution guidelines
├── LICENSE # MIT License
└── Cargo.toml # Dependencies and build configuration
See CONTRIBUTING.md for development setup, coding standards, and the pull request process.
MIT — Copyright (c) 2026 Jiho Bae
QShield is built on the RustCrypto ecosystem: