zefer-cli
v1.1.1
Published
CLI for Zefer — AES-256-GCM file and text encryption. Official CLI companion to zefer.carrillo.app.
Downloads
379
Maintainers
Readme
zefer-cli
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
Table of Contents
- 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
About
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
Relationship to zefer
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.
Features
Core Encryption
- AES-256-GCM with PBKDF2-SHA256 (300k–1M iterations)
- Text mode and file mode (any format, any size)
- Chunked encryption (16 MB per chunk, unique IVs)
- Gzip / Deflate compression before encryption
- Auto-benchmark: picks optimal iteration count for the current machine
Security Layers
- Dual passphrase — two-person authorization (
-2) - Reveal key — share without exposing main key (
-r) - Secret question with PBKDF2-hashed answer (100k iterations)
- IP restriction — IPv4/IPv6 allowlist (
--allowed-ips) - Expiration — TTL in minutes (
--ttl) - Max decryption attempts (
--max-attempts) - Attempt counter persisted at
~/.zefer/attempts.json
Developer Experience
- Pipe-friendly:
stdin/stdoutsupport - Non-interactive mode for CI/CD scripts
- Real-time progress bar with Ink (React CLI)
- Cross-platform: Linux, macOS, Windows
- ASCII fallback for
cmd.exeand legacy terminals --verbosefor detailed operation info
Key Generator
- 5 modes:
alpha,hex,uuid,secure,unicode - Configurable length (default: 64 characters)
- Generate multiple keys at once (
--count) - Rejection sampling — no modulo bias
- OS-level CSPRNG via
crypto.randomBytes
File Format Compatibility
All .zefer files follow the same binary format used by the web app:
ZEFB3 — Single passphrase
[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 — With reveal key
[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 keyPublic 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.
Quick Start
Option 1 — Via npm ✦ recommended
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 mypasswordOption 2 — Standalone binary (no Node.js required)
Download 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-missingOption 3 — From source
git clone https://github.com/carrilloapps/zefer-cli.git
cd zefer-cli
npm install
npm run build
node dist/index.js --helpVerify
npx tsc --noEmit # Type check
npm run build # BuildCommands
zefer encrypt
zefer 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 encryptingExamples:
# 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
zefer 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 decryptingExamples:
# 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
zefer keygen [options]
Options:
-m, --mode <mode> alpha | hex | uuid | secure | unicode (default: secure)
-l, --length <n> Length in characters (default: 64)
-n, --count <n> Number of keys to generate (default: 1)| Mode | Character set | Best for |
|---|---|---|
| secure | Base64url (A-Z a-z 0-9 - _) | General passphrases |
| alpha | Printable ASCII + symbols | High-entropy passphrases |
| hex | 0-9 a-f | Tokens, hashes |
| uuid | UUID v4 format | Unique identifiers |
| unicode | Latin, Cyrillic, Arabic, CJK, Emoji | Maximum entropy |
Examples:
zefer keygen # 64-char secure key
zefer keygen -m hex -l 32 # 32-char hex token
zefer keygen -m alpha -l 128 # 128-char printable ASCII
zefer keygen -m uuid # UUID v4
zefer keygen -m unicode -l 24 # 24 Unicode characters
zefer keygen -n 5 -l 32 # 5 keys at oncezefer info
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.Security Options
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 |
Scripting & Automation
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.gzEnvironment variables
| 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) |
Cross-platform Support
| 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).
Tech Stack
| 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+ |
Project Structure
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 workflowSecurity Model
- 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.
Contributing
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 checkAuthor
Jose Carrillo — Senior Fullstack Developer & Tech Lead
10+ years building scalable, efficient, and secure software. Based in Colombia.
Support
If you find zefer-cli useful, consider supporting the project:
License
MIT © 2026 Jose Carrillo
