| name | encrypt-sensitive-data |
| description | Encrypts sensitive data at rest, in transit, and per-field using AEAD-only ciphers (AES-256-GCM or ChaCha20-Poly1305 — never ECB, never unauthenticated CBC, never raw RSA) — envelope encryption where a KMS-held KEK wraps a per-record/per-tenant DEK, per-column field encryption for PII with deterministic-vs-randomized chosen per query need, strict unique-nonce/IV discipline (random 96-bit or counter, NEVER reused under one key), AAD binding ciphertext to its context (tenant/row id), versioned keys + rotation that re-wraps DEKs without re-encrypting data, TLS 1.2+/1.3 with mTLS and modern cipher suites, and — critically — passwords are HASHED with argon2id/bcrypt, NOT encrypted. Distinct from secrets-management (stores the app secrets/keys this skill consumes) and map-privacy-data-gdpr (the legal PII/erasure obligations encryption helps satisfy). |
| when_to_use | You must protect sensitive data — encrypting PII/PHI/card data at rest, a per-column/field-level encryption scheme, envelope encryption with a KMS (AWS KMS/GCP KMS/Vault Transit), key rotation, choosing a cipher/mode/nonce strategy, enforcing TLS/mTLS, or hashing passwords. Distinct from secrets-management (storing and injecting the KEKs/API keys/credentials — that skill provisions the keys; this one uses them to encrypt data) and map-privacy-data-gdpr (the legal classification/erasure/residency duties that encryption and crypto-shredding help you meet). |
When to Use
Reach for this skill when the task is making sensitive data cryptographically protected — at rest, in transit, or field-by-field:
- "Encrypt SSNs / card numbers / health records / PII columns in the database"
- "Set up envelope encryption with AWS KMS / GCP KMS / Vault Transit (DEK + KEK)"
- "Rotate our encryption keys" / "we need versioned keys without re-encrypting everything"
- "Which cipher/mode — is AES-CBC okay? do we need a separate MAC? what nonce?"
- "Enforce TLS 1.3 / mutual TLS between services with modern cipher suites"
- "Are we storing passwords correctly?" (hash, don't encrypt)
- "Make a user's data unrecoverable on account deletion" (crypto-shredding)
NOT this skill:
- Storing/injecting the KEKs, API keys, DB creds, and
.env material this skill consumes → secrets-management (it provisions and rotates the secrets; this skill encrypts data with them)
- The legal side — what counts as PII/PHI, lawful basis, right-to-erasure, data residency → map-privacy-data-gdpr (this skill is the technical control, e.g. crypto-shredding, that satisfies those duties)
- TLS termination/cert issuance at the edge proxy, ACME, SNI routing → configure-dns-tls and configure-reverse-proxy-lb (this skill covers the cipher-suite/mTLS policy, not cert plumbing)
- Browser security response headers (HSTS, CSP) → configure-security-headers-csp (HSTS enforces HTTPS; this skill is the transport crypto itself)
- Login sessions, JWT signing/verification, token rotation → auth-jwt-session (signatures/JWE are adjacent but that owns session lifecycle)
- Identifying the threats/attacker model that justify these controls → threat-model-stride
- A broad security pass over a diff → security-review (this skill is the deep crypto specialist it defers to)
Steps
-
Classify data first, then pick the protection tier — encryption is not the answer to everything. Three distinct goals need three different tools:
| Goal | Use | NEVER |
|---|
| Verify a credential later (passwords) | slow password hash (argon2id) — one-way | encrypt; never decrypt a password |
| Protect data you must read back (PII, PHI, PAN, tokens) | AEAD encryption + KMS envelope | reversible "encoding", base64, ROT |
| Integrity/origin without secrecy | HMAC-SHA-256 / signature | "encrypt to authenticate" |
| Index/search without revealing value | HMAC-based blind index or deterministic enc | plaintext index column |
Encrypting a password is a bug, not a feature: anything reversible means an attacker (or insider) with the key gets every plaintext password.
-
Use AEAD ciphers only. Banned modes are non-negotiable. Authenticated Encryption with Associated Data gives confidentiality and tamper-detection in one primitive:
| Use this | Why |
|---|
| AES-256-GCM | hardware-accelerated (AES-NI), NIST-approved, ubiquitous KMS support |
| ChaCha20-Poly1305 | faster on CPUs without AES-NI (mobile/ARM), constant-time by design |
| AES-256-GCM-SIV / XChaCha20-Poly1305 | nonce-misuse-resistant / 192-bit nonce — prefer when you can't guarantee unique 96-bit nonces |
| Banned | Why it's broken |
|---|
| ECB | identical plaintext blocks → identical ciphertext (the "ECB penguin"); leaks structure |
| CBC/CTR without a MAC | unauthenticated → padding-oracle (CBC) & bit-flipping attacks; ciphertext is malleable |
| Raw RSA / RSA-PKCS#1v1.5 enc | use RSA-OAEP, or better ECIES/hybrid; never "RSA the whole payload" |
| DES/3DES/RC4/MD5/SHA-1 | broken/deprecated |
Don't hand-roll "AES + separate HMAC" (encrypt-then-MAC) unless you must — get the construction order wrong and you reintroduce the oracle. Use a vetted library: ( / ), GCM or , / (not the low-level API), GCM or Google , / RustCrypto crates, + . — they pick safe modes and manage nonces for you.
Common Errors
- Encrypting passwords instead of hashing. Reversible = one key compromise dumps every password. Fix: argon2id/bcrypt, one-way (step 7).
- Plain/fast hash for passwords (
SHA256(password), unsalted MD5). GPUs crack billions/sec; rainbow tables for unsalted. Fix: memory-hard KDF with per-password salt.
- ECB mode / unauthenticated CBC. ECB leaks structure; CBC-without-MAC → padding oracle, malleable ciphertext. Fix: AEAD (AES-GCM/ChaCha20-Poly1305) only.
- Nonce/IV reuse under one key (GCM). Catastrophic — leaks plaintext XOR and the auth key (forgeries). Fix: unique nonce per message; XChaCha20/GCM-SIV if you can't guarantee it (step 3).
- Hardcoded / static IV (
iv = new byte[12] all zeros). Same as reuse. Fix: fresh CSPRNG nonce per encryption, stored with ciphertext.
- Encrypting bulk data directly with the KMS/KEK. Throughput and cost explode; no clean rotation. Fix: envelope — KEK wraps per-record DEK (step 5).
- No AAD binding. Valid ciphertext copy-pasted between rows/tenants decrypts fine. Fix: pass row/tenant/version as AAD (step 4).
- No key version on ciphertext. Rotation becomes a flag-day re-encrypt-everything. Fix: store
key_version, dispatch decryption on it (step 9).
- Plaintext DEK left in memory / logged. Heap dump or log leak = game over. Fix: zero after use; never log keys/plaintext/tags.
Math.random() / rand() for keys, nonces, or salts. Predictable → forgeable. Fix: CSPRNG only.
- Disabling TLS verification (
verify=False, InsecureSkipVerify, rejectUnauthorized:false). Silent MITM. Fix: validate chain + SAN; only bypass in isolated tests.
- Weak TLS (TLS 1.0/1.1, CBC suites, static RSA, RC4/3DES). Fix: TLS 1.2+/1.3, AEAD+ECDHE suites; verify with testssl.sh.
== on MACs/tags/tokens. Timing side-channel. Fix: constant-time comparison.
- Roll-your-own crypto /
Cipher low-level API. Easy to misorder encrypt-then-MAC, mishandle padding. Fix: libsodium / Tink / AWS Encryption SDK.
- Deterministic encryption on high-cardinality PII you didn't mean to. Leaks equality patterns. Fix: randomized by default; deterministic/blind-index only where a query needs it (step 6).
Verify
- No banned modes/algorithms: grep the diff for
ECB, AES/CBC without an accompanying MAC, DES, RC4, MD5/SHA1 on secrets, raw RSA encrypt — zero hits. All symmetric encryption is AES-GCM / ChaCha20-Poly1305 (AEAD).
- Passwords are hashed, not encrypted: grep finds argon2id/bcrypt/scrypt on the password path and no encrypt/decrypt of passwords; salts are per-password (encoded in the hash); cost params meet the step-7 baseline.
- Nonce uniqueness: confirm every encryption draws a fresh CSPRNG nonce (or a guaranteed-unique counter); no static/zero IV; nonce stored with ciphertext. For high volume, DEK rotation or a nonce-misuse-resistant mode is in place.
- Envelope encryption holds: bulk data is encrypted with a DEK, the DEK is wrapped by a KMS-held KEK that never leaves KMS, plaintext DEK is zeroed after use, and a
key_version is stored per record.
- AAD binds context: moving a valid ciphertext from one row/tenant to another fails decryption (AAD mismatch).
- Rotation works without re-encrypting everything: rotating the KEK re-wraps DEKs only; old
key_version ciphertext still decrypts; a DEK-destroy crypto-shreds its records (they become permanently undecryptable).
- TLS posture:
testssl.sh <host> / SSL Labs returns A/A+ — TLS 1.2+ only, AEAD+forward-secret suites, no CBC/RC4/3DES; mTLS validates the full chain + SAN; no verify=False/InsecureSkipVerify in non-test code.
- Randomness + timing: all keys/nonces/salts come from a CSPRNG (no
Math.random/rand); MAC/tag/token comparisons are constant-time.
- Tamper detection: flipping one ciphertext byte makes decryption fail (auth tag rejects it) rather than returning garbage plaintext.
Done = sensitive data is encrypted with AEAD under unique nonces, bulk data uses KMS envelope encryption with versioned, rotatable keys and context-binding AAD, passwords are hashed with argon2id/bcrypt (never encrypted), PII fields are randomized-encrypted (deterministic/blind-index only where a query demands it), transport is TLS 1.2+/1.3 with modern suites and mTLS where needed, and all keys/nonces/salts come from a CSPRNG with constant-time tag checks — all proven by checks 1–9, with security-review run over the crypto diff.