Skip to content

Commit 69039ce

Browse files
committed
added support to encrypt folders
1 parent 1339eac commit 69039ce

8 files changed

Lines changed: 741 additions & 23 deletions

File tree

Cargo.lock

Lines changed: 60 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ rand = "0.8"
1818
anyhow = "1"
1919
rpassword = "7"
2020
zeroize = { version = "1", features = ["derive"] }
21+
tar = "0.4"
2122

2223
[dev-dependencies]
2324
criterion = { version = "0.5", features = ["html_reports"] }

README.md

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ A multi-threaded AEAD encryption engine built in Rust. Encrypts and decrypts fil
2222
- **In-place encryption**: `seal_in_place_separate_tag` / `open_in_place` via `ring` minimizes allocation in the hot loop
2323
- **Password zeroization**: Keys and passwords are securely wiped from memory after use
2424
- **O_DIRECT + sector-aligned format**: 4 KiB-aligned header and chunk slots enable `O_DIRECT` I/O, bypassing the kernel page cache for DMA-speed reads/writes on NVMe. Buffer pools use `std::alloc` with 4096-byte alignment
25+
- **Directory encryption**: Encrypt entire directories as a single encrypted archive. Tar-based packing preserves file names, permissions, timestamps, and directory structure inside the ciphertext. Extraction validates against path traversal and symlink escape attacks
2526
- **Self-describing file format**: Header stores cipher, chunk size, original file size, salt, base nonce, and Argon2id KDF parameters
2627

2728
## Performance
@@ -107,6 +108,18 @@ concryptor encrypt myfile.dat -p "password"
107108

108109
> **Security note:** `--password` / `-p` passes the password as a CLI argument, which is visible in `ps` output and shell history. For interactive use, omit it to get the secure hidden prompt. For scripting, prefer clearing history afterward or using a wrapper that reads from a file descriptor.
109110
111+
### Encrypt a directory
112+
113+
```bash
114+
# Encrypt a directory (auto-detects, produces mydir.tar.enc)
115+
concryptor encrypt mydir/
116+
117+
# With custom cipher and output
118+
concryptor encrypt mydir/ --cipher chacha -o secrets.enc
119+
```
120+
121+
Directory encryption creates a temporary tar archive (`.concryptor-*.tar`, 0600 permissions, CSPRNG-named), encrypts it, then auto-deletes the temp file. File names, directory structure, permissions, and timestamps are all inside the encrypted payload.
122+
110123
### Decrypt
111124

112125
```bash
@@ -120,6 +133,18 @@ concryptor decrypt encrypted.enc -o restored.dat
120133
concryptor decrypt myfile.dat.enc -p "password"
121134
```
122135

136+
### Decrypt and extract a directory
137+
138+
```bash
139+
# Decrypt and extract in one step (auto-strips .tar.enc -> directory name)
140+
concryptor decrypt mydir.tar.enc --extract
141+
142+
# Short flag, custom output directory
143+
concryptor decrypt mydir.tar.enc -x -o restored_dir/
144+
```
145+
146+
Without `--extract`, decrypting a directory archive produces the intermediate `.tar` file, which you can inspect or extract manually.
147+
123148
### Help
124149

125150
```bash
@@ -157,7 +182,7 @@ The salt and base nonce are generated fresh from `rand::thread_rng()` (backed by
157182

158183
## Security Design
159184

160-
- **Nonce derivation**: `chunk_nonce = base_nonce XOR chunk_index` (TLS 1.3 style). Swapping chunks causes decryption failure because the nonce at position N won't match the nonce used to encrypt the chunk originally at position M.
185+
- **Nonce derivation**: `chunk_nonce = base_nonce XOR chunk_index` (TLS 1.3 style). Swapping chunks causes decryption failure because the nonce at position N won't match the nonce used to encrypt the chunk originally at position M. Note: XOR-based nonce derivation has a theoretical weakness when the *same key* is used across multiple streams (distinct base nonces can produce overlapping nonce spaces). This does not apply to Concryptor because each encryption generates a fresh 128-bit random salt, producing a unique Argon2id key per file. Nonce uniqueness only matters under the same key, and key reuse probability is ~2^-128 per file pair.
161186
- **Header-authenticated AAD**: Every chunk's AEAD call uses `AAD = full_aligned_header (4096) || chunk_index (8 LE) || is_final (1)` (4105 bytes total). The entire 4 KiB header sector (core fields, KDF parameters, and reserved padding) is bound into every chunk's authentication tag. Modifying *any* header byte (cipher type, chunk size, original size, salt, nonce, KDF parameters, or reserved padding) invalidates all chunks. This prevents truncation attacks where an adversary edits `original_size` and removes trailing chunks, and also prevents smuggling data into the reserved padding region. Legacy v3 files are decrypted with 52-byte AAD for backward compatibility; no downgrade from v4 to v3 is possible because the version byte itself is within the authenticated AAD.
162187
- **STREAM-style final chunk indicator**: The last byte of the AAD is `0x01` for the final chunk and `0x00` for all others. This prevents two attacks:
163188
- **Truncation**: Removing the final chunk and promoting a non-final chunk to the end fails because the non-final chunk was encrypted with `is_final = 0x00` but decryption expects `0x01`.
@@ -169,7 +194,7 @@ The salt and base nonce are generated fresh from `rand::thread_rng()` (backed by
169194
## Testing
170195

171196
```bash
172-
# Run the full test suite (40 tests)
197+
# Run the full test suite (67 tests)
173198
cargo test
174199

175200
# Run benchmarks (HTML reports in target/criterion/)
@@ -194,6 +219,12 @@ The test suite covers:
194219
- **Reserved header bytes tampering detection** (modified padding region)
195220
- Non-deterministic encryption verification
196221
- Stress test with 256 small chunks
222+
- Directory archive pack/unpack roundtrips (both ciphers)
223+
- Empty directory, deeply nested directory, many-files, and binary content roundtrips
224+
- Symlink preservation for valid internal links
225+
- Rejection of symlinks escaping the extraction root (absolute and relative traversal)
226+
- Temp file auto-cleanup on Drop
227+
- Wrong password rejection for encrypted archives
197228

198229
## Dependencies
199230

@@ -210,6 +241,7 @@ The test suite covers:
210241
| `zeroize` | Secure memory wiping |
211242
| `anyhow` | Error handling |
212243
| `rpassword` | Hidden password input |
244+
| `tar` | Directory archiving and extraction |
213245

214246
## License
215247

0 commit comments

Comments
 (0)