Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 11 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Decrypting works the same way: drop the `.vx2` file, enter the original passphra
## What's New in 2.0

- **Zero File Size Limit**: Encrypt files of any size. The streaming engine removed the old 2 GB cap — terabyte-scale files work fine.
- **Constant ~66 MB RAM**: Argon2id key derivation uses 64 MB (fixed), and 1 MB chunk streaming adds ~2 MB. Peak stays at ~66 MB regardless of file size.
- **Constant ~258 MB RAM**: Argon2id key derivation uses 256 MB (fixed), and 1 MB chunk streaming adds ~2 MB. Peak stays at ~258 MB regardless of file size.
- **High-Efficiency Batch Queue Processing**: Select multiple files, enter one passphrase, and the app processes them sequentially. If any file fails (disk full, permission denied), its temporary data is cleaned up and the queue moves to the next file — the app never crashes.
- **VAULTX03 Streaming Engine**: AES-256-GCM-SIV with STREAM BE32 construction. Files are encrypted in 1 MB chunks with per-chunk authentication tags, keeping memory flat and guaranteeing integrity.
- **I/O Governor for Large Files**: Prevents OS dirty page cache saturation during multi-gigabyte encryptions. Every ~100 MB the engine forces a disk flush and yields the thread, eliminating system-wide freezes.
Expand Down Expand Up @@ -62,11 +62,11 @@ Decrypting works the same way: drop the `.vx2` file, enter the original passphra
|---|---|---|
| Language | **100% Safe Rust** | Zero `unsafe` blocks in the codebase — memory safety guaranteed by the borrow checker |
| Cipher | **AES-256-GCM-SIV** (RFC 8452) | Nonce-misuse resistant authenticated encryption |
| Key Derivation | **Argon2id** | Memory-hard KDF: 64 MiB, 3 iterations, 4 lanes, 32-byte output — GPU/ASIC resistant |
| Key Derivation | **Argon2id** | Memory-hard KDF: 256 MiB, 3 iterations, 4 lanes, 32-byte output — GPU/ASIC resistant |
| Key Expansion | **HKDF-SHA512** | Cryptographic domain separation between V2 and V3 pipelines |
| Randomness | **OsRng** (OS CSPRNG) | Fresh 16-byte salt + 7-byte stream nonce generated per file |
| Memory Hygiene | **Zeroizing\<T\>** | Key material zeroed from RAM on scope exit |
| Streaming I/O | **EncryptorBE32 / DecryptorBE32** | Constant ~66 MB RAM regardless of file size |
| Streaming I/O | **EncryptorBE32 / DecryptorBE32** | Constant ~258 MB RAM regardless of file size |
| File Writes | **Atomic .tmp → rename** | No partial or corrupted files on crash |
| Batch Error Handling | **Per-file isolation** | Failed files cleaned up; remaining queue continues |
| I/O Governor | **sync_data + thread yield** | Flushes disk cache every ~100 MB; prevents OS dirty page freezes on large files |
Expand All @@ -75,19 +75,10 @@ Decrypting works the same way: drop the `.vx2` file, enter the original passphra

```
Argon2id:
Memory : 64 MiB (65,536 KiB)
Memory : 256 MiB (262,144 KiB)
Iterations : 3
Lanes : 4
Output : 32 bytes

AES-256-GCM-SIV:
Key : 256 bits
Tag : 128 bits (16 bytes) per chunk

Streaming:
Chunk Size : 1 MB (1,048,576 bytes)
Nonce : 7 bytes (STREAM BE32 construction)
Max File : ~4 Exabytes (2^32 × 1 MB)
```

### VAULTX03 File Format
Expand Down Expand Up @@ -122,10 +113,10 @@ VAULTX02 files are automatically detected by their magic bytes and routed to the
### Memory Profile

```
Key derivation (Argon2id) : ~64 MB (fixed, 65,536 KiB blocks)
Key derivation (Argon2id) : ~256 MB (fixed, 262,144 KiB blocks)
Stream buffer (1 MB chunk): ~2 MB (read + ciphertext buffer)
───────────────────────────────────────────────────────────────
Peak RAM : ~66 MB (constant, independent of file size)
─────────────────────────────────────────────────────────────
Peak RAM : ~258 MB (constant, independent of file size)
```

---
Expand Down Expand Up @@ -564,7 +555,7 @@ For full setup, JNI architecture details, and building instructions, see the [An
|-------|-----------|------------|
| Encryption (V3) | AES-256-GCM-SIV (BE32 streaming) | 56-bit stream nonce per file, 128-bit auth tag per 1 MB chunk |
| Encryption (V2 legacy) | AES-256-GCM-SIV | 96-bit nonce, 128-bit auth tag |
| Key Derivation | Argon2id | m=65536 (64 MB), t=3 iterations, p=4 lanes |
| Key Derivation | Argon2id | m=262144 (256 MB), t=3 iterations, p=4 lanes |
| Key Expansion | HKDF-SHA512 | Two separate 256-bit subkeys: enc + nonce |
| Integrity | GHASH (GCM-SIV) | Per-chunk authentication; any bit-flip fails |
| RNG | OS CSPRNG (`OsRng`) | `getrandom` crate; no `rand` global state |
Expand All @@ -576,7 +567,7 @@ For full setup, JNI architecture details, and building instructions, see the [An
V3 (VAULTX03 streaming):
passphrase + 16-byte OsRng salt
Argon2id (64 MB, 3 passes, 4 lanes)
Argon2id (256 MB, 3 passes, 4 lanes)
64-byte master key
├─ HKDF-Extract+Expand → 32-byte encryption key
Expand All @@ -585,7 +576,7 @@ V3 (VAULTX03 streaming):
V2 (VAULTX02 legacy):
passphrase + 32-byte OsRng salt
Argon2id (64 MB, 3 passes, 4 lanes)
Argon2id (256 MB, 3 passes, 4 lanes)
64-byte master key
├─ HKDF-Extract+Expand → 32-byte encryption key
Expand All @@ -597,7 +588,7 @@ The salt is stored in plaintext in the file header. The master key and all inter
### Threat Model

**Protected against:**
- Offline brute-force: Argon2id with 64 MB memory cost makes GPU attacks expensive.
- Offline brute-force: Argon2id with 256 MB memory cost makes GPU attacks expensive.
- Ciphertext tampering: Every 1 MB chunk has an independent GCM-SIV authentication tag (GHASH). Corruption of any byte fails decryption of that chunk.
- Nonce reuse: Each encryption generates a fresh 7-byte (56-bit) random nonce via `OsRng`. `EncryptorBE32` expands it to 12 bytes internally with a per-chunk counter. Fresh nonce per file makes collision probability negligible.
- Sensitive data in memory: Plaintext buffers and keys are wrapped in `Zeroizing<>`, zeroed on drop. Passwords are cleared from UI state immediately after the crypto thread starts.
Expand Down
8 changes: 4 additions & 4 deletions neuron-encrypt/src/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ pub const EXTENSION: &str = ".vx2";
/// authentication tag. The final chunk is sealed with `encrypt_last`/`decrypt_last`
/// to prevent truncation attacks.
///
/// Memory profile: Argon2id (64 MB) during key derivation, then ~2 MB constant
/// during 1 MB chunk streaming — peak ~66 MB regardless of file size.
/// Memory profile: Argon2id (256 MB) during key derivation, then ~2 MB constant
/// during 1 MB chunk streaming — peak ~258 MB regardless of file size.
pub const MAGIC_V3: &[u8; 8] = b"VAULTX03";
/// 16-byte salt — 128-bit entropy, recommended minimum for Argon2.
pub const SALT_V3_LEN: usize = 16;
Expand Down Expand Up @@ -160,7 +160,7 @@ fn derive_key(password: &[u8], salt: &[u8]) -> CryptoResult<Zeroizing<Vec<u8>>>
let mut final_key = Zeroizing::new(vec![0u8; 32]);

let mut intermediate = Zeroizing::new(vec![0u8; 64]);
let params = Params::new(65_536, 3, 4, Some(64))
let params = Params::new(262_144, 3, 4, Some(64))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥 The Roast: You've cranked the Argon2id memory from 64 MB to 256 MB — a 4× jump with zero warning to existing users. Every file encrypted with the old version is now permanently unreadable because the key derivation output will differ. This isn't an upgrade; it's a silent data hostage situation. 🎤

🩹 The Fix: Either bump the file format version (e.g. V4) so new files use 256 MB while V2/V3 files still decrypt with 64 MB, or at minimum add a colossal prerelease warning that all previously encrypted data will be orphaned. Hardcoding different Argon2id parameters without version detection is a critical breaking change for an encryption tool.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep legacy VAULTX02 decryption on the old KDF

This function is also used by decrypt_file_legacy/decrypt_stream_v2, but VAULTX02 headers only contain magic, salt, and nonce and all historical V2 files were encrypted with m=65_536. Switching the legacy derivation to m=262_144 means the documented backwards-compatible V2 decryption path now derives the wrong key and rejects every existing legacy file as DecryptionFailed.

Useful? React with 👍 / 👎.

.map_err(|error| CryptoError::Argon2Failed(error.to_string()))?;
let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
argon2
Expand All @@ -180,7 +180,7 @@ fn derive_key(password: &[u8], salt: &[u8]) -> CryptoResult<Zeroizing<Vec<u8>>>
fn derive_key_v3(password: &[u8], salt: &[u8]) -> CryptoResult<Zeroizing<Vec<u8>>> {
let mut final_key = Zeroizing::new(vec![0u8; 32]);
let mut intermediate = Zeroizing::new(vec![0u8; 64]);
let params = Params::new(65_536, 3, 4, Some(64))
let params = Params::new(262_144, 3, 4, Some(64))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥 The Roast: Same 4× memory jump to 256 MB copy-pasted into derive_key_v3. Nothing says "we've thought this through" like repeating the same breaking change in a second function. V3 files encrypted with the previous release are equally unreadable now. Two times the breaking change, zero times the migration path. 🎤

🩹 The Fix: Same as above — version the file format so old V3 files still derive keys with the original 64 MB parameters, or provide a data migration tool. Don't silently break decryption for existing users.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep VAULTX03 decryption compatible with old KDF

When a user decrypts any VAULTX03 file produced by previous builds, the unchanged header only tells decrypt_file to dispatch to the V3 path; it does not store Argon2 parameters or use a new magic/version. Because those files were sealed with m=65_536, deriving with m=262_144 here produces a different key and all existing V3 ciphertexts fail authentication, making users' encrypted files unrecoverable unless a fallback or versioned format is added.

Useful? React with 👍 / 👎.

.map_err(|e| CryptoError::Argon2Failed(e.to_string()))?;
let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
argon2
Expand Down
Loading