diff --git a/README.md b/README.md index e618004..2fe0411 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,172 @@ -# fcry - [f]ile[cry]pt -A file en-/decryption tool for easy use. +# fcry - filecrypt -Currently `fcry` uses `ChaCha20Poly1305` ([RFC 8439](https://datatracker.ietf.org/doc/html/rfc8439)) as [AEAD](https://en.wikipedia.org/wiki/Authenticated_encryption) cipher provided by the [chacha20poly1305](https://docs.rs/chacha20poly1305/latest/chacha20poly1305/) crate. +`fcry` encrypts and decrypts files with an authenticated chunked format. New +files use XChaCha20-Poly1305 in a STREAM-style construction: a 19-byte random +nonce prefix, a 32-bit chunk counter, and a last-chunk bit form each 24-byte +XChaCha nonce. Every chunk authenticates the full file header as AEAD +associated data. -## Status -Currently `fcry` is __not thoroughly tested__ and in __early stages of development__. -There is a chance, that something is broken as of now. -Encryption __seems__ to work, but due to a possible lack of understanding of some underlying methods -(or misinterpretation) it could theoretically be not effective at all. +The tool is intended for local file encryption, scripted backups, and +streaming-friendly decrypts. It is not a general archive format, does not hide +file size, and does not protect plaintext after another process has received +it. -See [TODO.md](/ddidderr/fcry/src/branch/main/TODO.md) for further information. \ No newline at end of file +## Usage + +Encrypt with an interactive passphrase: + +```sh +fcry -i plain.bin -o plain.bin.fcry --passphrase +``` + +Decrypt with the same passphrase: + +```sh +fcry -d -i plain.bin.fcry -o plain.bin --passphrase +``` + +Use a raw 32-byte key file instead of a passphrase: + +```sh +fcry -i plain.bin -o plain.bin.fcry --key-file key.bin +fcry -d -i plain.bin.fcry -o plain.bin --key-file key.bin +``` + +For non-interactive passphrase use: + +```sh +FCRY_PASSWORD='correct horse battery staple' \ + fcry -i plain.bin -o plain.bin.fcry --passphrase-env FCRY_PASSWORD +``` + +`--passphrase-env` is useful for automation, but the environment variable can +remain visible to the current process environment and platform tooling. Prefer +interactive entry or a protected key file when possible. + +## Safety Properties + +- File outputs are written to private, randomly named temporary files and are + renamed into place only after encryption or decryption succeeds. Existing + outputs require `--force`, except for the self-replacement case that is + handled through the temporary file. +- New passphrase encryptions use Argon2id by default with 1024 MiB of memory, + 2 passes, and 4 lanes. Passphrases must be non-empty and at least 12 UTF-8 + bytes unless `--allow-weak-kdf` is explicitly supplied for tests or legacy + interop. +- Decryption enforces a memory ceiling for Argon2id headers. The default cap is + the lower of 4096 MiB, the architecture limit, and available Linux memory + when that can be detected. Override it with `--max-argon-memory-mib` only for + files you trust. +- Chunk size is bounded to `1..=64 MiB`. Worker threads are capped at 256, and + the pipeline bounds in-flight chunk memory. +- v3 files carry a key commitment derived from the stretched key and committed + header fields. This gives a fast, clear wrong-key failure before chunk + processing and prevents stripping or downgrading the commitment without + authentication failure. +- On Unix, `fcry` makes a best-effort call to disable core dumps for the process + before handling secrets. + +## Format + +The current on-disk format version is v3. + +```text +magic "fcry" 4 bytes +version u8 1 +alg_id u8 1 (1 = XChaCha20-Poly1305) +flags u8 1 +reserved u8 1 (must be 0) +chunk_size u32 LE 4 +kdf_id u8 1 (0 = raw key, 1 = Argon2id) +kdf_params variable +nonce_prefix [u8; 19] +plaintext_length u64 LE only when flags bit 0 is set +key_commitment [u8; 32] only when flags bit 1 is set +ciphertext chunks each plaintext chunk plus a 16-byte Poly1305 tag +``` + +The encoded header is AEAD associated data for every chunk. Changing the chunk +size, KDF parameters, nonce prefix, committed plaintext length, key commitment, +or other header bytes causes authentication failure. + +Version history: + +- v1: no flags and no committed plaintext length. +- v2: adds the length-committed flag and optional `plaintext_length`. +- v3: requires the key-commitment flag and stores the 32-byte key commitment. + +Regular file encryption commits `plaintext_length` in the header. Stdin +encryption cannot know the final length up front, so stdin-produced files do +not support random-access decrypt. + +## Streaming And Ranges + +Normal decrypt-to-stdout emits each plaintext chunk after that chunk has +authenticated. This means a truncated ciphertext can produce an authentic +prefix on stdout before the final truncation error is reported. That is +inherent to chunked streaming AE when bytes are released immediately. + +Use `--buffer-verify` when decrypting to stdout if downstream consumers must +not see any plaintext until the whole file has authenticated: + +```sh +fcry -d -i plain.bin.fcry --passphrase --buffer-verify > plain.bin +``` + +`--buffer-verify` writes plaintext to a private temporary file first, verifies +the complete ciphertext, and copies to stdout only after success. File outputs +already get atomic temporary-file behavior, so `--buffer-verify` is only valid +for decrypt-to-stdout. + +Random-access decrypt requires `--decrypt`, `--input-file`, `--offset`, and +`--length`, and the input must have a length-committed header: + +```sh +fcry -d -i plain.bin.fcry --passphrase --offset 1048576 --length 4096 > slice.bin +``` + +A successful range decrypt authenticates the requested chunks and header. It +does not prove that the rest of the file is present or untampered. Use a full +decrypt when you need whole-file integrity. `--length 0` is rejected because it +would authenticate no chunks. + +## Threat Model + +`fcry` aims to provide confidentiality and integrity for file contents against +an attacker who can read, copy, truncate, replace, or modify ciphertext files +after encryption. With passphrase mode, offline guessing is still possible; the +Argon2id parameters make each guess expensive but cannot make a weak passphrase +safe. + +The format authenticates all header fields that affect decryption, including +KDF parameters, chunk size, nonce prefix, committed plaintext length, and key +commitment. Unknown header flags and unsupported algorithms are rejected. + +The following are explicit non-goals: + +- Hiding plaintext length or access patterns. `plaintext_length` is cleartext + for regular-file encryptions, and ciphertext length already reveals an + approximate plaintext size. There is no padding scheme. +- Preventing plaintext exposure after successful decrypt. Plaintext written to + stdout, files, pipes, shell history, terminals, swap, backups, or downstream + tools is outside `fcry`'s control. +- Protecting plaintext chunk buffers from every local memory-forensics route. + Keys and passphrases use protected/zeroizing storage where practical, and + chunk buffers are zeroized on drop, but decrypted plaintext necessarily exists + in ordinary process memory while being processed. +- Disabling Windows Error Reporting or minidumps. Unlike Unix core dumps, those + are controlled by per-machine Windows policy; `fcry` records this as an + operator/deployment responsibility rather than changing host-wide policy. +- Recovering from loss of the passphrase or raw key file. There is no escrow or + backdoor. + +## Operational Notes + +- Keep backups of important plaintext until you have verified the encrypted + file and your recovery path. +- Store raw key files with restrictive permissions. On Unix, `fcry` warns when + a key file is group/world accessible. +- Use `--allow-weak-kdf` only for tests or compatibility with old intentionally + weak files. +- Use `--temp-dir` when the default temporary-file location is not acceptable + for decrypt-to-stdout buffering or output staging.