
Un motore di crittografia file multi-thread a gigabyte al secondo. Raggiunge un throughput estremo utilizzando una pipeline io_uring triplamente bufferizzata e senza lock, il chunking parallelo di Rayon e AEAD accelerati via hardware (AES-256-GCM / ChaCha20).
# Concryptor
[](https://crates.io/crates/concryptor)
[](LICENSE)
Un motore di crittografia AEAD multi-thread costruito in Rust. Cripta e decripta file con una produttività di gigabyte al secondo utilizzando una pipeline `io_uring` a triplo buffer, l'elaborazione parallela dei chunk tramite Rayon e cifrari ottimizzati in assembly tramite `ring`.
⚠️ **AVVISO: SOFTWARE SPERIMENTALE** ⚠️
**Questo progetto è estremamente recente e attualmente NON è raccomandato per uso in produzione o per dati critici.** Sebbene i primitivi crittografici (AES-256-GCM, ChaCha20-Poly1305 tramite `ring`) e il formato siano solidi, il codice non è stato sottoposto a audit di sicurezza formali o a test estensivi nel mondo reale. Utilizzo a proprio rischio. Per proteggere dati sensibili, considera l'uso di strumenti collaudati come GnuPG, age o OpenSSL fino a quando questo progetto non maturerà.
## Caratteristiche
- **Supporto di due cifrari**: AES-256-GCM (AES-NI hardware) e ChaCha20-Poly1305 tramite `ring` (ottimizzato in assembly)
- **Crittografia parallela**: elaborazione multi-thread dei chunk tramite Rayon su tutti i core della CPU
- **Pipeline io_uring a triplo buffer**: sovrappone l'I/O del kernel e la crittografia lato CPU utilizzando tre pool di buffer rotanti — mentre le scritture di un lotto sono in volo, il lotto successivo viene crittografato da Rayon e le letture del terzo lotto sono in volo. Nessun overhead syscall per chunk, nessuna limitazione di mmap (nessun SIGBUS, nessun esaurimento dello spazio di indirizzamento virtuale)
- **Derivazione della chiave Argon2id**: stiramento standard password-chiave (default 256 MiB di memoria, 3 iterazioni, configurabile con `--memory`)
- **Parametri KDF auto-descrittivi**: costo memoria, iterazioni e parallelismo sono memorizzati nell'intestazione del file crittografato, in modo che la decifratura utilizzi esattamente i parametri scelti al momento della crittografia. I file legacy (sentinel con tutti zeri) vengono gestiti in modo trasparente con i vecchi default di 64 MiB
- **Nonce indicizzati per chunk**: derivazione nonce XOR stile TLS 1.3 previene attacchi di riordinamento dei chunk
- **AAD autenticato dall'intestazione**: l'intera intestazione allineata a 4 KiB è inclusa nell'AAD di ogni chunk, autenticando tutti i campi dell'intestazione (core, parametri KDF e byte riservati) e prevenendo attacchi di troncamento, manipolazione dei campi dell'intestazione e smuggling nei byte riservati
- **Chunk finale stile STREAM**: un flag di chunk finale nell'AAD previene attacchi di troncamento e append (ispirato alla costruzione STREAM)
- **Casualità fresca per file**: salt crittografico di 16 byte e base nonce di 12 byte generati casualmente per ogni crittografia, memorizzati nell'intestazione
- **Crittografia in-place**: `seal_in_place_separate_tag` / `open_in_place` tramite `ring` minimizza l'allocazione nel ciclo caldo
- **Zeroizzazione della password**: chiavi e password vengono cancellate in modo sicuro dalla memoria dopo l'uso
- **O_DIRECT + formato allineato ai settori**: intestazione e slot dei chunk allineati a 4 KiB abilitano l'I/O O_DIRECT, bypassando la cache della pagina del kernel per letture/scritture a velocità DMA su NVMe. I pool di buffer utilizzano `std::alloc` con allineamento a 4096 byte
- **Crittografia di directory**: crittografa intere directory come un unico archivio crittografato. Il packing basato su tar preserva nomi file, permessi, timestamp e struttura delle directory all'interno del testo cifrato. L'estrazione valida contro attacchi di path traversal e symlink escape
- **Formato file auto-descrittivo**: l'intestazione memorizza cifrario, dimensione chunk, dimensione file originale, salt, base nonce e parametri KDF Argon2id
## Prestazioni
Benchmark eseguiti con `cargo bench` (Criterion, 10 campioni per misurazione). La derivazione della chiave è esclusa: i numeri riflettono solo la produttività crittografica pura.
**Hardware:**
- CPU: AMD Ryzen 5 5600X (6c/12t a 3.7 GHz base)
- RAM: 2x 8 GiB DDR4-2666 (doppio canale, 16 GiB totali)
- OS: Linux
**Nota sull'I/O:** Criterion scrive file temporanei in `/tmp`, che su questo sistema è `tmpfs` (supportato da RAM). Con `O_DIRECT`, il kernel non può utilizzare DMA asincrono reale su tmpfs, quindi questi numeri riflettono la produttività del cifrario + overhead di io_uring **senza il vantaggio del DMA bypass**. Su un vero NVMe Gen4, `O_DIRECT` elimina il doppio buffering della cache di pagina e abilita DMA direttamente nei pool di buffer allineati, il che dovrebbe produrre una produttività significativamente più alta.
| Dimensione file | AES-256-GCM Cripta | ChaCha20 Cripta | AES-256-GCM Decripta | ChaCha20 Decripta |
|-----------------|--------------------:|------------------:|--------------------:|-----------------:|
| 64 KiB | 244 MiB/s | 233 MiB/s | 233 MiB/s | 234 MiB/s |
| 1 MiB | 1.08 GiB/s | 882 MiB/s | 1010 MiB/s | 876 MiB/s |
| 16 MiB | 1.10 GiB/s | 923 MiB/s | 1.06 GiB/s | 988 MiB/s |
| 64 MiB | 984 MiB/s | 935 MiB/s | 988 MiB/s | 973 MiB/s |
| 256 MiB | 1.00 GiB/s | 1015 MiB/s | 1.01 GiB/s | 1.02 GiB/s |
Sweep della dimensione chunk (AES-256-GCM, file da 64 MiB):
| Dimensione chunk | Produttività |
|------------------|-------------:|
| 64 KiB | 1.01 GiB/s |
| 256 KiB | 1.05 GiB/s |
| 1 MiB | 1.07 GiB/s |
| 4 MiB | 988 MiB/s |
| 8 MiB | 988 MiB/s |
| 16 MiB | 1.00 GiB/s |
### Caratteristiche delle prestazioni
Il motore utilizza `ring` (AES-NI / NEON / ARMv8-CE ottimizzati in assembly) per le operazioni di cifratura e una pipeline `io_uring` a triplo buffer per l'I/O. Tre pool di buffer preallocati ruotano attraverso la pipeline: mentre le scritture del pool A vengono completate nel kernel, il pool B viene crittografato da Rayon sulla CPU e le letture del pool C vengono inviate al kernel. Questo sovrappone la latenza dell'I/O con il calcolo crittografico.
**Perché AES-256-GCM è più veloce di ChaCha20-Poly1305 su file piccoli:**
Il backend AES-GCM di `ring` sfrutta le istruzioni hardware AES-NI + CLMUL disponibili su x86-64, dandogli un vantaggio hardware rispetto a ChaCha20 (che è un cifrario software). A dimensioni maggiori, entrambi i cifrari convergono a circa 1.0 GiB/s, indicando che il collo di bottiglia si sposta dalla produttività del cifrario all'overhead di invio dell'I/O.
**Perché la produttività di picco è a 1-16 MiB, non a 256 MiB:**
I file piccoli (1-16 MiB) hanno pochi chunk, quindi il parallelismo di Rayon è efficiente e il working set si adatta alla cache. A 64-256 MiB, la pipeline io_uring è completamente attiva (tre lotti in volo), ma l'overhead per SQE e completamento CQE scala con il numero di chunk. Il design a triplo buffer garantisce che I/O e crittografia si sovrappongano, nascondendo parzialmente questo costo.
**Perché circa 1.0 GiB/s e non 10+ GiB/s:**
L'AES-NI moderno può spingere 2-4 GiB/s *per core*. Con 12 thread, la produttività grezza del cifrario potrebbe superare 10 GiB/s. Tre fattori spiegano il divario:
1. **Overhead per SQE di io_uring**: Ogni chunk richiede un SQE di lettura e uno di scrittura. Con 256 chunk per un file da 256 MiB, si tratta di 512 SQE inviati e 512 CQE raccolti. Sebbene io_uring eviti il costo di transizione del kernel per syscall di pread/pwrite, ha comunque un overhead del ring buffer e delle barriere di memoria per SQE.
2. **Profondità della pipeline**: Con `PIPELINE_DEPTH=3`, solo tre lotti ruotano attraverso la pipeline alla volta. Il vero funzionamento in regime stazionario richiede almeno tre lotti; i file che rientrano in uno o due lotti non beneficiano del pipelining.
3. **Effetti della gerarchia della cache**: Il 5600X ha 512 KiB di cache L2 per core e 32 MiB di cache L3 condivisa. Il chunk predefinito di 4 MiB supera la L2, e un lotto di circa 21 chunk (84 MiB di working set attivo) supera ampiamente la L3. Dimensioni di chunk più piccole (64-256 KiB) mostrano una produttività migliore nello sweep dei chunk perché una porzione maggiore del working set rimane nella cache.
**Ciclo di vita e sicurezza dei buffer:**
I pool di buffer vengono allocati una volta tramite `std::alloc::alloc_zeroed` con `Layout::from_size_align(size, 4096)` prima che l'anello io_uring venga creato, e vengono riutilizzati in tutte le iterazioni della pipeline senza riallocazione. Ogni chunk crittografato viene riempito di zeri fino all'allineamento del settore prima della scrittura O_DIRECT. L'anello viene esplicitamente rilasciato prima dei pool di buffer, assicurando che il kernel non faccia mai riferimento a memoria liberata (nessun UAF).
## Installazione
```bash
git clone https://github.com/frogsnot/concryptor.git
cd concryptor
cargo build --release
```
Il binario si troverà in `target/release/concryptor`.
## Utilizzo
### Cripta
```bash
# AES-256-GCM (predefinito), output in myfile.dat.enc
concryptor encrypt myfile.dat
# ChaCha20-Poly1305, percorso output personalizzato
concryptor encrypt myfile.dat --cipher chacha -o encrypted.enc
# Dimensione chunk personalizzata (in MiB)
concryptor encrypt largefile.iso --chunk-size 8
# KDF più forte (costo memoria 512 MiB)
concryptor encrypt secrets.tar --memory 512
# Non interattivo (salta richiesta password)
concryptor encrypt myfile.dat -p "password"
```
> **Nota di sicurezza:** `--password` / `-p` passa la password come argomento CLI, visibile nell'output di `ps` e nella cronologia della shell. Per uso interattivo, omettilo per ottenere il prompt nascosto sicuro. Per scripting, preferisci cancellare la cronologia successivamente o utilizzare un wrapper che legga da un descrittore di file.
### Cripta una directory
```bash
# Cripta una directory (rilevamento automatico, produce mydir.tar.enc)
concryptor encrypt mydir/
# Con cifrario personalizzato e output
concryptor encrypt mydir/ --cipher chacha -o secrets.enc
```
La crittografia di directory crea un archivio tar temporaneo (`.concryptor-*.tar`, permessi 0600, nome CSPRNG), lo cripta, quindi elimina automaticamente il file temporaneo. Nomi file, struttura delle directory, permessi e timestamp sono tutti all'interno del payload crittografato.
### Decripta
```bash
# Rimuove automaticamente l'estensione .enc
concryptor decrypt myfile.dat.enc
# Percorso output personalizzato
concryptor decrypt encrypted.enc -o restored.dat
# Non interattivo
concryptor decrypt myfile.dat.enc -p "password"
```
### Decripta ed estrae una directory
```bash
# Decripta ed estrae in un unico passaggio (rimuove automaticamente .tar.enc -> nome directory)
concryptor decrypt mydir.tar.enc --extract
# Flag breve, directory output personalizzata
concryptor decrypt mydir.tar.enc -x -o restored_dir/
```
Senza `--extract`, decriptare un archivio di directory produce il file `.tar` intermedio, che puoi ispezionare o estrarre manualmente.
### Aiuto
```bash
concryptor --help
concryptor encrypt --help
concryptor decrypt --help
```
## Formato file
Tutti i valori sono little-endian. L'intestazione occupa un intero settore da 4 KiB; ogni slot di chunk crittografato viene riempito fino al successivo limite di 4 KiB. Questo garantisce che ogni offset e dimensione di I/O sia allineata al settore per `O_DIRECT`.
```
Offset Dimensione Campo
------ ----------- ---------------------
0 10 Byte magici "CONCRYPTOR"
10 1 Versione formato (4)
11 1 Tipo cifrario (0 = AES-256-GCM, 1 = ChaCha20-Poly1305)
12 4 Dimensione chunk (byte, LE)
16 8 Dimensione file originale (byte, LE)
24 16 Salt Argon2 (casuale crittografico, unico per file)
40 12 Base nonce (casuale crittografico, unico per file)
52 4 m_cost Argon2 in KiB (LE, 0 = legacy 64 MiB)
56 4 t_cost / iterazioni Argon2 (LE, 0 = legacy 3)
60 4 p_cost / parallelismo Argon2 (LE, 0 = legacy 4)
64 4032 Riservato (riempito con zeri fino a 4096 byte)
4096 ... [Chunk 0: testo cifrato + tag 16 byte + riempimento zeri fino al limite del settore]
[Chunk 1: testo cifrato + tag 16 byte + riempimento zeri fino al limite del settore]
...
```
Per chunk da 4 MiB: ogni slot su disco è `ceil((4194304 + 16) / 4096) * 4096 = 4198400` byte (4080 byte di riempimento per chunk). I 4032 byte riservati nell'intestazione sono disponibili per funzionalità future (slot di chiave asimmetrica, metadati, ecc.).
Il salt e il base nonce vengono generati freschi da `rand::rng()` (supportato dal CSPRNG del sistema operativo) ad ogni crittografia. Riutilizzare una password tra file è sicuro perché salt diversi producono chiavi Argon2id diverse e base nonce diversi producono nonce per chunk diversi.
## Progettazione della sicurezza
- **Derivazione del nonce**: `chunk_nonce = base_nonce XOR chunk_index` (stile TLS 1.3). Scambiare i chunk causa il fallimento della decifratura perché il nonce alla posizione N non corrisponderà al nonce utilizzato per crittografare il chunk originariamente alla posizione M. Nota: la derivazione del nonce basata su XOR ha una debolezza teorica quando la *stessa chiave* viene utilizzata su più flussi (base nonce distinti possono produrre spazi di nonce sovrapposti). Questo non si applica a Concryptor perché ogni crittografia genera un salt casuale a 128 bit fresco, producendo una chiave Argon2id unica per file. L'unicità del nonce è rilevante solo sotto la stessa chiave e la probabilità di riutilizzo della chiave è ~2^-128 per coppia di file.
- **AAD autenticato dall'intestazione**: ogni chiamata AEAD per chunk utilizza `AAD = full_aligned_header (4096) || chunk_index (8 LE) || is_final (1)` (4105 byte totali). L'intero settore di intestazione da 4 KiB (campi principali, parametri KDF e padding riservato) è vincolato nel tag di autenticazione di ogni chunk. Modificare *qualsiasi* byte dell'intestazione (tipo cifrario, dimensione chunk, dimensione originale, salt, nonce, parametri KDF o padding riservato) invalida tutti i chunk. Questo previene attacchi di troncamento in cui un avversario modifica `original_size` e rimuove i chunk finali, e impedisce anche lo smuggling di dati nella regione di padding riservato. I file legacy v3 vengono decifrati con AAD da 52 byte per compatibilità all'indietro; non è possibile alcun downgrade da v4 a v3 perché il byte di versione stesso è all'interno dell'AAD autenticato.
- **Indicatore di chunk finale stile STREAM**: l'ultimo byte dell'AAD è `0x01` per il chunk finale e `0x00` per tutti gli altri. Questo previene due attacchi:
- **Troncamento**: rimuovere il chunk finale e promuovere un chunk non finale alla fine fallisce perché il chunk non finale è stato crittografato con `is_final = 0x00` ma la decifratura si aspetta `0x01`.
- **Estensione**: aggiungere chunk falsificati fallisce perché l'attaccante non può produrre un tag valido per `is_final = 0x01` senza la chiave.
- **Casualità fresca per file**: un salt di 16 byte e un base nonce di 12 byte vengono estratti dal CSPRNG del sistema operativo (`rand::rng()`) per ogni crittografia. Due crittografie dello stesso file con la stessa password producono testo cifrato completamente diverso. Il riutilizzo del nonce (che è catastrofico per AES-GCM) è evitato per costruzione.
- **Derivazione della chiave**: Argon2id con costo memoria configurabile (default 256 MiB, regolabile con `--memory`), 3 iterazioni di tempo, parallelismo 4. Il default di 256 MiB è 4 volte il minimo OWASP ed è costoso per attaccanti GPU/FPGA/ASIC. I parametri KDF sono memorizzati nell'intestazione del file (byte 52-63), rendendo i file auto-descrittivi — la decifratura utilizza sempre i parametri corretti indipendentemente dalle impostazioni predefinite correnti. Se i byte 52-63 sono tutti zero (file legacy pre-parametri-KDF), vengono applicati i vecchi default di 64 MiB / 3 / 4.
- **Zeroizzazione**: le chiavi di crittografia vengono azzerate immediatamente dopo la costruzione del cifrario. Le password vengono azzerate dopo l'uso.
## Test
```bash
# Esegui l'intera suite di test (67 test)
cargo test
# Esegui i benchmark (report HTML in target/criterion/)
cargo bench
# Filtra i benchmark
cargo bench -- "encrypt/AES"
cargo bench -- "chunk_sweep"
```
La suite di test copre:
- Roundtrip serializzazione/deserializzazione intestazione
- Determinismo e sensibilità della derivazione della chiave
- Proprietà di unicità e identità del nonce
- Roundtrip crittografia/decrittografia per entrambi i cifrari per dimensioni di file (vuoto, 1 byte, casi limite, multi-chunk)
- Rifiuto di password errata
- Rilevamento di manomissioni (testo cifrato capovolto, tag corrotti, salt corrotto, file troncati)
- Rilevamento di attacchi di riordinamento dei chunk
- Rilevamento di disallineamento del tipo di cifrario
- **Rilevamento di attacchi di troncamento** (`original_size` modificato + chunk rimossi)
- **Rilevamento di manipolazione dei campi dell'intestazione** (`chunk_size` modificato)
- **Rilevamento di manomissione dei byte riservati dell'intestazione** (regione di padding modificata)
- Verifica di crittografia non deterministica
- Stress test con 256 chunk piccoli
- Roundtrip di archiviazione/estrazione di directory (entrambi i cifrari)
- Roundtrip di directory vuota, directory annidata profondamente, molti file e contenuto binario
- Conservazione dei symlink per collegamenti interni validi
- Rifiuto di symlink che escono dalla radice di estrazione (traversal assoluto e relativo)
- Pulizia automatica dei file temporanei su Drop
- Rifiuto di password errata per archivi crittografati
## Dipendenze
| Crate | Scopo |
|-----------|-------|
| `ring` | AES-256-GCM e ChaCha20-Poly1305 AEAD ottimizzati in assembly |
| `io-uring`| Interfaccia Linux io_uring per I/O asincrono di lettura/scrittura |
| `libc` | Flag O_DIRECT e pread/pwrite allineati per I/O dell'intestazione |
| `argon2` | Derivazione chiave Argon2id |
| `rayon` | Elaborazione parallela dei chunk |
| `clap` | Parsing degli argomenti CLI |
| `indicatif` | Barra di progresso nel terminale |
| `rand` | Generazione di numeri casuali crittografici |
| `zeroize` | Cancellazione sicura della memoria |
| `anyhow` | Gestione degli errori |
| `rpassword` | Input password nascosto |
| `tar` | Archiviazione ed estrazione di directory |
## Installazione
```bash
# Da crates.io (raccomandato)
cargo install concryptor
# Dal sorgente
git clone https://github.com/FrogSnot/Concryptor
cd Concryptor
cargo build --release
# Il binario si trova in target/release/concryptor
```
## Licenza
Questo progetto è concesso in licenza con [GNU Affero General Public License v3.0](https://github.com/frogsnot/concryptor/blob/master/LICENSE).