
Eine Gigabyte-pro-Sekunde, mehrthreadige Dateiverschlüsselungs-Engine. Erzielt extreme Durchsatzraten durch eine sperrfreie, dreifach gepufferte io_uring-Pipeline, paralleles Rayon-Chunking und hardwarebeschleunigte AEADs (AES-256-GCM / ChaCha20).
Eine multi-threaded AEAD-Verschlüsselungs-Engine, entwickelt in Rust. Verschlüsselt und entschlüsselt Dateien mit Gigabyte-pro-Sekunde-Durchsatz mittels einer dreifach gepufferten io_uring-Pipeline, paralleler Chunk-Verarbeitung über Rayon und assembleroptimierten Chiffren via ring.
⚠️ HINWEIS: EXPERIMENTELLE SOFTWARE ⚠️
Dieses Projekt ist extrem neu und wird derzeit NICHT für den produktiven oder sicherheitskritischen Einsatz empfohlen. Obwohl die kryptografischen Primitive (AES-256-GCM, ChaCha20-Poly1305 via ring) und das Formatdesign solide sind, wurde die Codebasis keinen formellen Sicherheitsaudits oder umfangreichen Tests unter realen Bedingungen unterzogen. Nutzung auf eigene Gefahr. Für den Schutz sensibler Daten sollten Sie auf bewährte Werkzeuge wie GnuPG, age oder OpenSSL zurückgreifen, bis dieses Projekt ausgereift ist.
ring (assembleroptimiert)--memory)seal_in_place_separate_tag / open_in_place via ring minimiert Allokationen in der heißen SchleifeO_DIRECT-I/O, das den Kernel-Page-Cache umgeht und DMA-schnelle Lese-/Schreibvorgänge auf NVMe ermöglicht. Pufferpools verwenden std::alloc mit 4096-Byte-AusrichtungGemessen mit cargo bench (Criterion, 10 Samples pro Messung). Schlüsselableitung ausgeschlossen – Zahlen beziehen sich nur auf reinen Krypto-Durchsatz.
Hardware:
Hinweis zu I/O: Criterion schreibt temporäre Dateien nach /tmp, was auf diesem System tmpfs (RAM-gestützt) ist. Mit O_DIRECT kann der Kernel kein echtes asynchrones DMA auf tmpfs verwenden, daher spiegeln diese Zahlen den Chiffre-Durchsatz + io_uring-Overhead ohne den DMA-Umgehungsvorteil wider. Auf einem echten Gen4-NVMe-Laufwerk eliminiert O_DIRECT das Page-Cache-Doppelpuffern und ermöglicht DMA direkt in die ausgerichteten Pufferpools, was einen signifikant höheren Durchsatz ergeben sollte.
| Dateigröße | AES-256-GCM Verschl. | ChaCha20 Verschl. | AES-256-GCM Entschl. | ChaCha20 Entschl. |
|---|---|---|---|---|
| 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 |
Chunk-Größen-Sweep (AES-256-GCM, 64 MiB Datei):
| Chunk-Größe | Durchsatz |
|---|---|
| 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 |
Die Engine verwendet ring (assembleroptimiertes AES-NI / NEON / ARMv8-CE) für Chiffre-Operationen und eine dreifach gepufferte io_uring-Pipeline für I/O. Drei vorab allozierte Pufferpools rotieren durch die Pipeline: während die Schreibvorgänge von Pool A im Kernel abgeschlossen werden, wird Pool B von Rayon auf der CPU verschlüsselt, und die Lesevorgänge von Pool C werden an den Kernel übergeben. Dies überlappt die I/O-Latenz mit der Krypto-Berechnung.
Warum AES-256-GCM bei kleinen Dateien schneller ist als ChaCha20-Poly1305:
Das AES-GCM-Backend von ring nutzt AES-NI + CLMUL-Hardwareanweisungen, die auf x86-64 verfügbar sind, was ihm einen Hardwarevorteil gegenüber ChaCha20 (einer Software-Chiffre) verschafft. Bei größeren Größen konvergieren beide Chiffren auf ~1,0 GiB/s, was darauf hindeutet, dass der Engpass vom Chiffre-Durchsatz zum I/O-Submission-Overhead wechselt.
Warum der Spitzendurchsatz bei 1-16 MiB liegt, nicht bei 256 MiB: Kleine Dateien (1-16 MiB) haben wenige Chunks, daher ist die Rayon-Parallelität effizient und der Working Set passt in den Cache. Bei 64-256 MiB ist die io_uring-Pipeline voll aktiv (drei Batches in der Luft), aber der Overhead pro SQE-Submission und CQE-Completion skaliert mit der Chunk-Anzahl. Das Dreifachpuffer-Design stellt sicher, dass sich I/O und Krypto überlappen und verdeckt diese Kosten teilweise.
Warum nur etwa 1,0 GiB/s und nicht 10+ GiB/s: Moderne AES-NI kann 2-4 GiB/s pro Kern erreichen. Mit 12 Threads könnte der reine Chiffre-Durchsatz 10 GiB/s überschreiten. Drei Faktoren erklären die Lücke:
PIPELINE_DEPTH=3 rotieren nur drei Batches gleichzeitig durch die Pipeline. Ein echter Einschwing-Overlap erfordert mindestens drei Batches; Dateien, die in ein oder zwei Batches passen, profitieren nicht von der Pipeline.Puffer-Lebenszyklus und Sicherheit:
Pufferpools werden einmalig via std::alloc::alloc_zeroed mit Layout::from_size_align(size, 4096) alloziert, bevor der io_uring-Ring erstellt wird, und werden über alle Pipeline-Iterationen hinweg ohne Neuzuweisung wiederverwendet. Jeder verschlüsselte Chunk wird vor dem O_DIRECT-Schreibvorgang mit Nullen auf Sektorgrenze aufgefüllt. Der Ring wird explizit vor den Pufferpools gelöscht, sodass der Kernel niemals auf den freigegebenen Speicher verweist (kein Use-After-Free).
git clone https://github.com/frogsnot/concryptor.git
cd concryptor
cargo build --release
Die Binärdatei befindet sich unter target/release/concryptor.
# AES-256-GCM (Standard), Ausgabe nach myfile.dat.enc
concryptor encrypt myfile.dat
# ChaCha20-Poly1305, eigener Ausgabepfad
concryptor encrypt myfile.dat --cipher chacha -o encrypted.enc
# Eigene Chunk-Größe (in MiB)
concryptor encrypt largefile.iso --chunk-size 8
# Stärkeres KDF (512 MiB Speicherkosten)
concryptor encrypt secrets.tar --memory 512
# Nicht-interaktiv (überspringt Passwortabfrage)
concryptor encrypt myfile.dat -p "passwort"
Sicherheitshinweis:
--password/-pübergibt das Passwort als CLI-Argument, das in derps-Ausgabe und der Shell-Historie sichtbar ist. Für interaktive Nutzung weglassen, um die sichere versteckte Eingabeaufforderung zu erhalten. Für Skripte empfehlen wir, die Historie danach zu löschen oder einen Wrapper zu verwenden, der aus einem Dateideskriptor liest.
# Verzeichnis verschlüsseln (automatische Erkennung, erzeugt mydir.tar.enc)
concryptor encrypt mydir/
# Mit eigener Chiffre und Ausgabe
concryptor encrypt mydir/ --cipher chacha -o secrets.enc
Die Verzeichnisverschlüsselung erstellt ein temporäres Tar-Archiv (.concryptor-*.tar, Berechtigung 0600, CSPRNG-benannt), verschlüsselt es und löscht die temporäre Datei automatisch. Dateinamen, Verzeichnisstruktur, Berechtigungen und Zeitstempel sind alle im verschlüsselten Payload enthalten.
# Entfernt automatisch die .enc-Erweiterung
concryptor decrypt myfile.dat.enc
# Eigener Ausgabepfad
concryptor decrypt encrypted.enc -o restored.dat
# Nicht-interaktiv
concryptor decrypt myfile.dat.enc -p "passwort"
# Entschlüsseln und in einem Schritt extrahieren (entfernt automatisch .tar.enc -> Verzeichnisname)
concryptor decrypt mydir.tar.enc --extract
# Kurzflag, eigenes Ausgabeverzeichnis
concryptor decrypt mydir.tar.enc -x -o restored_dir/
Ohne --extract erzeugt die Entschlüsselung eines Verzeichnisarchivs die Zwischendatei .tar, die Sie manuell inspizieren oder extrahieren können.
concryptor --help
concryptor encrypt --help
concryptor decrypt --help
Alle Werte sind Little-Endian. Der Header belegt einen vollständigen 4-KiB-Sektor; jeder verschlüsselte Chunk-Slot wird auf die nächste 4-KiB-Grenze aufgefüllt. Dadurch sind alle Offsets und I/O-Größen sektorausgerichtet für O_DIRECT.
Offset Größe Feld
------ ----- ---------------------
0 10 Magic Bytes "CONCRYPTOR"
10 1 Formatversion (4)
11 1 Chiffretyp (0 = AES-256-GCM, 1 = ChaCha20-Poly1305)
12 4 Chunk-Größe (Bytes, LE)
16 8 Original-Dateigröße (Bytes, LE)
24 16 Argon2-Salz (kryptografisch zufällig, eindeutig pro Datei)
40 12 Basis-Nonce (kryptografisch zufällig, eindeutig pro Datei)
52 4 Argon2 m_cost in KiB (LE, 0 = Legacy 64 MiB)
56 4 Argon2 t_cost / Iterationen (LE, 0 = Legacy 3)
60 4 Argon2 p_cost / Parallelität (LE, 0 = Legacy 4)
64 4032 Reserviert (mit Nullen auf 4096 Bytes aufgefüllt)
4096 ... [Chunk 0: Chiffrat + 16-Byte-Tag + Null-Auffüllung auf Sektorgrenze]
[Chunk 1: Chiffrat + 16-Byte-Tag + Null-Auffüllung auf Sektorgrenze]
...
Bei 4-MiB-Chunks: Jeder Datenträger-Slot ist ceil((4194304 + 16) / 4096) * 4096 = 4198400 Bytes (4080 Bytes Auffüllung pro Chunk). Die 4032 reservierten Bytes im Header stehen für zukünftige Funktionen zur Verfügung (asymmetrische Schlüsselslots, Metadaten usw.).
Der Salz und die Basis-Nonce werden bei jeder Verschlüsselung frisch aus rand::rng() (unterstützt durch den OS-CSPRNG) generiert. Die Wiederverwendung eines Passworts über mehrere Dateien hinweg ist sicher, da unterschiedliche Salze unterschiedliche Argon2id-Schlüssel und unterschiedliche Basis-Noncen unterschiedliche Pro-Chunk-Noncen erzeugen.
chunk_nonce = base_nonce XOR chunk_index (TLS 1.3-Art). Das Vertauschen von Chunks führt zu einem Entschlüsselungsfehler, da die Nonce an Position N nicht mit der Nonce übereinstimmt, die zum Verschlüsseln des ursprünglichen Chunks an Position M verwendet wurde. Hinweis: Die XOR-basierte Nonce-Ableitung hat eine theoretische Schwachstelle, wenn der gleiche Schlüssel über mehrere Streams hinweg verwendet wird (unterschiedliche Basis-Noncen können sich überschneidende Nonce-Räume erzeugen). Dies trifft auf Concryptor nicht zu, da jede Verschlüsselung ein frisches 128-Bit-Zufallssalz erzeugt, das einen eindeutigen Argon2id-Schlüssel pro Datei liefert. Nonce-Eindeutigkeit ist nur unter demselben Schlüssel relevant, und die Wahrscheinlichkeit einer Schlüsselwiederverwendung beträgt ~2^-128 pro Dateipaar.AAD = full_aligned_header (4096) || chunk_index (8 LE) || is_final (1) (insgesamt 4105 Bytes). Der gesamte 4-KiB-Header-Sektor (Kernfelder, KDF-Parameter und reservierte Auffüllung) wird in das Authentifizierungstag jedes Chunks eingebunden. Das Ändern irgendeines Header-Bytes (Chiffretyp, Chunk-Größe, Originalgröße, Salz, Nonce, KDF-Parameter oder reservierte Auffüllung) macht alle Chunks ungültig. Dies verhindert Trunkierungsangriffe, bei denen ein Angreifer original_size bearbeitet und nachfolgende Chunks entfernt, und verhindert auch das Schmuggeln von Daten in den reservierten Auffüllungsbereich. Legacy-v3-Dateien werden aus Gründen der Abwärtskompatibilität mit 52-Byte-AAD entschlüsselt; ein Downgrade von v4 auf v3 ist nicht möglich, da das Versionsbyte selbst in der authentifizierten AAD enthalten ist.0x01 für den finalen Chunk und 0x00 für alle anderen. Dies verhindert zwei Angriffe:
is_final = 0x00 verschlüsselt wurde, die Entschlüsselung aber 0x01 erwartet.is_final = 0x01 erzeugen kann.rand::rng()) bezogen. Zwei Verschlüsselungen derselben Datei mit demselben Passwort erzeugen völlig unterschiedliche Chiffrate. Nonce-Wiederverwendung (die für AES-GCM katastrophal ist) wird durch die Konstruktion vermieden.--memory), 3 Zeititerationen, Parallelität 4. Der Standard 256 MiB ist das 4-fache des OWASP-Minimums und teuer für GPU/FPGA/ASIC-Angreifer. KDF-Parameter werden im Dateiheader (Bytes 52-63) gespeichert, sodass Dateien selbstbeschreibend sind – die Entschlüsselung verwendet immer die korrekten Parameter, unabhängig von den aktuellen Standardwerten. Wenn Bytes 52-63 alle Null sind (Legacy-Dateien vor KDF-Parametern), werden die alten 64 MiB / 3 / 4 Standardwerte angewendet.# Vollständige Testsuite ausführen (67 Tests)
cargo test
# Benchmarks ausführen (HTML-Berichte in target/criterion/)
cargo bench
# Benchmarks filtern
cargo bench -- "encrypt/AES"
cargo bench -- "chunk_sweep"
Die Testsuite deckt ab:
original_size + entfernte Chunks)chunk_size)| Crate | Zweck |
|---|---|
ring | Assembleroptimiertes AES-256-GCM und ChaCha20-Poly1305 AEAD |
io-uring | Linux io_uring-Schnittstelle für asynchrones Lese-/Schreib-I/O |
libc | O_DIRECT-Flag und ausgerichtetes pread/pwrite für Header-I/O |
argon2 | Argon2id-Schlüsselableitung |
rayon | Datenparallele Chunk-Verarbeitung |
clap | CLI-Argument-Parsing |
indicatif | Terminal-Fortschrittsbalken |
rand | Kryptografische Zufallszahlengenerierung |
zeroize | Sicheres Speicherlöschen |
anyhow | Fehlerbehandlung |
rpassword | Versteckte Passworteingabe |
tar | Archivierung und Extraktion von Verzeichnissen |
# Von crates.io (empfohlen)
cargo install concryptor
# Aus dem Quellcode
git clone https://github.com/FrogSnot/Concryptor
cd Concryptor
cargo build --release
# Binärdatei unter target/release/concryptor
Dieses Projekt ist unter der GNU Affero General Public License v3.0 lizenziert.