ende is a CLI for exchanging secrets (tokens/passwords/etc.) between developers using encrypted payloads instead of plaintext.
Core guarantees:
- Encrypted with recipient public key so only intended recipients can decrypt
- Signed by sender so tampering/spoofing is detectable
- Local keyring is the trust root (GitHub username mode is optional)
- Decrypt requires a trusted sender pin (
sender_key_id+ signing public key match)
brew tap DevopsArtFactory/ende https://github.com/DevopsArtFactory/homebrew-ende
brew install ende
ende --versionReplace vX.Y.Z with the release tag.
Linux (auto-detect architecture):
VERSION=vX.Y.Z
ARCH="$(uname -m)"
case "$ARCH" in
x86_64) ARCH="amd64" ;;
aarch64|arm64) ARCH="arm64" ;;
*) echo "Unsupported arch: $ARCH" >&2; exit 1 ;;
esac
curl -fL "https://github.com/DevopsArtFactory/ende/releases/download/${VERSION}/ende-linux-${ARCH}" -o ende
chmod +x ende
sudo mv ende /usr/local/bin/ende
ende --versionWindows (auto-detect architecture, PowerShell):
$Version = "vX.Y.Z"
$ArchRaw = [System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture.ToString().ToLower()
switch ($ArchRaw) {
"x64" { $Arch = "amd64" }
"arm64" { $Arch = "arm64" }
default { throw "Unsupported arch: $ArchRaw" }
}
Invoke-WebRequest -Uri "https://github.com/DevopsArtFactory/ende/releases/download/$Version/ende-windows-$Arch.exe" -OutFile "ende.exe"
.\ende.exe --versionEach developer generates their own local key once.
./ende key keygen --name <my-id>Example:
./ende key keygen --name alicekeygen prints:
- your recipient public key
- your signing public key
- your
share:code (ENDE-PUB-1:...)
You can print the share code again later:
./ende key share --name aliceSet your default signer once:
./ende key use --name aliceGenerated assets:
~/.config/ende/keyring.yaml~/.config/ende/keys/<id>.agekey(decryption private key)~/.config/ende/keys/<id>.signkey(signing private key)
Bob sends his share: code to Alice.
On Alice's side:
./ende register
# share code (ENDE-PUB-1:...): ENDE-PUB-1:...
# peer name override (optional, Enter to use the shared name):Non-interactive form:
./ende register --alias bob --share 'ENDE-PUB-1:...'./ende doctorecho 'TOKEN=abc123' | ./ende encrypt -t bobImportant:
- Default output is armored text to stdout.
--sign-asis required unless a default signer is set viaende key use.--tocan be repeated for multi-recipient delivery.
Multi-recipient example:
echo 'TOKEN=abc123' | ./ende encrypt -t bob -t diana -o secret.endeEncrypt from a file:
./ende encrypt -t bob -f secrets.env -o secret.txtWrite raw binary instead of text:
echo 'TOKEN=abc123' | ./ende encrypt -t bob --binary -o secret.endePrompt for a secret interactively:
./ende encrypt -t bob --prompt -o secret.txtReview peer and output details before encrypting:
echo 'TOKEN=abc123' | ./ende encrypt -t bob --confirm -o secret.txtFor automation:
echo 'TOKEN=abc123' | ./ende encrypt -t bob --confirm --yes -o secret.txt./ende verify -i secret.ende./ende decrypt -i secret.ende -o secret.txtText envelope (armored) input is also supported:
./ende decrypt -i secret.txt -o secret.outImportant:
- Default is
--verify-required=true; decrypt fails if signature verification fails. - If sender is not pinned in trusted senders, decrypt fails.
- Plaintext stdout is blocked by default. Use
--out -explicitly to allow stdout.
Explicit stdout example:
./ende decrypt -i secret.ende -o -Other plaintext output options:
./ende decrypt -i secret.ende -o decrypted.txt --no-clobber
./ende decrypt -i secret.ende --out-temp
./ende decrypt -i secret.txt --text-outThe default trust model is the local keyring. GitHub mode is a convenience layer.
Register example:
./ende recipient add --github octocat --key "age1..." --key-index 0Behavior:
- Looks up GitHub SSH keys and stores a TOFU pin
- On re-registration, pin mismatch causes hard failure
- Actual encryption still uses the provided
agerecipient key (--key)
ende enc=ende encryptende dec=ende decryptende v=ende verifyende k=ende keyende rcpt=ende recipientende snd=ende senderende reg=ende registerende unreg=ende unregisterende key kg=ende key keygenende key ls=ende key list
Generate local key material.
Options:
--name <id>: key ID (required)--set-default <bool>: set this key as default signer (defaulttrue)--export-public: export recipient/signing public keys to files--export-dir <path>: directory for exported public key files--export-prefix <name>: file prefix for exported files (default:--name)
Export public key material.
Options:
--name <id>: key ID (required)--type recipient|signing-public: export type (required)
Import recipient public key as alias.
Options:
--name <alias>: recipient alias (required)--file <path>: file containing age recipient key (required)
List local keys, recipients, and trusted senders.
Set default signer key ID for encrypt.
Options:
--name <id>: key ID- positional arg
<id>is also supported (ende key use alice)
Print a share code for an existing local key.
Options:
--name <id>: key ID- positional arg
<id>is also supported (ende key share alice)
Add recipient alias.
Options:
--alias <name>: alias (required for local mode)--key <age1...>: age recipient public key (required)--share <token>: share token (ENDE-PUB-1:...) for recipient+sender auto registration--github <username>: GitHub username (optional)--key-index <n>: GitHub SSH key index to pin (default0)
Show recipient details.
Rotate recipient public key.
Options:
--key <age1...>: new recipient public key (required)
Encrypt + sign payload.
Options:
-t, --to <alias|github:user|age1...>: recipient target(s), repeatable (required)-s, --sign-as <key-id>: sender signing key ID (optional if default signer exists)-i, --in <path|->: input (default-= stdin)-f, --file <path>: input file path (alias of--in)-o, --out <path|->: output (default-= stdout)--text: output ASCII-armored envelope (defaulttrue)--binary: output raw binary envelope--prompt: prompt secret input interactively--confirm: show a summary and ask before encrypting--yes: skip the confirmation prompt when--confirmis used
Verify + decrypt envelope.
Options:
-i, --in <path|->: input (default-)-o, --out <path|->: plaintext output (--out -must be explicit)--verify-required <bool>: enforce signature verification (defaulttrue)--text-out: print decrypted plaintext to stdout--no-clobber: refuse to overwrite an existing plaintext file--out-temp: write plaintext to a temporary0600file and print the path
Verify signature without decrypting.
Options:
-i, --in <path|->: input (default-)
Add trusted sender signing key pin.
Options:
--id <sender-id>: sender ID to trust (required)--signing-public <base64>: Ed25519 public key (required)--github <username>: optional metadata--force: overwrite existing sender
Show trusted sender details.
Rotate trusted sender signing public key.
Options:
--signing-public <base64>: new Ed25519 public key (required)
List trusted senders.
Register recipient + trusted sender in one step.
Options:
--alias <name>: alias to register--share <token>: share token (ENDE-PUB-1:...) for one-step registration--recipient-key <age1...>: recipient key for manual one-step registration--signing-public <base64>: sender signing public key for manual one-step registration--force: overwrite existing recipient/sender entries
Remove a registered alias and its matching trusted sender entry.
-
Trust root
- Local keyring with pinned keys is the default trust root
- GitHub username is convenience metadata, not a trust root
- Trusted sender pin (
sender id -> signing public key) is mandatory for secure decrypt
-
No custom crypto primitive implementation
- Uses
filippo.io/age - Avoids implementing custom cryptographic algorithms
- Uses
-
Authentication + integrity
- Ed25519 signature is required
- Signature target is
ciphertext + canonical metadata (CBOR) - Verification is done before decryption for early rejection
-
Secure defaults
--sign-asrequired inencryptunless default signer is configuredverify-required=trueby default indecrypt- unknown sender IDs are rejected during decrypt
- Plaintext stdout blocked by default (
--out -required) - Private key file permission
0600enforced on Unix-like systems - Windows skips POSIX mode checks because NTFS ACLs do not map to
0600
-
Operational safety
- Secrets are handled via file/stdin paths, not CLI secret arguments
- Reduces shell history leakage risk
- Standardize key ID naming conventions.
- Verify recipient fingerprints out-of-band during onboarding.
- Rotate keys periodically (
recipient rotate). - Add verify/decrypt regression checks in CI.
For the latest options table and raw help output, see: