@thoisoithree/fcrypt
v0.3.7
Published
Cross-platform CLI for password-based file encryption and decryption
Maintainers
Readme
fcrypt
fcrypt is a cross-platform Rust CLI for local file encryption, decryption,
key generation, and post-quantum asymmetric encryption.
The Cargo package, Rust crate, and installed binary are all named fcrypt.
Use fcrypt -v, fcrypt -V, or fcrypt --version to print the version.
enc and dec are short aliases for encrypt and decrypt, including
under asym. Existing encode and decode aliases remain available.
Within decryption commands, -v FILE retains its signature-verification meaning.
fcrypt is designed for large files too. It processes data as authenticated
chunks, writes outputs through temporary files, and finalizes results only after
the operation succeeds.
Release 0.3.x compatibility notice
Version 0.3.0 introduced the opaque .bin container format, retained by the
0.3.x releases. It does not provide backward compatibility for pre-0.3.0
encrypted files.
To read old files created by fcrypt 0.2.0 or earlier, use fcrypt version
0.2.0.
Highlights
- Opaque password-based encrypted files with no cleartext magic bytes, version, algorithm identifiers, or recipient identifiers.
- Password slots use a fixed opaque v1 Argon2id profile: 128 MiB, time cost 3, parallelism 1.
- Post-quantum asymmetric encryption with ML-KEM-1024 + HQC-256 recipient slots.
- AES-256-GCM streaming payload encryption with
u64chunk indexes. - Parallel payload encryption and decryption with automatic worker selection.
- Detached ML-DSA-87 signatures for opaque ciphertext files.
- Explicit identity creation with
identity create <name>for recipient and signing key pairs. The legacyasym encryptworkflow retains its historical automatic key generation behavior. - Random password generation and Diceware-style passphrase generation from the embedded EFF large wordlist.
- Atomic output workflow: plaintext, ciphertext, signature, and key files are written through temporary files first. Keys generated during encryption or signing are committed with the corresponding outputs; failures preserve existing keys and outputs when rollback succeeds. If rollback fails, the error reports retained recovery backup paths. Multi-file transactions do not guarantee recovery after a process crash or power loss.
- npm packages with prebuilt binaries for Linux, macOS, and Windows on x64 and arm64.
Payload threads
Large files use multiple CPU cores automatically. Use --threads N or -t N (0–32)
to set a worker limit; 0 selects available parallelism and 1 runs
sequentially. This global option also works with the legacy asymmetric commands.
fcrypt encrypt archive.tar --threads 4
fcrypt decrypt archive.tar.bin --threads 4
fcrypt encrypt archive.tar --threads 1The actual worker count is limited by the number of chunks and a 64 MiB budget for parallel chunk buffers (input and output combined). Existing I/O buffers, worker stacks, and the password KDF allocation are additional. Large chunks that cannot fit multiple workers use the sequential path. Reading, ordered writing, and progress reporting stay on the calling thread. Argon2 parameters and signature hashing are unchanged; these stages and disk throughput may limit overall speedup.
The library exposes fcrypt::sym::parallel::with_threads(n, || operation())
for synchronous calls. It scopes the setting to the calling thread and restores
it afterward; it is not inherited by threads created by callers.
Security model
Password encryption
Password mode uses the opaque container format:
password -> fixed Argon2id profile -> password slot -> manifest key
manifest key -> encrypted manifest -> file secret
file secret -> HKDF-SHA3-512 -> AES-256-GCM payload chunksThe password is entered through a hidden terminal prompt. The password is not stored in the encrypted file and is not logged.
Asymmetric encryption
Asymmetric mode uses recipient keys:
ML-KEM-1024 shared secret ┐
├─ HKDF-SHA3-512 ── slot key ── manifest key
HQC-256 shared secret ────┘
manifest key -> encrypted manifest -> file secret
file secret -> HKDF-SHA3-512 -> AES-256-GCM payload chunksML-KEM-1024 and HQC-256 provide two independent post-quantum KEM layers for recipient access.
Optional detached signatures use ML-DSA-87:
opaque ciphertext transcript -> ML-DSA-87 signature -> <ciphertext>.sigA recipient key and a signing key are different keys:
*_recipient_default.pubis shared with people who should encrypt files for the recipient.*_recipient_default.secdecrypts matching opaque files and must remain secret.*_signer_mldsa87.pubis shared with people who should verify signatures.*_signer_mldsa87.seccreates signatures and must remain secret.
Never share .sec files.
Install
Cargo
cargo install fcrypt --locked
fcrypt --helpnpm
npm install -g @thoisoithree/fcrypt
fcrypt --helpThe npm package installs a small launcher and a prebuilt platform binary for the current OS/CPU.
Published platform packages:
@thoisoithree/fcrypt-linux-x64@thoisoithree/fcrypt-linux-arm64@thoisoithree/fcrypt-darwin-x64@thoisoithree/fcrypt-darwin-arm64@thoisoithree/fcrypt-win32-x64@thoisoithree/fcrypt-win32-arm64
GitHub release binaries
GitHub Releases provide raw binary files and .sha256 checksum files for:
- Linux x64:
fcrypt-linux-x64 - Linux arm64:
fcrypt-linux-arm64 - macOS Intel:
fcrypt-darwin-x64 - macOS Apple Silicon:
fcrypt-darwin-arm64 - Windows x64:
fcrypt-win32-x64.exe - Windows arm64:
fcrypt-win32-arm64.exe
Build from source
cargo build --release --lockedAsymmetric PQC mode is enabled by default through the pqc feature and uses a
patched Open Quantum Safe liboqs 0.15.x build through an API-compatible fork
of tectonic-oqs, packaged separately as fcrypt-oqs and fcrypt-oqs-sys.
The C sources are pinned to commit
282809f06dccf6893980035cb11f319684d10d52; the build rejects any fresh or
cached checkout whose HEAD differs or whose worktree is not clean. Building
with PQC support can require:
- Rust stable
- Git and network access while the versioned liboqs source is fetched
- C compiler
- CMake
- platform build tools
To build only the password-based mode:
cargo build --release --locked --no-default-featuresWhen built without the pqc feature, fcrypt asym ... / fcrypt assym ...
commands return a clear error and password mode remains available.
Help
Short command-local help:
fcrypt -h
fcrypt encrypt -h
fcrypt decrypt -h
fcrypt identity -h
fcrypt sign -h
fcrypt verify -hFull help for every command, option, alias, and example:
fcrypt help-allThe compatibility forms -ha and --help-all are also accepted. They are
handled before command parsing, except after --, so a file literally named
-ha remains a valid input path.
fcrypt --help-allCommand overview
# Recommended commands
fcrypt encrypt <INPUT>... [-o <OUTPUT>]
fcrypt decrypt <INPUT.bin>... [-o <OUTPUT>]
fcrypt sign <INPUT.bin> --key <SIGNER.SEC>
fcrypt verify <INPUT.bin> --key <SIGNER.PUB>
fcrypt identity create <NAME>
fcrypt identity list
fcrypt identity inspect <KEY>
fcrypt password generate [--length <N>]
fcrypt phrase generate [--words <N>]
# Compatibility commands
fcrypt asym ...
fcrypt assym ...
fcrypt keygen ...encrypt and encode are aliases. decrypt and decode are aliases. The
legacy asym, assym, and keygen command groups remain supported.
Password-based usage
Encrypt a file:
fcrypt encrypt report.pdfEquivalent alias:
fcrypt encode report.pdfThe output is:
report.pdf.binEach completed file is reported with its size and elapsed time:
Encrypted report.pdf → report.pdf.bin (12.4 MiB, 1.8s)Encrypting with a password shorter than 12 characters prints a warning (also
listed under warnings in --json output).
Decrypt:
fcrypt decrypt report.pdf.binIf decryption fails, the error explains what to check: the password (or that
the file may need --identity if it was encrypted for a recipient key), or, in
key-file mode, that the key file must be byte-for-byte identical and the
optional password must match.
Equivalent alias:
fcrypt decode report.pdf.binForce overwrite of an existing output file:
fcrypt decrypt report.pdf.bin --forceChoose a specific output path:
fcrypt encrypt report.pdf --output archive.bin
fcrypt decrypt archive.bin --output report-restored.pdfSeveral files can be processed in one command. Credentials are requested
once, every overwrite question is asked before any work starts, and each file
gets its usual <INPUT>.bin (or decrypted) name. --output is only allowed
with a single input, and --new-identity requires a single input; use
identity create and --recipient for several files.
fcrypt encrypt *.pdf
fcrypt decrypt *.pdf.bin
fcrypt encrypt a.txt b.txt --recipient ./alice_recipient_default.pubEach file is still encrypted independently with its own salt, so the password
KDF runs once per file. Processing stops at the first failing file; files
completed before it are kept, and the error names the failing input. With
--json, a multi-file result lists files under files instead of the
top-level input/output fields.
For automation, read a password from a protected file. A single trailing LF or CRLF is removed; other whitespace remains part of the password:
fcrypt encrypt report.pdf --password-file ./fcrypt-password.txt --json
fcrypt decrypt report.pdf.bin --password-file ./fcrypt-password.txt --quietAny file can serve as the key instead of a password. Its full contents are hashed with SHA3-256 (binary files are fine, nothing is trimmed), so the container format is unchanged. fcrypt then asks for an optional password: press Enter to use the key file alone, or type a password (confirmed on encryption) to require both the file and the password for decryption:
fcrypt encrypt report.pdf --key-file ./photo.jpg
fcrypt decrypt report.pdf.bin -K ./photo.jpgWithout an interactive terminal no password is asked for; to combine a key file
with a password in scripts, add --password-file:
fcrypt encrypt report.pdf -K ./photo.jpg --password-file ./fcrypt-password.txtKeep the key file byte-for-byte identical: any change, including re-saving or
metadata edits, makes the ciphertext undecryptable. Empty files, directories,
and the operation's own input or output are rejected. --key-file cannot be
combined with recipient-key options.
--json emits one machine-readable result to stdout. --quiet suppresses
normal success messages and progress, while --no-progress keeps final output
but hides progress indicators. Passwords must never be supplied as command-line
arguments.
Asymmetric PQC opaque usage
Recommended recipient-key workflow
fcrypt identity create alice
fcrypt encrypt report.pdf --recipient ./alice_recipient_default.pubThe unified command never creates a recipient key silently. Use
--new-identity <NAME> when intentional one-off identity creation is desired.
Back up the generated recipient .sec file: without it, encrypted data cannot
be recovered.
Sign and verify ciphertexts without decrypting:
fcrypt sign report.pdf.bin --key ./alice_signer_mldsa87.sec
fcrypt verify report.pdf.bin --key ./alice_signer_mldsa87.pub
fcrypt decrypt report.pdf.bin \
--identity ./alice_recipient_default.sec \
--verify ./alice_signer_mldsa87.pubNew signed containers carry an authenticated signer requirement inside their
opaque recipient slot. Decryption therefore fails closed if .sig is removed,
and also requires --verify <signer.pub> when the sidecar is present. Always
pass --verify when authenticity of older containers is required; containers
created before this marker cannot reveal that a sidecar was removed.
Decryption with --verify copies ciphertext to a private temporary file, then
verifies and decrypts that same snapshot. Concurrent changes to the original
file cannot change the verified plaintext. This requires temporary disk space
for one additional copy of the ciphertext, alongside the staged plaintext.
Generate named recipient and signing keys
Generate four key files in the current directory:
fcrypt keygen pair aliceOutput files:
alice_recipient_default.pub
alice_recipient_default.sec
alice_signer_mldsa87.pub
alice_signer_mldsa87.secKeys are non-expiring by default. To create keys with a lifetime in days:
fcrypt keygen pair alice 365Encrypt for an existing recipient
fcrypt asym encrypt report.pdf -r ./alice_recipient_default.pubLong option:
fcrypt asym encrypt report.pdf --recipient-public ./alice_recipient_default.pubWhen -r / --recipient-public is provided, no new recipient keypair is
generated.
Choose a key directory
By default, keys are saved next to the input file in <file_stem>_keys:
report.pdf -> report_keys/
docs/report.pdf -> docs/report_keys/
archive.tar.gz -> archive.tar_keys/Override it with -k / --keys-dir:
fcrypt asym encrypt report.pdf -k ./keysWhen --keys-dir is provided, fcrypt uses that directory directly and does
not create a nested report_keys/ directory.
Decrypt
fcrypt asym decrypt report.pdf.bin -i ./report_keys/a13f2c9b_recipient_default.secEquivalent alias:
fcrypt asym decode report.pdf.bin -i ./report_keys/a13f2c9b_recipient_default.secIf -i / --identity is omitted, fcrypt tries available recipient secret
keys in the default or provided keys directory:
fcrypt asym decrypt report.pdf.bin
fcrypt asym decrypt report.pdf.bin -k ./report_keysOpaque ciphertexts do not reveal which recipient key should be tried.
Auto-discovery is therefore bounded to 32 matching recipient secret key files;
use -i to select a specific key when needed.
Encrypt and sign
Generate a signing key automatically and create a detached signature:
fcrypt asym encrypt report.pdf -sGenerated files:
report.pdf.bin
report.pdf.bin.sig
report_keys/
<label8>_recipient_default.pub
<label8>_recipient_default.sec
<label8>_signer_mldsa87.pub
<label8>_signer_mldsa87.secSign with an existing signing key:
fcrypt asym encrypt report.pdf -S ./alice_signer_mldsa87.sec-S / --sign-key automatically enables signing, so this is equivalent to
-s -S ./alice_signer_mldsa87.sec.
Sign an existing opaque file
Create a detached signature:
fcrypt asym sign report.pdf.binOutput:
report.pdf.bin.sigUse an existing signing key:
fcrypt asym sign report.pdf.bin -S ./alice_signer_mldsa87.secEmbedded signatures are not supported for opaque files.
Verify signatures while decrypting
Verify with a signer public key:
fcrypt asym decrypt report.pdf.bin \
-i ./report_keys/a13f2c9b_recipient_default.sec \
-v ./alice_signer_mldsa87.pubRequire a valid signature:
fcrypt asym decrypt report.pdf.bin \
-i ./report_keys/a13f2c9b_recipient_default.sec \
-v ./alice_signer_mldsa87.pub \
-RWhen --verify <signer.pub> is used, a valid detached signature is required
before decryption. The legacy -R / --require-signature option remains
accepted for compatibility and still requires a verification key.
Options
encrypt / encode
<INPUT>, -i, --input <FILE> Path to the input file.
-f, --force Overwrite the destination file without asking.decrypt / decode
<INPUT>, -i, --input <FILE> Path to the input file.
-f, --force Overwrite the destination file without asking.asym encrypt / asym encode
-o, --output <FILE> Destination .bin file. Defaults to <INPUT>.bin.
-r, --recipient-public <FILE> Recipient public key bundle.
-k, --keys-dir <DIR> Directory for generated or discovered keys.
-s, --sign Create a detached ML-DSA-87 signature.
-S, --sign-key <FILE> Signing secret key. Implies --sign.
-f, --force Overwrite output and signature files without asking.asym decrypt / asym decode
-o, --output <FILE> Destination plaintext file. Defaults to <INPUT.bin without .bin>.
-i, --identity <FILE> Recipient secret key bundle.
-k, --keys-dir <DIR> Directory for recipient secret key auto-discovery.
-v, --verify <FILE> Signing public key for signature verification.
-R, --require-signature Require a valid signature before decrypting.
-f, --force Overwrite the destination file without asking.asym sign
-o, --output <FILE> Detached signature output. Defaults to <INPUT>.sig.
-S, --sign-key <FILE> Signing secret key bundle. Generated if omitted.
-k, --keys-dir <DIR> Directory for generated signing keys.
-e, --embed Unsupported for opaque files.
-f, --force Overwrite the signature file without asking.Key generation
Random password
fcrypt password generate
fcrypt password generate --length 48
fcrypt password generate -c
fcrypt password generate -bpassword generate writes a random password with N characters to stdout followed by
a newline. N defaults to 32 and must be between 1 and 4096.
-c/--compatible restricts special characters to the widely accepted
!@#$%^&*_-+= set. -b/--base64 Base64-encodes the generated password
before writing it. The legacy -b64 spelling remains accepted.
The password generator uses the operating system CSPRNG and this portable alphabet:
A-Z a-z 0-9 ! # $ % & ( ) * + , - . / : ; < = > ? @ [ ] ^ _ { | } ~Random passphrase
Generate a phrase from the embedded EFF large wordlist:
fcrypt phrase generate
fcrypt phrase generate --words 10The word count defaults to 7 and must be between 1 and 128.
Use a custom separator:
fcrypt phrase generate --words 7 --separator .
fcrypt phrase generate --words 7 --separator _
fcrypt phrase generate --words 7 --separator " "keygen phrase uses the embedded EFF large wordlist with 7,776 words. Word
selection is cryptographically random and uniform. The default separator is
-.
Named asymmetric key pairs
fcrypt identity create alice
fcrypt identity create alice --lifetime-days 365identity create writes one recipient keypair and one signing keypair. The optional
lifetime is in days. The key filename prefix accepts ASCII letters, digits,
., _, and -, and must not start with ..
Opaque file format
Both password and asymmetric encryption use the same opaque .bin container.
The outer file has no magic bytes, cleartext format version, cleartext
algorithm identifiers, cleartext KDF parameters, or cleartext recipient key id.
Fixed outer layout:
32 bytes file nonce
8 * 16384 recipient slots
4112 bytes encrypted manifest
rest encrypted payload chunksThe encrypted manifest contains the internal format version, plaintext length, chunk size, chunk count, tag length, and random payload file secret.
Password slots use opaque v1 Argon2id:
memory: 131072 KiB (128 MiB)
time cost: 3
parallelism: 1
output: 32 bytesPayload chunks use AES-256-GCM. Chunk nonces are:
4-byte per-file nonce base || 8-byte big-endian chunk indexSee docs/OPAQUE_FORMAT.md for implementation-level
details.
Output naming
Encryption appends .bin to the full filename:
report.pdf -> report.pdf.binDecryption removes .bin when present:
report.pdf.bin -> report.pdfLegacy .enc suffixes are accepted only for output-path mapping:
old-file.enc -> old-fileThey do not imply legacy file format support in 0.3.x.
If the input has neither .bin nor .enc, decryption appends .dec:
archive.dat -> archive.dat.decSource files are never modified in place.
Release artifacts and package repositories
The release workflow builds raw binaries and SHA-256 checksum files for Linux,
macOS, and Windows. Tagging a release with vX.Y.Z triggers GitHub Actions
release automation.
APT and RPM repositories are published by the package repository workflow when
repository signing secrets are configured. See
docs/PACKAGE_REPOSITORIES.md for setup and
install commands.
Release process details are documented in RELEASE.md.
Development
Run tests:
cargo test --lockedRun clippy:
cargo clippy --all-targets --all-features -- -D warningsBuild release:
cargo build --release --lockedBuild without asymmetric PQC mode:
cargo build --release --locked --no-default-featuresTest coverage
The test suite covers:
- password small-file, empty-file, and chunk-boundary roundtrips;
- wrong password, corrupted ciphertext, and truncated file failures;
- opaque prelude tamper detection;
- fixed password KDF profile behavior;
- asymmetric opaque roundtrips;
- explicit recipient public key encryption and secret key decryption;
- bounded recipient key auto-discovery;
- detached ML-DSA-87 signatures;
- standalone signature verification and unified password/PQC CLI workflows;
- required signature failures;
- named
keygen pairgeneration with and without lifetime; - output naming rules;
- overwrite behavior;
- random password and EFF wordlist passphrase generation;
help-alland compatibility-ha/--help-alloutput.
Security notes
- Keep
.secfiles private. - Share only
.pubfiles. - A recipient
.seckey can decrypt matching opaque files. - A signer
.seckey can create signatures as that signer. - Use
--verify <signer.pub>when authenticity is mandatory during decryption. - Asymmetric key JSON and detached signature files are limited to 64 KiB.
- Opaque payload decryption uses the current chunk's actual length and rejects ciphertext chunks above the 64 MiB reader memory limit before allocation.
- Expired recipient secret keys remain usable for archival decryption, and expired signing public keys remain usable for historical verification. Expired recipient public keys and signing secret keys cannot be used for new work.
- Keep backups of recipient secret keys; losing the matching recipient
.seckey makes recovery impossible. - Password-based files cannot be recovered without the original password.
fcrypt0.3.xcannot read pre-0.3.0 encrypted files. Use version0.2.0for those files.
License
MIT. See LICENSE.
