
Un moteur de chiffrement de fichiers multi-threadé capable de traiter des gigaoctets par seconde. Il atteint un débit extrême grâce à un pipeline io_uring sans verrou et à triple tampon, un découpage parallèle via Rayon, et des AEAD accélérés matériellement (AES-256-GCM / ChaCha20).
Un moteur de chiffrement AEAD multi-threadé construit en Rust. Chiffre et déchiffre des fichiers à un débit de gigaoctets par seconde grâce à un pipeline io_uring triple-bufferisé, un traitement par blocs parallèle via Rayon, et des ciphers optimisés en assembleur via ring.
⚠️ AVERTISSEMENT : LOGICIEL EXPÉRIMENTAL ⚠️
Ce projet est extrêmement récent et n'est actuellement PAS recommandé pour une utilisation en production ou pour des données critiques. Bien que les primitives cryptographiques (AES-256-GCM, ChaCha20-Poly1305 via ring) et la conception du format soient solides, la base de code n'a pas fait l'objet d'audits de sécurité formels ni de tests approfondis en conditions réelles. Utilisez-le à vos risques et périls. Pour protéger des données sensibles, privilégiez des outils éprouvés comme GnuPG, age ou OpenSSL jusqu'à ce que ce projet mûrisse.
ring (optimisé en assembleur)--memory)seal_in_place_separate_tag / open_in_place via ring minimise les allocations dans la boucle chaudeO_DIRECT, contournant le cache de page du noyau pour des lectures/écritures à vitesse DMA sur NVMe. Les pools de tampons utilisent std::alloc avec un alignement de 4096 octetsBenchmarké avec cargo bench (Criterion, 10 échantillons par mesure). La dérivation de clé est exclue — les chiffres reflètent uniquement le débit cryptographique pur.
Matériel :
Note sur les E/S : Criterion écrit des fichiers temporaires dans /tmp, qui sur ce système est tmpfs (backé par la RAM). Avec O_DIRECT, le noyau ne peut pas utiliser le véritable DMA asynchrone sur tmpfs, donc ces chiffres reflètent le débit du cipher + la surcharge de io_uring sans l'avantage du contournement DMA. Sur un véritable disque NVMe Gen4, O_DIRECT élimine la double mise en cache du cache de page et permet le DMA directement dans les pools de tampons alignés, ce qui devrait donner un débit nettement plus élevé.
Balayage de la taille de bloc (AES-256-GCM, fichier de 64 Mio) :
Le moteur utilise ring (AES-NI / NEON / ARMv8-CE optimisé en assembleur) pour les opérations de cipher et un pipeline io_uring triple-bufferisé pour les E/S. Trois pools de tampons pré-alloués tournent dans le pipeline : pendant que les écritures du pool A se terminent dans le noyau, le pool B est chiffré par Rayon sur le CPU, et les lectures du pool C sont soumises au noyau. Cela superpose la latence des E/S avec le calcul cryptographique.
Pourquoi AES-256-GCM est plus rapide que ChaCha20-Poly1305 sur les petits fichiers :
Le backend AES-GCM de ring exploite les instructions matérielles AES-NI + CLMUL disponibles sur x86-64, ce qui lui donne un avantage matériel par rapport à ChaCha20 (qui est un cipher logiciel). Pour les grandes tailles, les deux ciphers convergent vers ~1,0 Gio/s, indiquant que le goulot d'étranglement passe du débit du cipher à la surcharge de soumission des E/S.
Pourquoi le débit de pointe est à 1-16 Mio, pas à 256 Mio : Les petits fichiers (1-16 Mio) ont peu de blocs, donc le parallélisme Rayon est efficace et l'ensemble de travail tient dans le cache. À 64-256 Mio, le pipeline io_uring est pleinement actif (trois lots en vol), mais la surcharge de soumission SQE et de récupération CQE augmente avec le nombre de blocs. La conception triple-buffer garantit le chevauchement des E/S et de la cryptographie, masquant partiellement ce coût.
Pourquoi ~1,0 Gio/s et pas 10+ Gio/s : L'AES-NI moderne peut pousser 2-4 Gio/s par cœur. Avec 12 threads, le débit brut du cipher pourrait dépasser 10 Gio/s. Trois facteurs expliquent l'écart :
PIPELINE_DEPTH=3, seulement trois lots tournent dans le pipeline à tout moment. Un véritable régime permanent de chevauchement nécessite au moins trois lots ; les fichiers qui tiennent dans un ou deux lots ne bénéficient pas du pipeline.Cycle de vie et sécurité des tampons :
Les pools de tampons sont alloués une fois via std::alloc::alloc_zeroed avec Layout::from_size_align(size, 4096) avant que l'anneau io_uring ne soit créé, et sont réutilisés à travers toutes les itérations du pipeline sans réallocation. Chaque bloc chiffré est rembourré de zéros jusqu'à l'alignement de secteur avant l'écriture O_DIRECT. L'anneau est explicitement libéré avant les pools de tampons, garantissant que le noyau ne référence jamais de mémoire libérée (pas d'UAF).
git clone https://github.com/frogsnot/concryptor.git
cd concryptor
cargo build --release
Le binaire se trouve dans target/release/concryptor.
# AES-256-GCM (par défaut), sortie vers myfile.dat.enc
concryptor encrypt myfile.dat
# ChaCha20-Poly1305, chemin de sortie personnalisé
concryptor encrypt myfile.dat --cipher chacha -o encrypted.enc
# Taille de bloc personnalisée (en Mio)
concryptor encrypt largefile.iso --chunk-size 8
# KDF plus fort (coût mémoire 512 Mio)
concryptor encrypt secrets.tar --memory 512
# Non interactif (saute l'invite de mot de passe)
concryptor encrypt myfile.dat -p "password"
Note de sécurité :
--password/-ptransmet le mot de passe comme argument CLI, visible dans la sortiepset l'historique du shell. Pour une utilisation interactive, omettez-le pour obtenir l'invite sécurisée masquée. Pour les scripts, privilégiez l'effacement de l'historique après ou l'utilisation d'un wrapper qui lit à partir d'un descripteur de fichier.
# Chiffre un répertoire (détection automatique, produit mydir.tar.enc)
concryptor encrypt mydir/
# Avec cipher et sortie personnalisés
concryptor encrypt mydir/ --cipher chacha -o secrets.enc
Le chiffrement de répertoire crée une archive tar temporaire (.concryptor-*.tar, permissions 0600, nommé avec CSPRNG), la chiffre, puis supprime automatiquement le fichier temporaire. Les noms de fichiers, la structure des répertoires, les permissions et les horodatages sont tous à l'intérieur de la charge utile chiffrée.
# Supprime automatiquement l'extension .enc
concryptor decrypt myfile.dat.enc
# Chemin de sortie personnalisé
concryptor decrypt encrypted.enc -o restored.dat
# Non interactif
concryptor decrypt myfile.dat.enc -p "password"
# Déchiffre et extrait en une seule étape (supprime automatiquement .tar.enc -> nom du répertoire)
concryptor decrypt mydir.tar.enc --extract
# Option courte, répertoire de sortie personnalisé
concryptor decrypt mydir.tar.enc -x -o restored_dir/
Sans --extract, le déchiffrement d'une archive de répertoire produit le fichier .tar intermédiaire, que vous pouvez inspecter ou extraire manuellement.
concryptor --help
concryptor encrypt --help
concryptor decrypt --help
Toutes les valeurs sont en little-endian. L'en-tête occupe un secteur complet de 4 Kio ; chaque emplacement de bloc chiffré est rembourré jusqu'à la limite de 4 Kio suivante. Cela garantit que chaque décalage et taille d'E/S est aligné sur le secteur pour O_DIRECT.
Décalage Taille Champ
-------- ------ ---------------------
0 10 Octets magiques "CONCRYPTOR"
10 1 Version du format (4)
11 1 Type de cipher (0 = AES-256-GCM, 1 = ChaCha20-Poly1305)
12 4 Taille de bloc (octets, LE)
16 8 Taille originale du fichier (octets, LE)
24 16 Sel Argon2 (cryptographiquement aléatoire, unique par fichier)
40 12 Nonce de base (cryptographiquement aléatoire, unique par fichier)
52 4 Coût m_cost Argon2 en Kio (LE, 0 = héritage 64 Mio)
56 4 Coût t_cost / itérations Argon2 (LE, 0 = héritage 3)
60 4 Coût p_cost / parallélisme Argon2 (LE, 0 = héritage 4)
64 4032 Réservé (rembourré de zéros jusqu'à 4096 octets)
4096 ... [Bloc 0 : texte chiffré + étiquette de 16 octets + rembourrage de zéros jusqu'à la limite de secteur]
[Bloc 1 : texte chiffré + étiquette de 16 octets + rembourrage de zéros jusqu'à la limite de secteur]
...
Pour des blocs de 4 Mio : chaque emplacement disque fait ceil((4194304 + 16) / 4096) * 4096 = 4198400 octets (4080 octets de rembourrage par bloc). Les 4032 octets réservés dans l'en-tête sont disponibles pour des fonctionnalités futures (emplacements de clé asymétrique, métadonnées, etc.).
Le sel et le nonce de base sont générés fraîchement à partir de rand::rng() (backé par le CSPRNG du système) à chaque chiffrement. Réutiliser un mot de passe entre fichiers est sûr car des sels différents produisent des clés Argon2id différentes, et des nonces de base différents produisent des nonces par bloc différents.
nonce_bloc = nonce_de_base XOR index_bloc (style TLS 1.3). Échanger des blocs provoque un échec de déchiffrement car le nonce à la position N ne correspondra pas au nonce utilisé pour chiffrer le bloc originellement à la position M. Note : la dérivation de nonce par XOR a une faiblesse théorique lorsque la même clé est utilisée à travers plusieurs flux (des nonces de base distincts peuvent produire des espaces de nonces qui se chevauchent). Cela ne s'applique pas à Concryptor car chaque chiffrement génère un sel aléatoire frais de 128 bits, produisant une clé Argon2id unique par fichier. L'unicité du nonce n'a d'importance que sous la même clé, et la probabilité de réutilisation de clé est ~2^-128 par paire de fichiers.AAD = en-tête_complet_aligné (4096) || index_bloc (8 LE) || est_final (1) (4105 octets au total). L'ensemble du secteur d'en-tête de 4 Kio (champs de base, paramètres KDF et rembourrage réservé) est lié à l'étiquette d'authentification de chaque bloc. Modifier un quelconque octet de l'en-tête (type de cipher, taille de bloc, taille originale, sel, nonce, paramètres KDF ou rembourrage réservé) invalide tous les blocs. Cela empêche les attaques de troncature où un adversaire édite original_size et supprime les blocs de fin, et empêche également la contrebande de données dans la région de rembourrage réservé. Les fichiers hérités v3 sont déchiffrés avec un AAD de 52 octets pour la rétrocompatibilité ; aucun downgrade de v4 à v3 n'est possible car l'octet de version lui-même est dans l'AAD authentifié.0x01 pour le dernier bloc et 0x00 pour tous les autres. Cela empêche deux attaques :
# Exécute la suite de tests complète (67 tests)
cargo test
# Exécute les benchmarks (rapports HTML dans target/criterion/)
cargo bench
# Filtrer les benchmarks
cargo bench -- "encrypt/AES"
cargo bench -- "chunk_sweep"
La suite de tests couvre :
original_size modifié + blocs supprimés)chunk_size modifié)# Depuis crates.io (recommandé)
cargo install concryptor
# Depuis les sources
git clone https://github.com/FrogSnot/Concryptor
cd Concryptor
cargo build --release
# Le binaire se trouve dans target/release/concryptor
Ce projet est sous licence GNU Affero General Public License v3.0.
| Taille du fichier | Chiffrement AES-256-GCM | Chiffrement ChaCha20 | Déchiffrement AES-256-GCM | Déchiffrement ChaCha20 |
|---|
| 64 Kio | 244 Mio/s | 233 Mio/s | 233 Mio/s | 234 Mio/s |
| 1 Mio | 1,08 Gio/s | 882 Mio/s | 1010 Mio/s | 876 Mio/s |
| 16 Mio | 1,10 Gio/s | 923 Mio/s | 1,06 Gio/s | 988 Mio/s |
| 64 Mio | 984 Mio/s | 935 Mio/s | 988 Mio/s | 973 Mio/s |
| 256 Mio | 1,00 Gio/s | 1015 Mio/s | 1,01 Gio/s | 1,02 Gio/s |
| Taille de bloc | Débit |
|---|
| 64 Kio | 1,01 Gio/s |
| 256 Kio | 1,05 Gio/s |
| 1 Mio | 1,07 Gio/s |
| 4 Mio | 988 Mio/s |
| 8 Mio | 988 Mio/s |
| 16 Mio | 1,00 Gio/s |
is_final = 0x000x01is_final = 0x01 sans la clé.rand::rng()) pour chaque chiffrement. Deux chiffrements du même fichier avec le même mot de passe produisent un texte chiffré complètement différent. La réutilisation du nonce (catastrophique pour AES-GCM) est évitée par construction.--memory), 3 itérations temporelles, parallélisme de 4. La valeur par défaut de 256 Mio est 4× le minimum OWASP et coûteuse pour les attaquants GPU/FPGA/ASIC. Les paramètres KDF sont stockés dans l'en-tête du fichier (octets 52-63), rendant les fichiers auto-descriptifs — le déchiffrement utilise toujours les bons paramètres quels que soient les paramètres par défaut actuels. Si les octets 52-63 sont tous à zéro (fichiers hérités avant les paramètres KDF), les anciennes valeurs par défaut de 64 Mio / 3 / 4 sont appliquées.| Crate | Objectif |
|---|
ring | AES-256-GCM et ChaCha20-Poly1305 AEAD optimisés en assembleur |
io-uring | Interface Linux io_uring pour les E/S asynchrones lecture/écriture |
libc | Drapeau O_DIRECT et pread/pwrite aligné pour les E/S d'en-tête |
argon2 | Dérivation de clé Argon2id |
rayon | Traitement de blocs en parallèle de données |
clap | Analyse d'arguments CLI |
indicatif | Barre de progression dans le terminal |
rand | Génération de nombres aléatoires cryptographiques |
zeroize | Effacement sécurisé de la mémoire |
anyhow | Gestion des erreurs |
rpassword | Saisie de mot de passe masquée |
tar | Archivage et extraction de répertoires |