Password Generation

Secure password generation and strength testing.

Password Generation Fundamentals

# ENTROPY SOURCES (ranked by quality)
# 1. /dev/random    - blocking, highest entropy (use for keys)
# 2. /dev/urandom   - non-blocking, sufficient for passwords
# 3. openssl rand   - uses /dev/urandom internally
# 4. $RANDOM        - AVOID! predictable PRNG

# OPENSSL GENERATION (most portable)
openssl rand -base64 32                          # 32 bytes → 44 chars (with padding)
openssl rand -base64 32 | tr -d '/+=' | head -c 32  # URL-safe, exactly 32 chars
openssl rand -hex 32                             # 32 bytes → 64 hex chars

# URANDOM GENERATION (no dependencies)
< /dev/urandom tr -dc 'A-Za-z0-9' | head -c 32   # Alphanumeric only
< /dev/urandom tr -dc 'A-Za-z0-9!@#$%' | head -c 32  # With symbols

# PYTHON GENERATION (strongest stdlib)
python -c "import secrets; print(secrets.token_urlsafe(32))"   # URL-safe, 43 chars
python -c "import secrets; print(secrets.token_hex(32))"       # 64 hex chars
python -c "import secrets; print(secrets.token_bytes(32).hex())"  # Same

# ENTROPY CALCULATION
# Alphanumeric (62 chars): log2(62) = 5.95 bits/char
# 32 chars = 190 bits entropy (excellent)
# 16 chars = 95 bits entropy (minimum recommended)
# 8 chars = 48 bits entropy (too weak!)

# PASSWORD STRENGTH TIERS
# < 64 bits:   Weak (brute-forceable)
# 64-80 bits:  Acceptable (short-term)
# 80-128 bits: Strong (long-term)
# > 128 bits:  Paranoid (cryptographic keys)

Gopass Password Management

# GENERATE AND STORE (one command)
gopass generate v3/domains/d000/services/new-service 32
# Generates 32-char password and stores it

# GENERATE WITH SYMBOLS
gopass generate -s v3/domains/d000/services/new-service 32
# -s includes symbols (!@#$%^&*)

# GENERATE WITHOUT SYMBOLS (API keys)
gopass generate -n v3/domains/d000/api-keys/github 40
# -n alphanumeric only

# GENERATE AND SHOW
gopass generate -c v3/domains/d000/services/new-service 32
# -c copies to clipboard instead of showing

# GENERATE FOR SPECIFIC CHARSET
gopass generate --symbols='!@#$%' v3/domains/d000/services/svc 32

# RETRIEVE PASSWORD
gopass show v3/domains/d000/services/new-service
gopass show -c v3/domains/d000/services/new-service  # Copy to clipboard
gopass show -o v3/domains/d000/services/new-service  # Password only (no metadata)

# RETRIEVE WITH TIMEOUT (auto-clear clipboard)
gopass show -c -C 45 v3/domains/d000/services/new-service
# Clears clipboard after 45 seconds

# SEARCH PASSWORDS
gopass find service-name                         # Search by name
gopass search "pattern"                          # Search content too

# LIST ALL ENTRIES
gopass ls                                        # Tree view
gopass ls v3/domains/d000                        # Subtree only

# INFRASTRUCTURE PASSWORD PATTERNS
# Naming convention: v3/domains/d000/{category}/{service}
gopass generate v3/domains/d000/network/pfsense-admin 32
gopass generate v3/domains/d000/identity/ise-admin 32
gopass generate v3/domains/d000/k8s/argocd-admin 32
gopass generate v3/domains/d000/observability/grafana-admin 32

# MULTILINE ENTRIES (structured secrets)
gopass edit v3/domains/d000/services/complex-service
# Opens editor for:
# password-line-1
# user: admin
# url: https://service.example.com
# api_key: xxxx
# notes: Additional information

# RETRIEVE SPECIFIC FIELD
gopass show v3/domains/d000/services/complex-service user
gopass show v3/domains/d000/services/complex-service api_key

Age Encryption for Secrets

# AGE: Modern encryption tool (replaces GPG for file encryption)
# https://github.com/FiloSottile/age

# GENERATE AGE KEYPAIR
age-keygen -o ~/.config/age/key.txt
# Output:
# Public key: age1abc123...

# VIEW PUBLIC KEY
age-keygen -y ~/.config/age/key.txt

# ENCRYPT FILE TO RECIPIENT
age -r age1abc123... plaintext.txt > encrypted.age
age -r age1abc123... -o encrypted.age plaintext.txt  # Explicit output

# ENCRYPT TO MULTIPLE RECIPIENTS
age -r age1abc123... -r age1def456... secret.txt > secret.age

# ENCRYPT WITH PASSPHRASE (no keys needed)
age -p secret.txt > secret.age
# Prompts for passphrase

# DECRYPT WITH KEY
age -d -i ~/.config/age/key.txt encrypted.age > plaintext.txt

# DECRYPT WITH PASSPHRASE
age -d encrypted.age > plaintext.txt
# Prompts for passphrase

# PIPE-FRIENDLY ENCRYPTION
echo "secret-api-key" | age -r age1abc123... > api-key.age
age -d -i ~/.config/age/key.txt api-key.age

# ENCRYPT TARBALL
tar czf - sensitive-dir/ | age -r age1abc123... > sensitive-dir.tar.gz.age

# DECRYPT TARBALL
age -d -i ~/.config/age/key.txt sensitive-dir.tar.gz.age | tar xzf -

# INFRASTRUCTURE SECRET PATTERNS

# Encrypt environment file
age -r age1abc123... ~/.secrets/environments/domains/d000/dev/network.env \
    > ~/.secrets/environments/domains/d000/dev/network.env.age

# Decrypt for use
age -d -i ~/.config/age/key.txt network.env.age > /tmp/network.env
source /tmp/network.env
rm /tmp/network.env  # Cleanup!

# Verify encryption (should show binary/encrypted data)
file encrypted.age    # Output: data
head -c 50 encrypted.age | xxd  # Shows age header

# AGE VS GPG
# Age: Simpler, faster, smaller keys, no web of trust
# GPG: More features, wider support, complex key management
# Use age for: file encryption, secrets, automation
# Use GPG for: email signing, package signing, key servers

Age Operational Patterns

# MASTER KEY LOCATION (your setup)
AGE_KEY=~/.secrets/.metadata/keys/master.age.key

# ------------------------------------
# SEARCH ENCRYPTED FILES (pipe to grep)
# ------------------------------------

# Search for pattern in encrypted file
age -d -i $AGE_KEY document.adoc.age | grep -iE "client.*(id|secret)"

# Search with context
age -d -i $AGE_KEY document.adoc.age | grep -B2 -A2 "password"

# Search multiple encrypted files
for f in ~/.secrets/documents/notes/*.age; do
    echo "=== $f ==="
    age -d -i $AGE_KEY "$f" 2>/dev/null | grep -i "oauth" || true
done

# Find which encrypted file contains a pattern
find ~/.secrets -name "*.age" -exec sh -c '
    age -d -i '"$AGE_KEY"' "$1" 2>/dev/null | grep -q "CLIENT_ID" && echo "$1"
' _ {} \;

# ------------------------------------
# VIEW ENCRYPTED FILES
# ------------------------------------

# View in pager
age -d -i $AGE_KEY document.age | less

# View with syntax highlighting (bat)
age -d -i $AGE_KEY document.adoc.age | bat --language=asciidoc

# View first N lines
age -d -i $AGE_KEY document.age | head -50

# Extract specific section
age -d -i $AGE_KEY notes.age | sed -n '/^## Setup/,/^## /p'

# ------------------------------------
# EDIT ENCRYPTED FILES
# ------------------------------------

# Edit in place with temp file (SECURE pattern)
TEMP=$(mktemp)
trap "rm -f $TEMP" EXIT
age -d -i $AGE_KEY document.age > "$TEMP"
$EDITOR "$TEMP"
age -r "$(age-keygen -y $AGE_KEY)" -o document.age "$TEMP"

# ------------------------------------
# BATCH OPERATIONS
# ------------------------------------

# Decrypt all .age files in directory to /tmp
for f in ~/.secrets/documents/notes/*.age; do
    out="/tmp/$(basename "$f" .age)"
    age -d -i $AGE_KEY "$f" > "$out"
done

# Re-encrypt after bulk edit
for f in /tmp/*.adoc; do
    age -r "$(age-keygen -y $AGE_KEY)" -o "~/.secrets/documents/notes/$(basename "$f").age" "$f"
    rm "$f"  # Cleanup plaintext
done

# List encrypted files by size
find ~/.secrets -name "*.age" -exec ls -lh {} \; | awk '{print $5, $9}'

# ------------------------------------
# DSOURCE INTEGRATION
# ------------------------------------

# Load secrets via dsource (your dsec tool)
DSEC_EVAL_VERIFIED=true dsec source d000 dev/network

# Decrypt and source environment
age -d -i $AGE_KEY ~/.secrets/environments/domains/d000/dev/network.env.age | \
    while IFS='=' read -r key value; do
        export "$key=$value"
    done

# Quick inline extraction (env files)
CLIENT_ID=$(age -d -i $AGE_KEY oauth.env.age | grep CLIENT_ID | cut -d= -f2)

# ------------------------------------
# ENCRYPTED JSON/YAML (age + jq/yq)
# ------------------------------------

# Extract fields from encrypted JSON
age -d -i $AGE_KEY tokens.json.age | jq -r '.client_id'

# Formatted extraction with labels
age -d -i $AGE_KEY tokens.json.age | jq -r '"Client ID: " + .client_id, "Client Secret: " + .client_secret'

# Extract and export to env vars
eval "$(age -d -i $AGE_KEY tokens.json.age | jq -r '@sh "CLIENT_ID=\(.client_id) CLIENT_SECRET=\(.client_secret)"')"

# Extract nested fields
age -d -i $AGE_KEY config.json.age | jq -r '.oauth.credentials.client_id'

# Extract array elements
age -d -i $AGE_KEY accounts.json.age | jq -r '.accounts[].email'

# Filter and extract
age -d -i $AGE_KEY services.json.age | jq -r '.services[] | select(.name=="gmail") | .token'

# Encrypted YAML with yq
age -d -i $AGE_KEY config.yaml.age | yq '.database.password'

# Transform encrypted data
age -d -i $AGE_KEY old-format.json.age | jq '{id: .client_id, secret: .client_secret}' | \
    age -r "$(age-keygen -y $AGE_KEY)" -o new-format.json.age

# ------------------------------------
# BACKUP AND RECOVERY
# ------------------------------------

# Backup key securely (encrypted with passphrase)
age -p -o master.age.key.backup $AGE_KEY

# Verify backup decrypts
age -d master.age.key.backup | head -1  # Should show AGE-SECRET-KEY-...

# Re-encrypt everything with new key
NEW_KEY=~/.secrets/.metadata/keys/new-master.age.key
for f in ~/.secrets/**/*.age; do
    age -d -i $AGE_KEY "$f" | age -r "$(age-keygen -y $NEW_KEY)" -o "${f}.new"
    mv "${f}.new" "$f"
done

# ------------------------------------
# GOTCHAS
# ------------------------------------

# WRONG: Leaving plaintext on disk
age -d -i $AGE_KEY secret.age > /tmp/secret.txt
# Now secret.txt is on disk!

# CORRECT: Use temp file with trap or pipe directly
age -d -i $AGE_KEY secret.age | grep "pattern"  # Never touches disk

# WRONG: Hardcoding key path
age -d -i ~/.config/age/key.txt file.age  # Works on ONE machine

# CORRECT: Use variable or standard location
age -d -i "$AGE_KEY" file.age  # Portable across systems

# WRONG: Storing passphrase-encrypted files for automation
age -p -o secret.age plaintext  # Can't automate without human

# CORRECT: Use key-based encryption for automation
age -r age1... -o secret.age plaintext  # Automatable

TOTP/2FA Secrets Management

# TOTP (Time-based One-Time Password)
# RFC 6238 - 6-digit codes rotating every 30 seconds

# GOPASS TOTP SUPPORT
# Store TOTP secret as 'totp' field
gopass edit v3/domains/d000/identity/github-2fa
# Add line: totp: JBSWY3DPEHPK3PXP

# Generate current TOTP code
gopass otp v3/domains/d000/identity/github-2fa
# Output: 123456

# TOTP FROM RAW SECRET (without gopass)
# Convert hex to base32 if needed
HEX_SECRET="48656c6c6f21"
echo "$HEX_SECRET" | xxd -r -p | base32
# Output: JBSWY3DPEHPK3PXP

# Generate TOTP with oathtool
oathtool --totp -b JBSWY3DPEHPK3PXP
# -b = base32 input

# Generate TOTP with specific time
oathtool --totp -b --now="2024-01-15 12:00:00 UTC" JBSWY3DPEHPK3PXP

# Python TOTP generation
python3 << 'EOF'
import hmac
import struct
import time
import base64

secret = base64.b32decode("JBSWY3DPEHPK3PXP")
counter = int(time.time()) // 30
msg = struct.pack(">Q", counter)
h = hmac.new(secret, msg, "sha1").digest()
offset = h[-1] & 0x0F
code = (struct.unpack(">I", h[offset:offset+4])[0] & 0x7FFFFFFF) % 1000000
print(f"{code:06d}")
EOF

# QR CODE FOR TOTP ENROLLMENT
# Format: otpauth://totp/LABEL?secret=BASE32SECRET&issuer=ISSUER
qrencode -o totp-qr.png \
    "otpauth://totp/GitHub:evan@example.com?secret=JBSWY3DPEHPK3PXP&issuer=GitHub"

# BACKUP TOTP SECRETS
# Export all TOTP entries from gopass
gopass ls | while read entry; do
    secret=$(gopass show "$entry" totp 2>/dev/null)
    if [ -n "$secret" ]; then
        echo "$entry: $secret"
    fi
done > totp-backup.txt

# Encrypt the backup
age -r age1abc123... totp-backup.txt > totp-backup.txt.age
rm totp-backup.txt

HashiCorp Vault Secrets

# VAULT KV SECRETS ENGINE
# Path: kv/ (Key-Value secrets)

# Store secret
vault kv put kv/database/postgres password="$(openssl rand -base64 32)"

# Retrieve secret
vault kv get kv/database/postgres
vault kv get -field=password kv/database/postgres  # Just the value
vault kv get -format=json kv/database/postgres | jq -r '.data.data.password'

# List secrets
vault kv list kv/
vault kv list kv/database/

# Delete secret (soft delete - recoverable)
vault kv delete kv/database/postgres

# Undelete (recover)
vault kv undelete -versions=1 kv/database/postgres

# Destroy (permanent)
vault kv destroy -versions=1 kv/database/postgres

# VERSION HISTORY
vault kv metadata get kv/database/postgres
# Shows all versions with timestamps

vault kv get -version=2 kv/database/postgres
# Retrieve specific version

# DYNAMIC SECRETS (database credentials)
vault read database/creds/readonly-role
# Output:
# lease_id       database/creds/readonly-role/abc123
# lease_duration 1h
# username       v-token-readonly-abc123
# password       A1b2C3d4E5f6G7h8

# TRANSIT ENGINE (encryption as a service)
# Encrypt data
vault write transit/encrypt/my-key plaintext=$(echo "secret" | base64)
# Output: ciphertext: vault:v1:abc123...

# Decrypt data
vault write transit/decrypt/my-key ciphertext="vault:v1:abc123..."
# Output: plaintext: c2VjcmV0  (base64)
echo "c2VjcmV0" | base64 -d
# Output: secret

# INFRASTRUCTURE PATTERNS

# Store ISE credentials
vault kv put kv/network/ise \
    username="admin" \
    password="$(gopass show -o v3/domains/d000/identity/ise-admin)"

# Store database credentials
vault kv put kv/database/postgres \
    username="app_user" \
    password="$(openssl rand -base64 24)" \
    host="postgres.inside.domusdigitalis.dev" \
    port="5432"

# Retrieve for application
export PGPASSWORD=$(vault kv get -field=password kv/database/postgres)
export PGUSER=$(vault kv get -field=username kv/database/postgres)
export PGHOST=$(vault kv get -field=host kv/database/postgres)

# APPROLE AUTHENTICATION (for automation)
# Get role-id (static)
vault read auth/approle/role/netapi/role-id

# Get secret-id (dynamic, one-time use)
vault write -f auth/approle/role/netapi/secret-id

# Login with AppRole
vault write auth/approle/login \
    role_id="abc123" \
    secret_id="def456"
# Returns token for subsequent requests

SSH Key Management

# KEY GENERATION (modern - Ed25519)
ssh-keygen -t ed25519 -C "evan@domusdigitalis.dev"
# -C comment (usually email)
# Creates: ~/.ssh/id_ed25519 (private) and ~/.ssh/id_ed25519.pub (public)

# KEY GENERATION WITH CUSTOM PATH
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_vault -C "vault-signing-key"
# Separate key for Vault SSH CA

# RSA KEY (legacy compatibility)
ssh-keygen -t rsa -b 4096 -C "evan@domusdigitalis.dev"
# -b 4096 for security (minimum 2048)

# ECDSA KEY
ssh-keygen -t ecdsa -b 521 -C "evan@domusdigitalis.dev"
# -b 256, 384, or 521 bits

# CHANGE PASSPHRASE
ssh-keygen -p -f ~/.ssh/id_ed25519
# Prompts for old and new passphrase

# VIEW KEY FINGERPRINT
ssh-keygen -lf ~/.ssh/id_ed25519.pub
# Output: 256 SHA256:abc123... evan@domusdigitalis.dev (ED25519)

# VIEW KEY IN DIFFERENT FORMATS
ssh-keygen -lf ~/.ssh/id_ed25519.pub -E md5     # MD5 format
ssh-keygen -lf ~/.ssh/id_ed25519.pub -E sha256  # SHA256 (default)

# SSH AGENT MANAGEMENT
eval $(ssh-agent -s)                             # Start agent
ssh-add ~/.ssh/id_ed25519                        # Add key
ssh-add -l                                       # List keys
ssh-add -L                                       # List public keys
ssh-add -d ~/.ssh/id_ed25519                     # Remove specific
ssh-add -D                                       # Remove all

# YUBIKEY/FIDO2 SSH KEYS
ssh-keygen -t ed25519-sk -C "yubikey-nano"       # FIDO2 resident key
# -sk = security key (requires touch)

# VIEW SSH CERTIFICATE
ssh-keygen -Lf ~/.ssh/id_ed25519_vault-cert.pub
# Shows: Type, Public key, Signing CA, Key ID, Serial, Valid, Principals, Extensions

# AUTHORIZED_KEYS MANAGEMENT
# Add key
cat ~/.ssh/id_ed25519.pub >> ~/.ssh/authorized_keys

# Verify permissions (critical!)
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pub

# Parse authorized_keys
awk '{print NR": "$3" ("$1")"}' ~/.ssh/authorized_keys
# Shows: line_number: comment (key_type)

# Remove duplicate keys
sort -u -k2 ~/.ssh/authorized_keys > /tmp/ak_clean
mv /tmp/ak_clean ~/.ssh/authorized_keys

# COUNT KEYS
wc -l < ~/.ssh/authorized_keys

# INFRASTRUCTURE PATTERNS

# Deploy key to multiple hosts
for host in vault-01 ise-01 bind-01; do
    ssh-copy-id -i ~/.ssh/id_ed25519.pub "evanusmodestus@$host"
done

# Vault SSH CA signing
vault write -field=signed_key ssh/sign/domus-client \
    public_key=@$HOME/.ssh/id_ed25519_vault.pub \
    valid_principals="evanusmodestus,admin,root,ansible" \
    > ~/.ssh/id_ed25519_vault-cert.pub

# Refresh SSH agent after signing
ssh-add -d ~/.ssh/id_ed25519_vault 2>/dev/null
ssh-add ~/.ssh/id_ed25519_vault

Infrastructure Secret Patterns

# DSEC/DSOURCE ENVIRONMENT LOADING
# Load development secrets
dsource d000 dev/network
# Exports: ISE_HOST, ISE_USER, ISE_PASS, WLC_HOST, etc.

# Load observability secrets
dsource d000 dev/observability
# Exports: WAZUH_API_URL, WAZUH_API_USER, WAZUH_API_PASSWORD, etc.

# Verify loaded variables
env | grep -E '^(ISE_|WLC_|WAZUH_|PFSENSE_)' | sort

# SECRET ROTATION WORKFLOW

# 1. Generate new password
NEW_PASS=$(openssl rand -base64 24)

# 2. Update in service (example: ISE)
# (Manual step or API call)

# 3. Update in gopass
echo "$NEW_PASS" | gopass insert -f v3/domains/d000/identity/ise-admin

# 4. Update age-encrypted env file
age -d -i ~/.config/age/key.txt ~/.secrets/environments/domains/d000/dev/network.env.age \
    > /tmp/network.env
sed -i "s/ISE_PASS=.*/ISE_PASS='$NEW_PASS'/" /tmp/network.env
age -r $(age-keygen -y ~/.config/age/key.txt) /tmp/network.env \
    > ~/.secrets/environments/domains/d000/dev/network.env.age
rm /tmp/network.env

# 5. Verify
dsource d000 dev/network
echo $ISE_PASS

# NETAPI CREDENTIAL PATTERNS
# ISE
dsource d000 dev/network
netapi ise mnt sessions

# pfSense
netapi pfsense dns list

# WLC
netapi wlc status

# SECRET AUDIT
# List all gopass entries with age
gopass ls | while read entry; do
    modified=$(gopass show "$entry" 2>/dev/null | grep -i 'modified:' | head -1)
    echo "$entry: $modified"
done

# Find secrets older than 90 days
# (Manual review - gopass doesn't track modification dates well)

# EMERGENCY SECRET COMPROMISE
# If credential leaked:
# 1. Rotate immediately
gopass generate v3/domains/d000/compromised-service 32

# 2. Update service
# (API or manual)

# 3. Audit access logs
# Check who used the old credential

# 4. Update encrypted files
# Re-encrypt any files using old credential

# 5. Document incident
# Log in security runbook

Password/Secret Gotchas

# WRONG: Using $RANDOM for security
password=$RANDOM$RANDOM$RANDOM
# $RANDOM is predictable! Only 32k possible values per call

# CORRECT: Use cryptographic source
password=$(openssl rand -base64 24)

# WRONG: Password in command history
mysql -u root -pMySecretPassword
# Password visible in: history, ps, /proc

# CORRECT: Use environment variable or prompt
mysql -u root -p
# Or:
MYSQL_PWD="$password" mysql -u root

# WRONG: Hardcoding secrets in scripts
API_KEY="sk_live_abc123"
# Committed to git, visible in logs

# CORRECT: Load from secure source
API_KEY=$(gopass show -o v3/domains/d000/api-keys/service)
# Or:
source <(dsource d000 dev/network)

# WRONG: Storing secrets in plain text
echo "password123" > ~/.secret
# Anyone with file access sees it

# CORRECT: Encrypt at rest
echo "password123" | age -r age1abc123... > ~/.secret.age

# WRONG: Base64 "encryption"
echo "secret" | base64 > encoded.txt
# Base64 is encoding, NOT encryption!

# CORRECT: Use actual encryption
echo "secret" | age -r age1abc123... > encrypted.age

# WRONG: Reusing passwords
# Same password for ISE, WLC, switches
# One compromise = total compromise

# CORRECT: Unique passwords per service
gopass generate v3/domains/d000/network/ise-admin 32
gopass generate v3/domains/d000/network/wlc-admin 32
gopass generate v3/domains/d000/network/switch-admin 32

# WRONG: Short TOTP secrets
# Some services provide short hex: "DEADBEEF"
# Only 32 bits of entropy!

# CORRECT: Minimum 128-bit TOTP secrets
# 26+ character base32 string

# WRONG: Leaving decrypted secrets in /tmp
age -d secret.age > /tmp/secret.txt
# Use it...
# Forget to delete!

# CORRECT: Trap for cleanup
cleanup() { rm -f /tmp/secret.txt; }
trap cleanup EXIT
age -d secret.age > /tmp/secret.txt
# Use it...
# Automatically cleaned up on exit

# WRONG: echo password (in logs)
echo "Password is: $PASSWORD"
# Goes to stdout, potentially logged

# CORRECT: Direct to stderr or suppress
printf "Password set for %s\n" "$USER" >&2
# Or just don't echo passwords at all

Quick Reference

# GENERATION
openssl rand -base64 32 | tr -d '/+=' | head -c 32  # URL-safe 32 chars
openssl rand -hex 32                                 # 64 hex chars
python -c "import secrets; print(secrets.token_urlsafe(32))"

# GOPASS
gopass generate path/to/secret 32        # Generate + store
gopass generate -s path/to/secret 32     # With symbols
gopass show -o path/to/secret            # Password only
gopass show -c path/to/secret            # Copy to clipboard
gopass otp path/to/secret                # Generate TOTP

# AGE ENCRYPTION
age -r age1public... file > file.age     # Encrypt
age -d -i ~/.config/age/key.txt file.age # Decrypt
age -p file > file.age                   # Passphrase mode

# VAULT
vault kv put kv/path key=value           # Store
vault kv get -field=key kv/path          # Retrieve
vault kv list kv/                        # List

# SSH KEYS
ssh-keygen -t ed25519 -C "comment"       # Generate
ssh-keygen -lf ~/.ssh/id_ed25519.pub     # Fingerprint
ssh-keygen -Lf certificate.pub           # View cert
ssh-add ~/.ssh/id_ed25519                # Add to agent

# ENVIRONMENT LOADING
dsource d000 dev/network                 # Load network secrets
dsource d000 dev/observability           # Load monitoring secrets
env | grep -E '^ISE_|^WLC_' | sort       # Verify loaded

# TOTP
oathtool --totp -b SECRET                # Generate code
gopass otp path/to/entry                 # From gopass

# ENTROPY (bits per character)
# Alphanumeric (62): 5.95 bits/char
# +Symbols (95): 6.57 bits/char
# Minimum: 16 chars = ~95 bits
# Recommended: 24+ chars = ~143+ bits