openssl — the crypto Swiss Army knife you already have installed

openssl has hundreds of subcommands and you need exactly six — but those six cover hashes, base64, passwords, TLS debugging, cert generation, and file encryption. Here they are.

OpenSSL is two things: a library that half the internet’s TLS runs on, and a command line tool you already have. The CLI is famously cryptic, but a short list of recipes covers almost everything a terminal user actually needs. The classic gotcha: macOS ships LibreSSL as /usr/bin/openssl — for the commands here, install the real thing with brew and it lands at /opt/homebrew/opt/openssl@3 (OpenSSL 3.6.4 at time of writing). Every recipe below was run against that binary; digests, base64, and rand behave the same on LibreSSL, while enc -pbkdf2 and the s_client plumbing below are checked on OpenSSL 3.

install

macOS
brew install openssl@3
# then use the full path or alias it:
echo 'alias openssl=/opt/homebrew/opt/openssl@3/bin/openssl' >> ~/.zshrc
Debian/Ubuntu
sudo apt install openssl
Fedora
sudo dnf install openssl
Arch
sudo pacman -S openssl

examples

# SHA-256 digest of a file (same value as `shasum -a 256`)
openssl dgst -sha256 backup.tar.gz
# Base64: encode stdin, then decode it back
echo -n "hello terminal" | openssl base64
echo "aGVsbG8gdGVybWluYWw=" | openssl base64 -d
# A 32-char random password (24 bytes -> 32 base64 chars)
openssl rand -base64 24
# Pure hex randomness, for API keys and nonces
openssl rand -hex 16
# Inspect a live TLS cert: who it's for, who signed it,
# whether the chain verified (0 = ok)
echo | openssl s_client -connect github.com:443 \
  -servername github.com 2>/dev/null | grep -E "subject=|issuer=|Verify"
# Just the dates, via the pipe into openssl x509
echo | openssl s_client -connect github.com:443 -servername github.com \
  2>/dev/null | openssl x509 -noout -subject -dates
# One-line self-signed cert for local HTTPS dev:
#   req -x509   make a certificate, not a signing request
#   -newkey rsa:2048 -keyout/-out   new key + where each file goes
#   -days 365   validity window
#   -nodes      NO password on the key (NE des = no DES)
#   -subj       skip the interactive question prompt
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem \
  -days 365 -nodes -subj "/CN=myproject.local"
# Password-encrypt a file (AES-256-CBC + PBKDF2 key stretching);
# the "Salted__" header in the output is normal
openssl enc -aes-256-cbc -pbkdf2 -in notes.txt -out notes.enc \
  -pass pass:correcthorse
# Decrypt: same cipher, same password, -d to reverse
openssl enc -d -aes-256-cbc -pbkdf2 -in notes.enc \
  -pass pass:correcthorse
# Keep passwords out of shell history: read from an env var
export NOTE_PW=$(openssl rand -base64 18)
openssl enc -aes-256-cbc -pbkdf2 -in notes.txt -out notes.enc \
  -pass env:NOTE_PW

pro tip

Drop -pass entirely and openssl prompts for the password interactively — nothing lands in your history, and the command in your scrollback stays reusable. Combine with -pass env: in scripts for the best of both.

honest limits

enc file encryption is honest but bare: AES-256-CBC has no integrity check at all. The “bad decrypt” error is just a padding check, so a wrong password occasionally “succeeds” and writes garbage, and tampered files aren’t reliably detected. Without -pbkdf2 you get weak legacy key derivation, and AEAD ciphers like AES-GCM, which would add authentication, are rejected outright (“AEAD ciphers not supported”). For files whose secrecy actually matters, use age or your existing tooling; enc earns its keep for quick at-rest scrambling of files on disks you already control. The self-signed cert above also warns in every browser by construction — there’s no CA to vouch for it — which is fine for localhost, useless for the public internet. And none of this is LibreSSL-guaranteed: flag behavior outside the recipes above was not verified on macOS’s /usr/bin/openssl.