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.
fcrypt is designed for large files. 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. - 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.
- npm packages with prebuilt binaries for Linux, macOS, and Windows on x64 and arm64.
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 chunks
The 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 chunks
ML-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>.sig
A 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
npm
The 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
Asymmetric PQC mode is enabled by default through the pqc feature and uses
Open Quantum Safe liboqs through Rust oqs bindings. Building with PQC
support can require:
- Rust stable
- C compiler
- CMake
- platform build tools
To build only the password-based mode:
When built without the pqc feature, fcrypt asym ... / fcrypt assym ...
commands return a clear error and password mode remains available.
Help
Short command-local help:
Full help for every command, option, alias, and example:
The 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.
Command overview
# Recommended commands
# Compatibility commands
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:
Equivalent alias:
The output is:
report.pdf.bin
Decrypt:
Equivalent alias:
Force overwrite of an existing output file:
Choose a specific output path:
For automation, read a password from a protected file. A single trailing LF or CRLF is removed; other whitespace remains part of the password:
--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
The 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:
Generate named recipient and signing keys
Generate four key files in the current directory:
Output files:
alice_recipient_default.pub
alice_recipient_default.sec
alice_signer_mldsa87.pub
alice_signer_mldsa87.sec
Keys are non-expiring by default. To create keys with a lifetime in days:
Encrypt for an existing recipient
Long option:
When -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:
When --keys-dir is provided, fcrypt uses that directory directly and does
not create a nested report_keys/ directory.
Decrypt
Equivalent alias:
If -i / --identity is omitted, fcrypt tries available recipient secret
keys in the default or provided keys directory:
Opaque 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:
Generated 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.sec
Sign with an existing signing key:
-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:
Output:
report.pdf.bin.sig
Use an existing signing key:
Embedded signatures are not supported for opaque files.
Verify signatures while decrypting
Verify with a signer public key:
Require a valid signature:
When --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
password generate writes a random password with N characters to stdout followed by
a newline. N 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:
Use a custom 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
identity 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 chunks
The 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 bytes
Payload chunks use AES-256-GCM. Chunk nonces are:
4-byte per-file nonce base || 8-byte big-endian chunk index
See docs/OPAQUE_FORMAT.md for implementation-level
details.
Output naming
Encryption appends .bin to the full filename:
report.pdf -> report.pdf.bin
Decryption removes .bin when present:
report.pdf.bin -> report.pdf
Legacy .enc suffixes are accepted only for output-path mapping:
old-file.enc -> old-file
They 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.dec
Source 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:
Run clippy:
Build release:
Build without asymmetric PQC mode:
Test 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. - 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.