rsecure is a file encryption CLI built on top of AES-256-GCM. Because it handles
sensitive data, the following document describes what it does and does not protect against,
and how to responsibly report a vulnerability.
Only the latest released version receives security fixes. Older versions are unsupported. Check the latest release at https://github.com/containerscrew/rsecure/releases.
| Element | Value |
|---|---|
| Cipher | AES-256-GCM (256-bit key, 128-bit tag) |
| Construction | STREAM (chunked AEAD) via aes_gcm::aead::stream::EncryptorBE32 |
| Chunk size | 131072 bytes (128 KiB), declared per-file in the header |
| Key derivation | HKDF-SHA256(ikm=master_key, salt=random 32 B per file, info="rsecure-v3-aes256gcm-stream") → 32-byte AES-256 subkey |
| STREAM nonce | Fixed all-zero 7-byte salt; uniqueness is provided by the per-file HKDF subkey, with STREAM's 4-byte BE32 counter ensuring uniqueness across chunks within the file |
| File header | RSEC (4 B) + version 0x03 (1 B) + flags (1 B) + chunk_size (u32 LE, 4 B) + HKDF salt (32 B) = 42 bytes; passphrase mode appends Argon2 params (9 B) + Argon2 salt (16 B) for 67 bytes total |
| Header authenticity | The entire on-disk header is passed as AAD on every chunk; any tampering invalidates the first GCM tag and decryption fails before any plaintext is recovered |
| Master key source | Either a 32-byte keyfile (default) or derived once-per-invocation from a passphrase via Argon2id, see below |
| Filename privacy (optional) | With --hide-name, the original filename is prepended to the plaintext stream as [u32 LE name_len][name] (so it is encrypted and authenticated like the file body) and the .enc is written under an opaque random name. The header's FLAG_ENCRYPTED_NAME (0x02) bit — part of the AAD — signals it; decrypt peels the prefix back off and restores the name. Only the leaf filename is protected: directory names, file sizes, and timestamps still leak (see Threat Model). |
The flags byte carries independent capability bits: 0x01 (FLAG_PASSPHRASE)
selects the master-key source, and 0x02 (FLAG_ENCRYPTED_NAME) marks an
embedded filename (see the format table). Both are covered by the header AAD.
flags & 0x01 == 0 (keyfile): the master key is the 32-byte keyfile passed
via -p. This is the strongest default — the master key has 256 bits of OS-RNG
entropy.
flags & 0x01 == 1 (passphrase): the master key is derived via
Argon2id(passphrase, argon2_salt, params) where the salt and parameters live in
the file header. Default parameters: m_cost = 19456 KiB (~19 MiB),
t_cost = 2, p_cost = 1, output length 32 bytes. These defaults can be
overridden per-invocation via --argon2-memory, --argon2-time, and
--argon2-parallelism; the chosen values are recorded in each file's header so
decryption picks them up automatically. Raising the parameters (more memory,
more iterations) strengthens the KDF; lowering them below the defaults weakens
it and is discouraged. The salt is generated once per invocation, so an entire
encrypt batch shares one Argon2 derivation; the decrypter caches by salt to
avoid re-running the KDF on subsequent files of the same batch.
Security in passphrase mode is bounded by the entropy of your passphrase. Argon2id raises the cost-per-attempt enough to make weak passphrases significantly harder to brute-force, but a 6-character dictionary word remains weak regardless of the KDF.
Because the AES-256 subkey is unique per file (derived from a fresh 256-bit
random salt), the (key, nonce) pair is globally unique across all files even
with a fixed STREAM nonce. This eliminates the birthday-bound nonce-collision
concern that would otherwise apply to AES-GCM's 96-bit nonce when many files
are encrypted under the same master key.
On decrypt, a sanity bound (chunk_size ≤ 16 MiB) rejects pathological headers
before any buffer is allocated, so a hostile .enc cannot trigger an unbounded
allocation.
- v1 (rsecure ≤ 0.5.0): AES-256-GCM STREAM with a 7-byte random nonce derived directly from the master key, no HKDF, no magic header.
- v2 (interim, brief release window before v3):
RSEC0x02header, HKDF-derived subkey, AAD-bound — same scheme as v3 keyfile mode but without the flags byte.
rsecure decrypt reads both transparently; the dispatcher picks the right
code path from the magic + version. New encryptions always use v3.
The cryptographic primitives are provided by the aes-gcm,
hkdf, and argon2 crates from RustCrypto,
widely-used, audited, pure-Rust implementations.
rsecure is being developed with the intent of remaining safe against a
large-scale quantum adversary. This is a stated design goal, not a formal
certification — the project is small, evolving, and has not undergone
independent cryptanalytic review. Read this section for what the intent means
in concrete terms and where the honest limits sit.
| Primitive | Classical security | Post-quantum security | Notes |
|---|---|---|---|
| AES-256-GCM | 256-bit key | ~128-bit (Grover) | NIST PQC Category 5; part of NSA CNSA 2.0 symmetric baseline. |
| HKDF-SHA256 | 256-bit preimage | ~128-bit preimage (Grover) | Used for per-file subkey derivation only; not for long-lived signatures. |
| Argon2id | Memory-hard | Memory-hard (√ speedup only) | Symmetric password KDF; unaffected by Shor. See RFC 9106. |
No asymmetric primitives are used anywhere in rsecure. There is no RSA, no
elliptic-curve Diffie–Hellman, no ECDSA, and no X25519 — so Shor's algorithm
has nothing to break in the current design. This is the single biggest
post-quantum risk in tools that rely on public-key primitives for key
agreement or signing, and rsecure sidesteps it by construction.
- No formal PQ certification exists for this implementation. NIST PQC categories apply to the primitives, not to this specific codebase or parameter choices.
- No post-quantum KEM or signature is included. rsecure does not ship ML-KEM (Kyber) or ML-DSA (Dilithium) today because there is no key-exchange or signing surface in scope. If a future feature ever requires asymmetric crypto (e.g., recipient-based encryption), the intention is to reach for NIST PQC standards rather than pre-quantum primitives.
- Legacy formats (v1, v2) inherit the parameters of their era. The post-quantum posture applies to files produced by the current v3 format; re-encrypting long-lived archives is recommended.
- Distributing keys and passphrases still relies on out-of-band channels;
the post-quantum properties of those channels are outside
rsecure's scope and remain the user's responsibility. - Side-channel resistance is best-effort (inherited from
aes-gcm) and is not itself a post-quantum property, but is listed here alongside the other honest caveats.
- NSA CNSA 2.0 (2022) — AES-256 and SHA-384+ as post-quantum symmetric baseline.
- NIST FIPS 203 (ML-KEM), FIPS 204 (ML-DSA), FIPS 205 (SLH-DSA) — standardized post-quantum asymmetric primitives, referenced here for future work only.
- NIST SP 800-208 — stateful hash-based signatures (not used by rsecure).
- Confidentiality of file contents under a chosen-plaintext attacker that does not possess the master key.
- Integrity & authenticity of each chunk — any tampering will be detected on decrypt (GCM authentication tag).
- Resistance to chunk reordering or truncation — STREAM binds chunks via a
counter and seals the final chunk with a distinct
encrypt_lastmarker. The decrypter enforces this: it only accepts a stream that terminates on that final chunk, so a ciphertext cut at a chunk boundary (or with its trailing segment removed) is rejected as truncated instead of yielding a partial plaintext. Reordering or splicing chunks breaks the per-chunk counter and fails authentication. - No catastrophic nonce reuse across files — the per-file HKDF subkey makes the
(key, nonce)pair globally unique even with a fixed STREAM nonce. - Best-effort memory hygiene for secrets. Passphrases, master keys, and
derived subkeys are wrapped in
zeroizeguards and cleared on drop. This is best-effort — Rust does not guarantee freedom from compiler-introduced copies or spilled stack slots, and swap / hibernation / core dumps can still leak secrets outside the process — but it narrows the residual-memory attack surface. - Header authenticity (v2 and v3). The entire on-disk header — magic, version, and for v3 the flags byte, chunk_size, HKDF salt, and (in passphrase mode) the Argon2 parameters and salt — is bound as AAD on every chunk. Modifying any header byte causes decryption to fail with an auth error on the first chunk, before any plaintext is written. There is no downgrade, rebinding, or wrong-key-via-salt-swap path that produces valid plaintext.
- Post-quantum design intent (symmetric-only). No RSA, ECDH, ECDSA, or X25519 is used, so Shor's algorithm has nothing to break in the current design. AES-256 and SHA-256 are only reduced to ~128-bit security by Grover. See Post-Quantum Considerations for the honest limits — this is a design goal, not a certification.
- Metadata is only partially protected. By default the leaf filename, the
directory structure, file sizes, and timestamps all remain visible — only the
file contents are encrypted. The optional
--hide-nameflag encrypts the leaf filename (writing the.encunder an opaque random name) but does not hide directory names, file sizes, or timestamps. Fully hiding structure and per-file sizes would require a single-container mode, which rsecure does not yet provide.--hide-nameonly helps if the original file is also removed (-r), otherwise the named plaintext stays on disk beside the opaque ciphertext. - Key storage is the user's responsibility. A master key file left on disk in plaintext offers no protection against an attacker with filesystem access. Use full-disk encryption, a hardware token, or a password manager for key custody.
- No forward secrecy / no post-compromise security. If the master key (or passphrase) is compromised, all past and future ciphertext under it is exposed (HKDF subkeys are derived deterministically from the master key and the per-file salt).
- No authenticated key exchange. Distributing the master key to another party is out of scope; use an out-of-band secure channel (Signal, age, GPG, in person).
- No plausible deniability. Encrypted files are clearly identifiable as such (
.encextension and structured header). - Side-channel resistance is best-effort, inherited from
aes-gcm. The crate uses constant-time arithmetic but does not formally guarantee freedom from cache or timing side channels on every target architecture. On CPUs without AES-NI (or equivalent hardware-accelerated AES), software AES is more exposed to cache-timing side channels. - Legacy v1 nonce collisions. Files encrypted by rsecure ≤ 0.5.0 used a 7-byte random nonce (56 bits), which approaches the birthday bound around 2²⁸ files (~268M) and crosses NIST's 2⁻³² safety margin around ~6k files. Re-encrypt long-lived v1 archives with the current version to migrate them to v3.
- Passphrase strength. In passphrase mode, the master key's effective
security is bounded by your passphrase's entropy. Argon2id raises the
per-attempt cost but cannot rescue a weak passphrase from an offline
brute-force attacker who obtains a
.encfile. Use a key file for the strongest guarantee, or a long, high-entropy passphrase (e.g., a diceware phrase of 6+ words).
Do not file public issues for security vulnerabilities.
Please report security issues via one of these channels:
- GitHub Security Advisories (preferred): open a private advisory at https://github.com/containerscrew/rsecure/security/advisories/new
- Email:
info@containerscrew.comwith the subject prefixed by[rsecure-sec].
Please include:
- A description of the issue and its impact.
- A minimal reproduction (file, command, expected vs. actual behavior).
- Your suggested fix, if any.
You can expect an acknowledgement within 7 days and a status update within 30 days. Coordinated disclosure is preferred; please give us a reasonable window to ship a fix before public disclosure.