FALlen Implementation v1

FALlen

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.

Get Started How It Works
256-bitMaster Seed
32Rounds
512-bitSponge Capacity
16 testSuites
Capabilities

Designed for large files, audited from every angle

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.

Per-Chunk GCM Authentication

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.

🔒

Whole-Container HMAC

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.

🔧

Adaptive Argon2id KDF

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.

🌐

Unicode Password Normalization

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.

💾

Safe Failure & Atomic Writes

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.

📈

Compression Before Encryption

--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.

Architecture

One master seed, infinite derived keys

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.

PasswordNFC + fold + trim
Argon2idm / t / p
FALlen Spongedomain 0x01
Master Seed32 bytes
Derive0x02 – 0x06
Keys / NoncesAES-256-GCM

Domain Separation Table

Each derivation purpose absorbs a unique domain byte first, making outputs independent even with identical seeds.

PurposeDomainOutputInputs
Master Seed0x0132 BRaw Argon2id output
Chunk Key0x0232 Bmaster_seed ‖ file_id ‖ chunk_number
Chunk Nonce0x0312 Bmaster_seed ‖ file_id ‖ chunk_number
Metadata Key0x0432 Bmaster_seed ‖ file_id
Metadata Nonce0x0512 Bmaster_seed ‖ file_id
Final MAC Key0x0632 Bmaster_seed ‖ file_id

The FALlen Permutation

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.

1
s[i] += s[i+1]
Modular addition (carries)
2
s[i] ^= s[i+3]
XOR diffusion
3
s[i] = rotl(s[i], ROT[i])
Per-word rotation
4
t[P1[i]] = s[i]
Word permutation P1
5
s[i] ^= s[i+1] & s[i+2]
Nonlinear (triangular, invertible)
6
t[P2[i]] = s[i]
Word permutation P2
7
s[i] ^= s[i+1] & s[i+2]
Nonlinear (second layer)
8
s[0] ^= RC[r]
π-derived round constant
Container Format

The .fal binary layout

Version 1, all integers little-endian. A fixed 40-byte header, followed by salt, file ID, encrypted metadata, chunk records, and a trailer.

[0]
magic "FAL" 3 bytes
[3]
version u8 = 1
[4]
flags u16
[6]
kdf_id u8 = 1 (Argon2id)
[7]
pw_norm u8 — 0x00 raw / 0x11 NFC+fold / 0x21 +stretch
[8]
argon2_m u32 KiB
[12]
argon2_t u32
[16]
argon2_p u32
[20]
salt_len u16 — 32 or 64
[22]
file_id_len u16 = 32
[24]
chunk_size u32
[28]
original_size u64
[36]
metadata_len u32
[40]
salt salt_len bytes
file_id 32 bytes
encrypted_metadata ciphertext + 16-byte GCM tag
chunk records [ number(u64) | ct_len(u32) | ciphertext | tag(16) ]
trailer [ chunk_count(u64) | HMAC-SHA256(32) ]
GCM Authenticated Data per chunk
"FALCHUNK1" || version || flags[2] || file_id || chunk_number(u64) || plaintext_len(u32)
CLI Usage

Encrypt and decrypt in seconds

Requires CMake ≥ 3.14, a C++20 compiler, and OpenSSL ≥ 3.0.

Build
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
ctest --test-dir build
Encrypt a file
# 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
Decrypt
# 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
Password recovery (falpass)
# 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
Library API (C++)
#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",
});
Security

Audited, fuzzed, and hardened

Every security finding from the adversarial audit has been patched, regression-tested, and verified with live probes under ASan/UBSan. Here are the guarantees.

🛡 Confidentiality

AES-256-GCM per chunk. Container reveals only file size (padded to chunks) and chunk count — nothing about plaintext.

🔒 Integrity

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.

🔑 Password Protection

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.

Safe Failure

Decryption writes to a temp file promoted only after full authentication. Wrong password = no plaintext at destination. Filenames sanitized to plain basenames.

🛡 Memory Hardening

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.

🔑 No Key/Nonce Reuse

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.

Audit Findings (all patched or verified impossible)

Patched
Symlink temp file attack — predictable decryption temp name followed symlinks. Now: randomized 32-hex names, O_EXCL|O_NOFOLLOW, fd-backed writes.
Patched
salt_len < 32 heap overflow — decoder accepted any salt_len but always read 32 bytes. Now: exactly 32 or 64 enforced.
Patched
RAM seed seizure — same-UID PTRACE_ATTACH could scrape the master seed. Now: dumpable=0, mlock, MADV_DONTDUMP, wipe on all paths.
Patched
Encrypt destroys pre-existing output — fopen before validation truncated/removed targets. Now: validation-first + atomic temp commit.
Patched
Weak KDF crackability — containers with m=8/t=1 crackable at millions of guesses/s. Now: creation rejected below floor (m≥64 MiB, t≥3).
Impossible
Nonce / key reuse — 6 domain-separated sponge calls, file_id+chunk_number context. Verified across 1,048,582 derivation inputs: zero collisions.
Impossible
Trailer HMAC bypass — every byte is covered by the incremental HMAC. 1,290-case malformed battery + 5,000-mutation corpus: zero crashes, zero plaintext leaks.
Not Feasible
Reduced-round algebraic break — all GF(2) probes, differential and linear analyses are noise by round 3. 32 rounds is a substantial margin over diffusion point.
Quantum Resistance

Resource-accounted quantum attack estimates

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.

AttackLogical GatesTime (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.

Source Layout

Everything in one repository

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.

fal/ ├── include/ │ ├── fal_common.hpp shared constants + endian helpers │ ├── fal_permutation.hpp FAL round function and permutation │ ├── fal_sponge.hpp sponge construction (absorb/finalize/squeeze) │ ├── fal_kdf.hpp Argon2id + master-seed derivation │ ├── fal_crypto.hpp AES-GCM, HMAC, random, domain-separated derive │ ├── fal_format.hpp container serialization / parsing │ ├── fal_encrypt.hpp fal_encrypt_file() │ ├── fal_decrypt.hpp fal_decrypt_file() │ ├── fal_password.hpp Unicode normalization + password prep │ └── fal_compress.hpp DEFLATE compression layer ├── src/ implementation files ├── tools/ │ ├── falencrypt.cpp CLI: encrypt file → .fal │ ├── faldecrypt.cpp CLI: decrypt .fal → file │ └── falpass_main.cpp CLI: password recovery ├── tests/ 16 test suites ├── fuzz/ 5 libFuzzer harnesses ├── benchmarks/ permutation + sponge throughput ├── corpus/ regression fixtures (795 files) ├── audit/ adversarial audit: probes, demos, regression ├── docs/ │ ├── FAL_FORMAT.md container format specification │ ├── FAL_PRIMITIVE.md permutation design + analysis │ ├── QUANTUM_ATTACK.md resource-accounted quantum estimates │ └── AUDIT.md full security & arithmetic audit └── CMakeLists.txt build system (C++20, OpenSSL, zlib)

Empirical Certification Results

Measured across all 1024 input bits of the permutation.

PropertyResult
Strict Avalanche (SAC)Mean flip 0.4960–0.5044 per bit — no weak input bits
Nonlinearity1.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 batteryMonobit, byte χ², longest run, block uniqueness, autocorrelation — all pass
Permutation invertibilityDeterministic + 20,000 random states verified
Fuzzing (libFuzzer)5 targets, 380+ robustness cases, ASan/UBSan clean