The zefer encryption tool — now for your terminal.
CLI companion to zefer.carrillo.app. Encrypt and decrypt .zefer files directly from the command line using AES-256-GCM. 100% offline, cross-platform, fully compatible with the web app.
Web App · Report Bug · Request Feature · Web App Repo · Documentation
- About
- Relationship to zefer
- Features
- File Format Compatibility
- Quick Start
- Commands
- Security Options
- Scripting & Automation
- Cross-platform Support
- Tech Stack
- Project Structure
- Security Model
- Contributing
- Author
- Support
- License
zefer-cli brings the full power of zefer to your terminal. Encrypt text and files into password-protected .zefer files using AES-256-GCM — the same cryptographic standard used by the web app. Every file you create here can be opened in the browser, and vice versa.
- Zero-knowledge — no network requests during encryption or decryption
- Cross-compatible —
.zeferfiles work identically in CLI and browser - All security features — dual key, reveal key, secret question, IP restriction, expiration, attempt limits
- Scriptable — pipe-friendly, all options via flags, CI-friendly output mode
- AI-ready — built-in MCP server exposes every capability to Claude, Cursor, Windsurf, VS Code and any MCP client
zefer-cli is the official CLI companion to the zefer web app. Both projects are maintained by the same author and share:
| Component | Web App | CLI |
|---|---|---|
| Binary format | ZEFB3 / ZEFR3 | ZEFB3 / ZEFR3 (identical) |
| Encryption | AES-256-GCM (Web Crypto API) | AES-256-GCM (Node.js crypto) |
| Key derivation | PBKDF2-SHA256 | PBKDF2-SHA256 (identical params) |
| Compression | CompressionStream API | Node.js zlib (same output) |
| File compatibility | Reads/writes .zefer |
Reads/writes .zefer |
A file encrypted with zefer-cli can be decrypted at zefer.carrillo.app and vice versa.
|
Core Encryption
|
Security Layers
|
|
Developer Experience
|
Key Generator & AI
|
All .zefer files follow the same binary format used by the web app:
[ZEFB3 magic — 5 bytes]
[header length — 4 bytes big-endian]
[header JSON — public, not encrypted]
[salt — 32 bytes][base IV — 12 bytes]
[encrypted chunks — 16 MB each, unique IV per chunk]
[ZEFR3 magic — 5 bytes]
[header length — 4 bytes big-endian]
[header JSON — public, not encrypted]
[main block size — 4 bytes]
[main block: salt + IV + chunks] ← encrypted with main passphrase
[reveal block: salt + IV + chunks] ← encrypted with reveal key
Public header (readable without a passphrase): iterations, compression, hint, note, mode
Encrypted payload (invisible without the key): file metadata, expiration, secret question hash, IP allowlist, max attempts
Legacy formats ZEFER3 and ZEFER2 are supported for backward-compatible decryption only.
npm install -g zefer-cliThe command is zefer (no suffix):
zefer encrypt document.pdf -p mypassword
zefer decrypt document.pdf.zefer -p mypassword
zefer keygen
zefer info secret.zeferOr run without installing:
npx zefer-cli encrypt document.pdf -p mypasswordDownload the prebuilt binary for your platform from the latest release:
| Platform | File | Download |
|---|---|---|
| Linux x64 | zefer-linux-x64 |
↓ Download |
| Linux ARM64 | zefer-linux-arm64 |
↓ Download |
| macOS Intel | zefer-macos-x64 |
↓ Download |
| macOS Apple Silicon | zefer-macos-arm64 |
↓ Download |
| Windows x64 | zefer-win-x64.exe |
↓ Download |
Linux / macOS:
curl -L https://github.com/carrilloapps/zefer-cli/releases/latest/download/zefer-linux-x64 -o zefer
chmod +x zefer
sudo mv zefer /usr/local/bin/zefer # install system-wide (optional)
zefer --helpmacOS Apple Silicon:
curl -L https://github.com/carrilloapps/zefer-cli/releases/latest/download/zefer-macos-arm64 -o zefer
chmod +x zefer
sudo mv zefer /usr/local/bin/zefer
zefer --helpWindows (PowerShell):
Invoke-WebRequest -Uri https://github.com/carrilloapps/zefer-cli/releases/latest/download/zefer-win-x64.exe -OutFile zefer.exe
.\zefer.exe --helpVerify integrity with the checksums.txt included in each release:
curl -L https://github.com/carrilloapps/zefer-cli/releases/latest/download/checksums.txt | sha256sum -c --ignore-missinggit clone https://github.com/carrilloapps/zefer-cli.git
cd zefer-cli
npm install
npm run build
node dist/index.js --helpnpx tsc --noEmit # Type check
npm run build # Buildzefer encrypt [input] [options]
Arguments:
input File to encrypt. Use "-" to read from stdin.
Options:
-o, --output <path> Output path (default: <input>.zefer)
-p, --passphrase <p> Passphrase (prompted if omitted)
-2, --second <p> Second passphrase — enables dual-key mode
-r, --reveal <key> Reveal key — creates ZEFR3 dual-block file
-t, --text <content> Encrypt text directly instead of a file
--hint <hint> Public hint (visible without passphrase)
--note <note> Public note (visible without passphrase)
-q, --question <q> Secret question
-a, --answer <a> Secret question answer
--ttl <minutes> Expiration in minutes. 0 = never (default: 0)
-i, --iterations <n> PBKDF2 iterations. 0 = auto-benchmark (default: 0)
-c, --compression <m> none | gzip | deflate | deflate-raw (default: none)
--max-attempts <n> Max decryption attempts. 0 = unlimited (default: 0)
--allowed-ips <ips> Comma-separated IP allowlist (IPv4 or IPv6)
--dual-key Enable dual-key mode (requires --second)
--verbose Show security details before encrypting
Examples:
# Encrypt a file (passphrase prompted)
zefer encrypt report.pdf
# Encrypt with all options
zefer encrypt secret.txt \
-p "main-passphrase" \
-2 "second-key" \
--dual-key \
--reveal "reveal-passphrase" \
-q "What is your pet's name?" \
-a "firulais" \
--ttl 1440 \
--max-attempts 3 \
--hint "two parts required" \
-c gzip \
-o secret.zefer \
--verbose
# Encrypt text directly
zefer encrypt --text "Top secret note" -p mypassword -o note.zefer
# Pipe from stdin
echo "my secret" | zefer encrypt -p mypassword -o secret.zefer
cat document.pdf | zefer encrypt -p mypassword -o document.zeferzefer decrypt <input> [options]
Arguments:
input .zefer file to decrypt. Use "-" to read from stdin.
Options:
-o, --output <path> Output path.
Default: stdout for text mode, original filename for file mode.
-p, --passphrase <p> Passphrase (prompted if omitted)
-2, --second <p> Second passphrase (dual-key mode)
-a, --answer <a> Secret question answer (prompted if needed)
--force Overwrite existing output file
--verbose Show public file info before decrypting
Examples:
# Decrypt (passphrase prompted)
zefer decrypt secret.zefer
# Decrypt to stdout (text mode)
zefer decrypt note.zefer -p mypassword
# Decrypt with all options
zefer decrypt secret.zefer \
-p "main-passphrase" \
-2 "second-key" \
-a "firulais" \
-o recovered.txt \
--force
# Pipe the output
zefer decrypt note.zefer -p mypassword | grep "important"
# Use the reveal key instead of the main passphrase
zefer decrypt secret.zefer -p "reveal-passphrase" -a "firulais"zefer keygen [options]
Options:
-m, --mode <mode> unicode | secure | alpha | hex | base58 | pin | uuid (default: secure)
-l, --length <n> Length in characters, 1-2048 (default: 64)
-n, --count <n> Number of keys, 1-50 (default: 1)
--exclude-ambiguous Exclude ambiguous characters (0 O 1 l I)
--exclude <chars> Exclude specific characters from the alphabet
--require-all Guarantee lower/upper/digit/symbol when available
--no-repeats Never emit the same character twice in a row
--group <n> Insert a dash every N characters (cosmetic)
--sort Sort keys from strongest to weakest score
--quiet Raw values only (pipe-friendly, no analysis)
Every key is scored (strength bar + effective bits), exactly like the web /generator.
| Mode | Character set | Best for |
|---|---|---|
unicode |
Latin, Cyrillic, Arabic, CJK, Greek, Emoji (~668 symbols) | Maximum entropy |
secure |
Latin + symbols + accented (189 symbols) | General passphrases |
alpha |
A-Z a-z 0-9 |
Alphanumeric tokens |
hex |
0-9 a-f |
Tokens, hashes |
base58 |
Bitcoin alphabet (no 0 O I l) |
Keys read aloud or hand-copied |
pin |
0-9 |
Devices, cards, safes |
uuid |
UUID v7 (RFC 9562) | Unique identifiers |
Examples:
zefer keygen # 64-char secure key, scored
zefer keygen -m base58 -l 24 --group 6 # XqaiTi-CBpTQC-3Em58S-X9u4XQ
zefer keygen -m pin -l 8 # numeric PIN
zefer keygen -n 10 --sort # 10 keys, strongest first
zefer keygen --exclude-ambiguous --require-all
zefer keygen --quiet -n 5 # raw values for pipingExposes every zefer capability as Model Context Protocol tools over stdio, so any MCP-compatible client (Claude Code, Claude Desktop, Cursor, Windsurf, VS Code, custom agents) can encrypt, decrypt, generate and analyze locally. Dependency-free JSON-RPC — works in the npm install and in every standalone binary.
Full integration guide (all clients, transports, tool schemas, troubleshooting): docs/MCP.md
| Tool | What it does |
|---|---|
zefer_encrypt |
Encrypt text or a file into .zefer (all options: dual key, reveal key, TTL, question, compression, IPs…) |
zefer_decrypt |
Decrypt a .zefer file — text returned inline, files written to disk |
zefer_keygen |
Generate scored keys — 7 modes + advanced options, sorted strongest first |
zefer_analyze_password |
Full strength report: entropy, 4 attack scenarios, NIST/OWASP/AES-128/post-quantum compliance |
zefer_inspect |
Deep .zefer analysis without the passphrase: structure, entropy, SHA-256, KDF resistance, observations |
Smart detection — the binary knows how it was launched:
zefer mcp→ MCP server (explicit)- spawned with no arguments + piped stdin (how MCP clients start servers) → MCP server (automatic)
- a human terminal (TTY) → regular CLI, always
Example tool call (what your agent sends under the hood):
{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"zefer_encrypt",
"arguments":{"text":"api_key=abc123","passphrase":"my-strong-pass",
"outputPath":"secret.zefer","ttlMinutes":1440,"compression":"gzip"}}}Everything runs locally — no network, no telemetry, passphrases never leave the machine.
Full security report for any password — parity with the web /generator analyzer.
zefer analyze [password] # prompted securely if omitted
Reports: strength score, estimated alphabet, maximum/effective entropy, total keyspace, post-quantum entropy (Grover), crack time across 4 attack scenarios (throttled login 10²/s → nation-state 10¹⁵/s), compliance checks (NIST SP 800-63B, OWASP ≥64 bits, long-term ≥100 bits, AES-128, post-quantum) and weakness detection (leaked lists, sequences, keyboard patterns, repeats, years). 100% local — the password never leaves your machine.
Shows the public header plus a deep security analysis (parity with the web
/analyzer): structural integrity (chunk-framing walk, corruption detection),
encrypted chunk count, estimated content size, ciphertext randomness (Shannon
entropy), salt/IV hex, file SHA-256 fingerprint, a passphrase-resistance table
derived from the file's PBKDF2 iterations, and severity-tagged security
observations — all without the passphrase.
Show the public header of a .zefer file without decrypting it.
zefer info secret.zefer▸ zefer info
secret.zefer
────────────────────────────────────────────────
File secret.zefer (943 B)
Format ZEFR3 (binary, with reveal key)
Mode file
Iterations 1,000,000
Compression gzip
Hint two parts required
Secret question, IP restriction, expiration, and max attempts
are inside the encrypted payload and cannot be read without the passphrase.
All security metadata is stored inside the encrypted payload — invisible to anyone without the passphrase.
| Option | Flag | Description |
|---|---|---|
| Expiration | --ttl <minutes> |
File becomes undecryptable after N minutes |
| Max attempts | --max-attempts <n> |
Blocks after N failed attempts (tracked at ~/.zefer/attempts.json) |
| Secret question | -q + -a |
Extra authentication, answer hashed with PBKDF2 (100k iterations) |
| IP allowlist | --allowed-ips |
Restrict decryption to specific IPs |
| Dual key | --dual-key + -2 |
Requires two separate passphrases |
| Reveal key | --reveal |
Creates a second encrypted block — share without exposing main passphrase |
| Public hint | --hint |
Visible without passphrase — helps the recipient remember |
| Public note | --note |
Public message — visible without passphrase |
zefer-cli is designed to be scriptable. Passphrase via flag, output to stdout, pipe-friendly:
# Encrypt all .env files in a directory
for f in *.env; do
zefer encrypt "$f" -p "$ZEFER_PASS" -o "encrypted/$f.zefer"
done
# Decrypt and pipe to another command
zefer decrypt secrets.zefer -p "$ZEFER_PASS" | jq '.api_key'
# CI/CD: encrypt a secret file before committing
zefer encrypt .env.production \
-p "$CI_ENCRYPT_PASS" \
--ttl 10080 \
-o .env.production.zefer
# Non-interactive password via environment
ZEFER_PASS="$(cat ~/.zefer_passphrase)"
zefer decrypt backup.zefer -p "$ZEFER_PASS" -o backup.tar.gz| Variable | Effect |
|---|---|
ZEFER_ASCII=1 |
Force ASCII output (no Unicode spinner/blocks) |
ZEFER_UNICODE=1 |
Force Unicode output |
NO_COLOR=1 |
Disable all color output (standard) |
| Platform | Terminal | Mode |
|---|---|---|
| Linux | Any TTY | Unicode + raw mode |
| macOS | Terminal.app, iTerm2 | Unicode + raw mode |
| Windows | Windows Terminal, VS Code | Unicode + raw mode |
| Windows | PowerShell 5 | Unicode + muted readline |
| Windows | cmd.exe / conhost | ASCII fallback + muted readline |
| All | Piped / non-TTY | Silent (no spinner), reads passphrase from stdin |
| All | CI=true |
ASCII fallback, no spinner |
Password input is always hidden — either via setRawMode (Unix/Windows Terminal) or readline output muting (legacy Windows).
| Layer | Technology |
|---|---|
| CLI framework | Ink 5 (React 18 for the terminal) |
| Argument parsing | Commander 12 |
| Language | TypeScript 5 |
| Encryption | Node.js crypto module (AES-256-GCM) |
| Key derivation | PBKDF2-SHA256 via crypto.pbkdf2 (async, libuv thread pool) |
| Compression | Node.js zlib module (gzip, deflate, deflate-raw) |
| Colors | chalk 5 |
| Build | tsup (ESM bundle, 50 KB) |
| Runtime | Node.js 20+ |
src/
commands/
encrypt.tsx # Encrypt command — Ink UI + file I/O
decrypt.tsx # Decrypt command — Ink UI + all security checks
keygen.tsx # Key generator — 5 modes
info.tsx # Public header viewer
lib/
crypto.ts # AES-256-GCM + PBKDF2 — Node.js port of zefer/app/lib/crypto.ts
chunked-crypto.ts # 16 MB chunked encryption — Node.js port
compression.ts # Gzip/Deflate — Node.js zlib port
zefer.ts # ZEFB3/ZEFR3 format encode/decode — Node.js port
progress.ts # Real-time progress tracking (same stage weights as web)
keygen.ts # CSPRNG key generation (5 modes)
attempts.ts # Attempt counter (~/.zefer/attempts.json)
ui/
Header.tsx # CLI header component
ProgressBar.tsx # ASCII / Unicode progress bar
Spinner.tsx # Braille / ASCII spinner
StatusLine.tsx # Combined status + progress
utils/
format.ts # File sizes, dates, durations
prompt.ts # Password input (cross-platform)
terminal.ts # Unicode / ASCII capability detection
index.ts # Commander setup + command wiring
dist/
index.js # Compiled ESM bundle (50 KB, includes shebang)
docs/
ARCHITECTURE.md # Technical deep-dive
CONTRIBUTING.md # Development setup + conventions
SECURITY.md # Threat model + cryptographic details
RELEASING.md # npm token setup, GitHub Actions, version workflow
- All encryption/decryption is done locally — no network requests, no servers
- PBKDF2 runs in the libuv thread pool (non-blocking, Ink UI stays responsive)
- Each file has a unique random salt and IV — no two encryptions are identical
- AES-GCM auth tag verifies ciphertext integrity before decryption
- Secret question answer is normalized (trim + lowercase) and hashed with PBKDF2 (100k iterations)
- IP restriction, expiration, and attempt limit are inside the encrypted payload — invisible without the key
- Timing attack mitigation: minimum 100ms response on wrong passphrase
- Attempt counter is file-specific (keyed by first 40 bytes of ciphertext) and stored at
~/.zefer/attempts.json
| Primitive | Algorithm | Parameters |
|---|---|---|
| Symmetric encryption | AES-256-GCM | 256-bit key, 96-bit IV, 128-bit auth tag |
| Key derivation | PBKDF2-SHA256 | 300k / 600k / 1M iterations, 256-bit salt |
| Answer hashing | PBKDF2-SHA256 | 100,000 iterations |
| Random generation | crypto.randomBytes |
OS-level CSPRNG |
For the full threat model, see docs/SECURITY.md.
Contributions are welcome. Please read docs/CONTRIBUTING.md and CODE_OF_CONDUCT.md first.
git clone https://github.com/carrilloapps/zefer-cli.git
cd zefer-cli
npm install
npm run dev # Run directly with tsx (no build step)
npm run build # Compile to dist/
npm run typecheck # TypeScript type check
Jose Carrillo — Senior Fullstack Developer & Tech Lead
10+ years building scalable, efficient, and secure software. Based in Colombia.
If you find zefer-cli useful, consider supporting the project:
MIT © 2026 Jose Carrillo