Fast, Authenticated Encryption with Large Chunks — a dedicated sponge construction for encrypting large files with AES-256-GCM, domain-separated key derivation, and whole-container HMAC authentication.
FALlen is not a toy cipher. Every component — from the custom ARX permutation to the container format — has been independently probed, fuzzed, and adversarially audited.
Every 4 MiB chunk (configurable) gets its own AES-256-GCM key and nonce, derived from a single master seed via domain-separated sponge calls. Reordering, replay, or removal of any chunk is detected.
A trailing HMAC-SHA256 covers the entire container — header, metadata, and all chunk records. Truncation, appended data, and header edits are caught even if every individual chunk passes.
Default cost adapts to host RAM: ≥ 32 GiB → 4 GiB, 8–32 GiB → 2 GiB, lower → 256 MiB. Parameters are stored in the header so a file encrypted on a big machine stays decryptable on a small one.
Full case folding, NFC normalization, and Unicode whitespace trimming. Pässwort and pässwort (any case, NFD or NFC) derive the same key. An optional stretch pre-pass doubles per-guess cost.
Decryption writes to a randomized temp file and promotes only after full authentication. Wrong password or any tampering leaves zero plaintext at the destination. Encrypt uses validation-first ordering with atomic commit.
--compress uses DEFLATE to shrink the plaintext before it enters the cipher. Encrypted streams are pseudorandom and incompressible — a 110 KB text file can become a 21 KB container.
A per-file 256-bit random file_id and a fresh random salt separate
every encryption. All chunk keys, nonces, and MAC keys are derived from the
master seed with domain separation.
Each derivation purpose absorbs a unique domain byte first, making outputs independent even with identical seeds.
| Purpose | Domain | Output | Inputs |
|---|---|---|---|
| Master Seed | 0x01 | 32 B | Raw Argon2id output |
| Chunk Key | 0x02 | 32 B | master_seed ‖ file_id ‖ chunk_number |
| Chunk Nonce | 0x03 | 12 B | master_seed ‖ file_id ‖ chunk_number |
| Metadata Key | 0x04 | 32 B | master_seed ‖ file_id |
| Metadata Nonce | 0x05 | 12 B | master_seed ‖ file_id |
| Final MAC Key | 0x06 | 32 B | master_seed ‖ file_id |
A 1024-bit state (16 × 64-bit words) processed through 32 rounds. Each round applies addition, XOR, rotation, word permutation, two triangular nonlinear layers, and a π-derived round constant.
.fal binary layoutVersion 1, all integers little-endian. A fixed 40-byte header, followed by salt, file ID, encrypted metadata, chunk records, and a trailer.
"FALCHUNK1" || version || flags[2] || file_id || chunk_number(u64) || plaintext_len(u32)
Requires CMake ≥ 3.14, a C++20 compiler, and OpenSSL ≥ 3.0.
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release cmake --build build ctest --test-dir build
# Interactive password prompt (read twice, no echo) falencrypt -f plain.txt -o plain.txt.fal # Explicit KDF parameters falencrypt -f large_dataset.csv -o large_dataset.csv.fal \ --memory-kb 131072 --time-t 3 --parallelism 1 # With compression (110 KB text → 21 KB container) falencrypt -f report.txt -o report.txt.fal --compress # Force overwrite, skip storing metadata falencrypt -f data.bin -o data.bin.fal --force --no-mtime --no-mode
# Restore using the authenticated stored filename faldecrypt -f out.fal -o . # Explicit output path faldecrypt -f out.fal -o decrypted.bin # Rename on decrypt faldecrypt -f out.fal -o dir/ --name renamed.bin
# Brute-force short PINs falpass -f lost.fal -c "0123456789" -m 1 -M 4 -o recovered.bin # Wordlist-combination mode falpass -f lost.fal -w words.txt -W 4 \ --seps none,-,_ --case lower,UPPER -o out # Benchmark throughput + feasibility falpass -f lost.fal -B
#include <fal_encrypt.hpp> #include <fal_decrypt.hpp> // Encrypt fal::fal_encrypt_file("plain.txt", "plain.txt.fal", { .password = "s3cret", .chunk_size = 4194304, // 4 MiB default .compress = true, }); // Decrypt fal::fal_decrypt_file("plain.txt.fal", ".", { .password = "s3cret", });
Every security finding from the adversarial audit has been patched, regression-tested, and verified with live probes under ASan/UBSan. Here are the guarantees.
AES-256-GCM per chunk. Container reveals only file size (padded to chunks) and chunk count — nothing about plaintext.
Every chunk GCM-authenticated with AAD binding file + position + format options. Trailer HMAC-SHA256 covers the whole container. Chunk reorder, replay, truncation, header edits — all rejected.
Argon2id with per-file random salt. Defaults adapt to RAM. Password normalization (NFC + case fold + Unicode trim). Optional stretch pre-pass doubles per-guess cost.
Decryption writes to a temp file promoted only after full authentication. Wrong password = no plaintext at destination. Filenames sanitized to plain basenames.
PR_SET_DUMPABLE=0, RLIMIT_CORE=0, mlock + MADV_DONTDUMP on secrets. Compiler-barrier-resistant wipe on every exit path. Symlink-safe temp files with O_EXCL|O_NOFOLLOW.
Fresh random salt + file_id per encryption. Domain-separated sponge per purpose. Two chunks in the same file share neither key nor nonce. Re-encrypting the same file yields entirely new material.
The naive 2128 Grover bound is a strict lower bound. A real attack must evaluate the full verification circuit, pay for logical qubits, error correction, and circuit depth. Resource-accounted figures below assume 1015 logical gates/s, ×103 error correction overhead.
| Attack | Logical Gates | Time (P=1) | Time (P=106) | vs 300 Years |
|---|---|---|---|---|
| Seed recovery (256-bit) | ~2149 | 2.9 × 1025 y | 2.9 × 1022 y | 1020–1023× over |
| Full-width gates | ~2163 | ≫ table | ≫ table | Unbeatable on any horizon |
| Password, 80-bit entropy | ~272 | ~1.8 × 102 y | ~0.006 y | Too small — do not use |
| Password, 128-bit entropy | ~296 | ~3.0 × 109 y | ~9.4 × 104 y | Comfortable |
| Password, 160-bit entropy | ~2112 | ~1.9 × 1014 y | ~6.1 × 109 y | Overwhelming |
The practical takeaway: the cryptographic primitive is safe on any roadmap. The password is the real attack surface. ≥ 128-bit entropy passwords keep even a 109-machine Grover descent above 104–105 years, and Argon2id's quantum memory requirement (229 logical qubits at 64 MiB) makes the cost even higher in practice.
Reference implementation in C++20. The permutation, sponge, KDF, crypto layer, format parser, encrypt/decrypt APIs, CLI tools, 16 test suites, fuzz harnesses, and the full audit record.
Measured across all 1024 input bits of the permutation.
| Property | Result |
|---|---|
| Strict Avalanche (SAC) | Mean flip 0.4960–0.5044 per bit — no weak input bits |
| Nonlinearity | 1.0000 crossover vs linear map over 4000 random pairs |
| Diffusion (single-bit) | All 16 words affected within 2 rounds |
| Diffusion saturation | ~455/1024 bits changed by round 4+ (near-random 512) |
| Sponge output battery | Monobit, byte χ², longest run, block uniqueness, autocorrelation — all pass |
| Permutation invertibility | Deterministic + 20,000 random states verified |
| Fuzzing (libFuzzer) | 5 targets, 380+ robustness cases, ASan/UBSan clean |