UNPKG

aiwg

Version:

Deployment tool and support utility for AI context. Copies agents, skills, commands, rules, and behaviors into the paths each AI platform reads (Claude Code, Codex, Copilot, Cursor, Warp, OpenClaw, and 6 more) so one source of truth works across 10 platfo

156 lines (115 loc) 6.61 kB
--- id: crypto-flag-verification severity: HIGH applies_to: [all-agents, applied-cryptographer] tags: [cryptography, cli-tools, openssl, defaults] --- # Cryptographic CLI Flag Verification **Enforcement Level**: HIGH **Scope**: Any code or script invoking low-level crypto CLI tools **Framework**: security-engineering ## Rule When invoking low-level cryptographic CLI tools (`openssl`, `gpg`, `age`, `7z`, `dd` with crypto pipes, etc.), the invocation MUST specify all KDF, mode, and iteration parameters explicitly. Tool defaults are frequently insecure or deprecated, and silent fallbacks are common. Specifically: - `openssl enc` MUST include `-pbkdf2 -iter <N>` (N ≥ 600,000 for SHA-256) AND an AEAD mode (`-aes-256-gcm`, `-chacha20-poly1305` if available). Without `-pbkdf2`, `openssl enc` defaults to `EVP_BytesToKey` — a single MD5 iteration. Without an AEAD mode, the output is unauthenticated (also see `no-unauthenticated-encryption`). - `gpg --symmetric` MUST include `--s2k-mode 3 --s2k-count <high>` AND `--s2k-cipher-algo AES256 --s2k-digest-algo SHA512`. Default S2K count varies by version and is often insufficient. - `7z` archive encryption is acceptable for casual use; for security-sensitive archives, use `age` or a libsodium-based program instead. - `zip --encrypt` (legacy ZipCrypto) is BROKEN. Use 7z or age. ## Why CLI crypto tools are designed for backwards compatibility and breadth, not for safe defaults. `openssl enc` in particular has multiple footguns: - Default key derivation (`-pass`) without `-pbkdf2` is `EVP_BytesToKey`, which uses a single MD5 iteration over `password || salt`. Brute-force speed: ~10^9 attempts/sec on a single GPU. A 10-character password is recovered in seconds. - Default mode is CBC (unauthenticated). `-aes-256-cbc` is unauthenticated, allowing tampering and padding-oracle attacks. - Salt is auto-generated by default (good) but the file format binds salt and ciphertext loosely (`Salted__` magic + 8 bytes), which has been deprecated in OpenSSL 3.0+. The combination of weak default KDF + unauthenticated mode is what review finding H6 caught: `openssl enc -aes-256-cbc -pass fd:0` was nominally claimed to use "PBKDF2 100k iterations", but the actual command line lacked `-pbkdf2 -iter`, making the protection ~five orders of magnitude weaker than claimed. This rule exists because the cost of confirming a flag is grep, and the cost of missing a flag is total compromise. Source: review finding H6 (2026-05-03 gap analysis). ## How to apply ### Detection Grep patterns to flag for review: ```bash # openssl enc without -pbkdf2 — BLOCK grep -rn "openssl enc" --include="*.sh" --include="*.py" --include="Makefile" \ | grep -v "\-pbkdf2" # openssl enc with CBC — BLOCK (also no-unauthenticated-encryption) grep -rn "openssl enc.*-aes.*-cbc" # openssl enc reading from stdin without explicit flags grep -rn "openssl enc" | grep -v "\-iter" # gpg --symmetric without explicit S2K grep -rn "gpg --symmetric" --include="*.sh" \ | grep -v "\-\-s2k-mode 3" # zip --encrypt — broken construction grep -rn "zip.*\-\-encrypt" grep -rn "zip.*-e " | grep -v "/\*" ``` ### Remediation #### `openssl enc` — required form ```bash # Correct invocation: AEAD mode + explicit KDF + iteration count openssl enc -aes-256-gcm \ -pbkdf2 -iter 600000 \ -in plaintext \ -out ciphertext \ -pass fd:0 ``` But for new code, **don't use `openssl enc`**. Use a small Python program around libsodium instead: ```python #!/usr/bin/env python3 """Encrypt stdin to stdout using XChaCha20-Poly1305. Reads passphrase from fd 3 (caller responsibility to pipe it cleanly). """ import os, sys import nacl.secret, nacl.pwhash, nacl.utils passphrase = os.read(3, 4096).rstrip(b"\n") salt = nacl.utils.random(nacl.pwhash.argon2id.SALTBYTES) key = nacl.pwhash.argon2id.kdf( nacl.secret.SecretBox.KEY_SIZE, passphrase, salt, opslimit=nacl.pwhash.argon2id.OPSLIMIT_INTERACTIVE, memlimit=nacl.pwhash.argon2id.MEMLIMIT_INTERACTIVE, ) box = nacl.secret.SecretBox(key) plaintext = sys.stdin.buffer.read() ct = box.encrypt(plaintext) # wire format: salt (16) || ct (24-byte nonce + plaintext + 16-byte tag) sys.stdout.buffer.write(salt + ct) ``` The whole program is ~20 lines, has one dependency (`pynacl`, audited C lib underneath), uses Argon2id for the password, and produces an AEAD ciphertext with all parameters explicit. #### `gpg --symmetric` — required form ```bash gpg --symmetric \ --s2k-mode 3 \ --s2k-count 65011712 \ --s2k-cipher-algo AES256 \ --s2k-digest-algo SHA512 \ --compress-algo none \ --batch --passphrase-fd 0 \ -o ciphertext.gpg \ plaintext ``` `s2k-count` must be a power of two between 1024 and 65011712 (per RFC 4880). Use the maximum unless latency is unacceptable. #### `age` — generally safe defaults ```bash # Encrypt to a recipient (X25519 or SSH key) age -r age1xyz... -o ct.age plaintext # Encrypt with passphrase (prompts interactively; uses scrypt internally) age -p -o ct.age plaintext ``` `age` has good defaults; verify the binary signature before use, and pin a specific version in production scripts. ## Verification checklist for any crypto CLI invocation Before approving any code that calls a crypto CLI: - [ ] Mode is AEAD (or paired with a separate MAC per `no-unauthenticated-encryption`) - [ ] KDF is explicit (`-pbkdf2 -iter N`, `--s2k-mode 3 --s2k-count N`) - [ ] Iteration / cost parameter is current per OWASP guidance - [ ] Password/key is read from fd or env, never `-k password` or `-pass pass:...` (visible in `ps`) - [ ] Output format embeds enough metadata for clean decryption (KDF params, salt, mode) - [ ] No silent fallbacks (e.g., `openssl enc` without `-aes-*` flag has version-dependent default) - [ ] Decryption verifies authentication BEFORE any structural processing (padding, parsing) ## Linked rules - `no-unauthenticated-encryption` — most common pairing: missing flags AND missing authentication - `no-adhoc-kdf` — `openssl enc` without `-pbkdf2` falls back to an ad-hoc-equivalent KDF (single MD5) - `no-key-reuse-across-purposes` — when a CLI tool internally derives multiple keys, verify they're domain-separated ## References - OpenSSL `enc(1)` man page — read the FILE FORMATS and OPTIONS sections - RFC 4880 §3.7 — OpenPGP S2K (string-to-key) specification - OWASP Cryptographic Storage Cheat Sheet — symmetric encryption guidance - "PSA: Don't use `openssl enc`" — recurring blog topic (search "openssl enc considered harmful") - Filippo Valsorda's `age` specification — `age-encryption.org/v1`