Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
[[enprot]] == Engyon: enprot
image:https://github.com/engyon/enprot/actions/workflows/tests.yml/badge.svg["Build Status", link="https://github.com/engyon/enprot/actions?workflow=tests"] image:https://img.shields.io/badge/MSRV-1.85-blue["MSRV 1.85", link="https://blog.rust-lang.org/2025/02/20/Rust-1.85.0.html"]
Enprot is a confidentiality processor for text and source code files. It lets you embed encrypted, stored, and authenticated segments directly inside any text-based file — source code, documentation, configuration — without breaking the host language's syntax.
- Human-editable markup: EPT directives sit inside host-language comments, so files stay valid C, Rust, Python, AsciiDoc, HTML, or LaTeX.
- Content-addressed storage (CAS): segments can be sanitized out of the document and stored by their SHA3-256 hash. Same content always produces the same CAS file — deterministic, dedup-friendly.
- Authenticated encryption: AES-256-SIV (default), AES-256-GCM, or AES-256-GCM-SIV. Password-derived keys via Argon2, Scrypt, or PBKDF2.
- Deterministic AEAD: optional
aes-256-gcm-det/aes-256-gcm-siv-detvariants derive the nonce from the plaintext so identical content always produces identical ciphertext — enabling CAS dedup on encrypted segments. - Subcommand CLI:
encrypt,decrypt,store,fetch,encrypt-store,verify,list,passthrough,completions,keygen,sign,verify-sig.
=== Installation
==== From source
Enprot requires https://github.com/randombit/botan[Botan 3.x]
(brew install botan on macOS; see ci/install.sh for Linux).
or build locally:
==== Shell completions
After installation, generate completions for your shell:
=== Quick start
=== EPT markup
An EPT file is a normal text file containing directives hidden inside
host-language comments. The default separators are // <( (left) and
)> (right), which work for C, C++, Rust, AsciiDoc, JavaScript, and
other //-comment languages.
A simple example (sample/test.ept):
hello, this is a test file
// <( BEGIN GEHEIM )>
Secret line 1
Secret line 2
// <( BEGIN Agent_007 )>
James Bond
// <( END Agent_007 )>
// <( END GEHEIM )>
// <( BEGIN Agent_007 )>
Super secret line 3
// <( END Agent_007 )>
Five directive types exist:
[cols="1,4", options="header"]
|===
| Directive | Meaning
| BEGIN <WORD> | Opens a named segment. Segments can nest.
| END <WORD> | Closes the matching segment.
| STORED <WORD> <hash> | A CAS pointer — the segment's content lives in the CAS file named <hash>.
| ENCRYPTED <WORD> [hash] [pbkdf:… cipher:…] | An encrypted segment. If a hash is present, the ciphertext is in CAS; otherwise inline DATA lines follow.
| DATA <base64> | One or more base64-encoded ciphertext lines inside an ENCRYPTED block.
|===
==== Host-language separators
Use -l/-r to override the separators for non-// languages, or use
the --lang preset:
[cols="1,2,1", options="header"]
|===
| --lang | Left sep | Right sep
| raw | <( | )>
| c | // <( | )>
| python | # <( | )>
| html | <!-- <( | )> -->
| latex | % <( | )>
|===
Example: encrypt a Python file's segments with # <( comments:
=== Subcommands
==== encrypt
Encrypt WORD segments inline (ciphertext stays in the document):
If no -k is given, you are prompted interactively (with repeat).
Encrypt-specific options (run enprot encrypt --help for the full list):
[cols="1,3", options="header"]
|===
| Option | Description
| --cipher <ALG> | aes-256-siv (default), aes-256-gcm, aes-256-gcm-siv, aes-256-gcm-det, aes-256-gcm-siv-det
| --pbkdf <ALG> | argon2 (default), scrypt, pbkdf2-sha256, pbkdf2-sha512, legacy
| --pbkdf-msec <MSEC> | Time-budget for KDF parameter tuning (default 100)
| --pbkdf-salt-len <N> | Salt length in bytes (default 16)
| --pbkdf-params <K=V,…> | Override KDF parameters manually (testing)
| --pbkdf-salt <HEX> | Fixed salt (testing)
| --pbkdf-disable-cache | Disable PBKDF cache (slower, but no shared derivation state)
| --cipher-iv <HEX> | Fixed IV (testing)
|===
==== decrypt
Decrypt WORD segments:
Multiple WORDs are comma-separated or repeated. Multiple -k flags
supply passwords for different WORDs.
==== store
Sanitize WORD segments to CAS — replace the segment content with a
STORED <WORD> <hash> pointer and write the original content to a file
named by its SHA3-256 hash:
After storing, the CAS directory contains the content:
$ ls cas
cea67c3ef34ff899793b557e9178c1b97bbcfe9722df2f6d35d2d0c91d2c1fe4
The same content always produces the same hash, so storing is idempotent — re-storing identical content is a no-op.
==== fetch
Restore WORD segments from CAS:
==== encrypt-store
Encrypt and store in one step. The ciphertext goes to CAS and is referenced by hash:
==== verify
Check file integrity without decrypting. Validates markup structure, CAS pointer resolution (file exists + hash matches), and extfield formatting:
Output is OK or FAIL per file. Exits non-zero on any failure —
useful in CI pipelines.
==== list
List all WORD segments in a file with their type and crypto metadata:
For multiple files, each is headed with == <path> ==.
==== passthrough
Parse and re-write a file without applying any transform. Useful for validating markup or measuring parse performance:
==== completions
Print a shell completion script:
|
==== keygen / sign / verify-sig
Detached Ed25519 signatures (PQC Phase 1; ML-DSA and composite
constructions are tracked in TODO.finalize/).
# Generate a keypair (PEM, one file each).
# Sign a file → produces FILE.sig (raw 64-byte Ed25519 signature).
# Verify → reads document.txt.sig by default.
Override the signature path with --sig-file PATH (verify-sig) or
-o PATH (sign). Both commands read stdin when no FILE is given.
=== Common options
These flags work with every subcommand (before or after the subcommand name):
[cols="1,3", options="header"]
|===
| Flag | Description
| -v, --verbose | Show parse/transform/write progress on stderr
| -q, --quiet | Suppress non-essential output
| -k, --key <WORD=PASSWORD> | Supply a password (repeatable; one pair per flag)
| -c, --casdir <DIR> | CAS directory (default ./cas if it exists, else .)
| -l, --left-separator <SEP> | Left EPT separator (default // <()
| -r, --right-separator <SEP> | Right EPT separator (default )>)
| --lang <LANG> | Preset separators: raw, c, python, html, latex
| --policy <POLICY> | Crypto policy: default or nist
| --defaults <POLICY> | Load a policy's defaults without enforcing it
| --fips | Force FIPS-compliant algorithms (implies --policy=nist)
| --max-depth <N> | Maximum nesting depth (default 100; 0 = infinite)
| -w, --word <WORD> | WORD(s) to operate on (repeatable, also comma-separated)
| -o, --output <FILE> | Output file for the previous input (repeatable)
| -p, --prefix <PREFIX> | Prefix for output filenames (directory mode if ends with /)
| --output-dir <DIR> | Write outputs into DIR by basename (conflicts with -p)
|===
=== Deterministic encryption
By default, AES-GCM and AES-GCM-SIV use random nonces — encrypting the same plaintext twice produces different ciphertexts. This breaks CAS deduplication for encrypted segments.
The -det cipher variants derive the nonce from the plaintext via
HKDF + HMAC, making encryption fully deterministic:
[cols="1,4", options="header"]
|===
| Cipher | Construction
| aes-256-gcm-det | enc_key = HKDF(master, "enc"), iv = HMAC(iv_key, pt)[..12], then AES-256-GCM
| aes-256-gcm-siv-det | Same HKDF/HMAC construction, then AES-256-GCM-SIV (misuse-resistant)
|===
Same (password, plaintext) always produces the same ciphertext, so
encrypt-store on identical segments deduplicates in CAS.
Usage:
=== Multi-file processing
Process wildcards in one invocation. Passwords are prompted once:
Output to a different file:
Output to a directory (basename preserved):
# or equivalently:
Plain prefix (prepended verbatim — legacy behavior):
# produces: build_src/main.ept, build_src/lib.ept, …
=== Working on source code
EPT directives inside comments don't break compilation. Enprot's own
source has an encrypted AUTHOR block in src/lib.rs:
// <( ENCRYPTED AUTHOR )>
// <( DATA X417HVMRRAs6Z1xGo5yY4TxUQ2tpAHEKQ1sg9+kfku5uUikK3y2tODtsUiGqfRGW )>
// <( DATA xUCGYFu02BCdqPM7uuX5UNvbfrLvKkj6gLYwg/cr42PJmr4o5xnw1qo= )>
// <( END AUTHOR )>
Decrypt it:
=== Crypto policies
Two built-in policies control which algorithms are permitted:
[cols="1,2,2", options="header"]
|===
| | default | nist
| Default cipher | AES-256-SIV | AES-256-GCM
| Default PBKDF | Argon2 | PBKDF2-SHA-512
| Allowed hashes | Any | SHA3-256, SHA3-512
| Min salt length | None | 16 bytes
| Min iterations | None | 1000
| GCM IV width | Any | 96 bits
|===
--fips forces the nist policy. On Linux, it also auto-engages
when /proc/sys/crypto/fips_enabled reads 1.
See docs/fips.adoc for details on what a fully FIPS-validated build
would require.
=== Compatibility
Documents encrypted by enprot <=0.3.1 still decrypt. The wire format of
the pbkdf: and cipher: extended fields is unchanged:
// <( ENCRYPTED <keyword> [cas-hash] pbkdf:$<id>$<k=v,k=v>$<base64-salt> cipher:$<alg>$iv=<base64-iv> )>
When the ciphertext is in CAS rather than inline, the CAS hash replaces
the inner DATA lines and the cipher: field carries the IV.
The legacy PBKDF (plain SHA3-512 truncation) is retained for
decrypting old blobs; selecting it for new encryption prints a warning.
=== Development
See CONTRIBUTING.md for the full guide. Quick reference:
&&
Architecture docs: CLAUDE.md. Upgrade history: TODO.upgrade/,
TODO.audit/, TODO.issues/, TODO.finalize/.
=== License
BSD-2-Clause. See LICENSE file.