Cryptography Usage skill for Codex
Right primitive, right library, nothing homemade. Codex picks primitives by purpose, AEAD for confidentiality, HMAC for shared-key integrity, Ed25519 for signatures, a CSPRNG for tokens, and implements them through misuse-resistant library APIs with associated data and per-purpose keys. It reviews existing code for ECB, unauthenticated CBC, nonce reuse, weak hashes and hard-coded keys, moves keys into KMS envelope encryption, and plans for post-quantum ML-KEM.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- cryptography-usage Installs to ~/.agents/skills/cryptography-usage/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install cryptography-usage The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project cryptography-usage Installs to .agents/skills/cryptography-usage/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/cryptography-usage.zipunzip -o cryptography-usage.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) cryptography-usage PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $cryptography-usage, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Chooses and applies cryptographic primitives and libraries correctly - AEAD encryption with AES-256-GCM or XChaCha20-Poly1305, HMAC, Ed25519 signatures, X25519 and HKDF, CSPRNG randomness, envelope encryption with AWS KMS or Cloud KMS, libsodium and Tink - and reviews code for ECB, unauthenticated CBC, nonce reuse, MD5 or SHA-1, Math.random, hard-coded keys and homemade schemes. Covers post-quantum ML-KEM planning. Use when encrypting data at rest, signing or verifying messages, generating tokens, or reviewing crypto code.
Use it when
- Encrypting fields, files or blobs at rest
- Signing or verifying messages and payloads
- Generating tokens, keys or nonces
- Reviewing existing crypto code or planning post-quantum migration
Use something else when
- You are hashing user passwords: use password-storage
- You are configuring TLS on servers: use tls-hardening
- You are solving cryptography puzzles in a CTF: use ctf-crypto-challenges
How to check the result
Round-trip tests pass, flipping any byte of ciphertext, tag, nonce or AAD makes decryption fail, and repeated encryptions of the same plaintext produce different ciphertexts.
The mistake it prevents
Using encryption without authentication or reusing a GCM nonce with the same key, which lets attackers modify or recover data.
Done well means: the code uses a high-level, misuse-resistant API from a maintained library, every primitive matches its job (confidentiality, integrity, authenticity), keys live in a KMS or secrets manager with a rotation story, and nothing in the design was invented locally. Most crypto bugs are wrong choices and wrong plumbing, not broken algorithms.
Inspect first
rg -n -i "createCipher(iv)?|Cipher\.getInstance|AES\.new|aes-\d+-(ecb|cbc|ctr)|/ECB/|/CBC/|MODE_ECB|MODE_CBC" -g '!node_modules'
rg -n -i "\bmd5\b|sha1|SHA-1|Math\.random|random\.random|rand\.Intn|new Random\(|mt_rand|uniqid" -g '!node_modules' -g '!*.lock'
rg -n -i "(secret|key|iv|nonce)\s*[:=]\s*['\"][A-Za-z0-9+/=]{8,}['\"]" -g '!node_modules' -g '!*test*'
rg -n -i "xor|rot13|base64.*(encrypt|secret)|custom.?crypt|obfuscat" -g '!node_modules' | head
Classify each hit by purpose before judging it. MD5 for a cache key or ETag is fine; MD5 for integrity against an attacker is not.
Choose by purpose
| Purpose | Use | Not |
|---|---|---|
| Encrypt data (symmetric) | AEAD: AES-256-GCM, ChaCha20-Poly1305, XChaCha20-Poly1305 (random nonces at scale) | ECB, CBC/CTR without MAC, custom XOR |
| Encrypt with a simple API | libsodium secretbox / crypto_aead_xchacha20poly1305, Tink Aead, Python cryptography AESGCM or Fernet | Raw block cipher APIs |
| Encrypt to a recipient’s public key | libsodium sealed boxes, HPKE (RFC 9180), age for files | Raw RSA without OAEP; RSA PKCS#1 v1.5 encryption |
| Integrity + authenticity, shared key | HMAC-SHA256 | sha256(key + msg) (length extension), CRC |
| Digital signatures | Ed25519; ECDSA P-256 when interop requires it; RSA-PSS 3072+ | RSA PKCS#1 v1.5 for new designs, DSA |
| Key agreement | X25519 (or ECDH P-256), then HKDF | Using the shared secret directly as a key |
| Derive keys from a key | HKDF-SHA256 with a distinct info per purpose | Reusing one key for encryption and MAC |
| Derive keys from a password | Argon2id, scrypt (see $password-storage) | PBKDF2 with low iterations, plain hashes |
| Hashing (non-password) | SHA-256, SHA-512, SHA-3, BLAKE2b/BLAKE3 | MD5, SHA-1 for security |
| Random tokens, keys, nonces | OS CSPRNG: secrets, crypto.randomBytes, crypto.getRandomValues, Go crypto/rand, Java SecureRandom | Math.random, random, math/rand, time-seeded RNGs |
| Data at rest in the cloud | Envelope encryption: KMS key wraps per-object data keys | One static key in an env var for everything |
| Transport | TLS 1.3 via the platform (see $tls-hardening) | Hand-rolled encryption over TCP |
Reference implementations
Python (cryptography package):
import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
def encrypt(key: bytes, plaintext: bytes, aad: bytes) -> bytes:
nonce = os.urandom(12) # 96-bit, never reused with this key
return nonce + AESGCM(key).encrypt(nonce, plaintext, aad)
def decrypt(key: bytes, blob: bytes, aad: bytes) -> bytes:
return AESGCM(key).decrypt(blob[:12], blob[12:], aad) # raises InvalidTag if tampered
Use associated data (aad) to bind ciphertext to its context (record ID, tenant ID,
column name) so it cannot be swapped between rows.
Node:
import { randomBytes, createCipheriv, createDecipheriv } from "node:crypto";
export function encrypt(key, plaintext, aad) {
const iv = randomBytes(12);
const c = createCipheriv("aes-256-gcm", key, iv);
c.setAAD(aad);
const ct = Buffer.concat([c.update(plaintext), c.final()]);
return Buffer.concat([iv, c.getAuthTag(), ct]); // iv | 16-byte tag | ciphertext
}
export function decrypt(key, blob, aad) {
const d = createDecipheriv("aes-256-gcm", key, blob.subarray(0, 12));
d.setAAD(aad);
d.setAuthTag(blob.subarray(12, 28));
return Buffer.concat([d.update(blob.subarray(28)), d.final()]); // final() throws on bad tag
}
Go:
block, _ := aes.NewCipher(key) // 32-byte key -> AES-256
aead, _ := cipher.NewGCM(block)
nonce := make([]byte, aead.NonceSize())
if _, err := rand.Read(nonce); err != nil { return nil, err }
ciphertext := aead.Seal(nonce, nonce, plaintext, aad)
Signatures (Python): Ed25519PrivateKey.generate(), .sign(msg), and
public_key.verify(sig, msg) which raises on failure. Always handle the failure path as
a hard reject.
Envelope encryption
encrypt: data_key = KMS.GenerateDataKey(key_id) -> plaintext key + wrapped key
ciphertext = AEAD(data_key.plaintext, data, aad)
store (wrapped key, nonce, ciphertext, key_id, version); discard plaintext key
decrypt: data_key = KMS.Decrypt(wrapped key) -> AEAD decrypt
AWS Encryption SDK, Google Tink with Cloud KMS, and Azure Key Vault SDKs implement this pattern; prefer them to hand assembly. Rotation re-wraps data keys without re-encrypting all data. KMS calls need network and IAM permissions; in a sandbox, write the code and unit-test with a local fake key provider.
Nonce and key limits
- AES-GCM with random 96-bit nonces: stay under about 2^32 encryptions per key, then rotate. Reusing a nonce with the same key reveals plaintext XOR and allows forgeries.
- XChaCha20-Poly1305 has 192-bit nonces; random nonces are safe at practically any volume. Prefer it for high-volume systems with random nonces.
- Never derive nonces from timestamps or counters that reset on restart or are shared across instances.
- One key, one purpose. Derive separate subkeys with HKDF for encryption, MAC and per-tenant use.
Hard rules
- Never write your own cipher, mode, padding scheme, MAC construction or protocol.
- Never use encryption without authentication; check the tag before using any plaintext.
- Never compare MACs or signatures with
==; use constant-time comparison. - Never hard-code keys or IVs, or commit them; load from KMS or a secrets manager.
- Never log keys, plaintext secrets or decrypted payloads.
- Treat “encrypted” fields used for lookup (deterministic encryption) as a deliberate, documented trade-off; use a keyed hash (HMAC) blind index instead where possible.
- JWT: pin algorithms on verify, reject
none; details in$jwt-auth.
Post-quantum planning
ML-KEM (FIPS 203) and ML-DSA (FIPS 204) were standardised in August 2024. Practical steps now:
- Transport: modern browsers, Cloudflare and recent OpenSSL (3.5+) negotiate hybrid X25519MLKEM768 in TLS 1.3; keeping TLS stacks current is most of the work.
- Data with long confidentiality lifetimes (harvest now, decrypt later): inventory where asymmetric key exchange protects it and plan hybrid KEMs.
- Symmetric crypto: AES-256 and SHA-256/384 remain appropriate.
- Build crypto agility: algorithm identifiers and key versions stored with ciphertexts and signatures.
Verification
- Round-trip tests: encrypt then decrypt returns the plaintext; flipping one byte of ciphertext, tag, nonce or AAD makes decrypt fail.
- Nonce test: encrypting the same plaintext twice yields different ciphertexts.
- Known-answer tests from RFC or NIST vectors when implementing interop formats.
- Re-run the inspection searches; every remaining MD5/SHA-1/random hit has a comment stating its non-security purpose.
Output template
## Crypto review: <scope>
| # | Location | Purpose | Current | Problem | Recommendation |
|---|---|---|---|---|---|
| 1 | billing/crypto.py:14 | Encrypt card tokens | AES-CBC, static IV, no MAC | Malleable, IV reuse | AESGCM with random nonce + AAD=record id, key from KMS |
Key management: <where keys live, rotation, access>
Out of scope / needs specialist review: <protocol design, HSM config>
Gotchas
- Base64 and hex are encodings, not encryption.
crypto.createCipher(no IV) was removed from Node; old snippets online still use it.- Java
Cipher.getInstance("AES")defaults to ECB with PKCS5 padding; always spell outAES/GCM/NoPadding. - Truncating tags or using GCM with short tags weakens authentication; keep 16 bytes.
- Anything novel (custom protocol, threshold schemes, ZK) needs review by a professional cryptographer; say so in the report.
What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Cryptography Usage"
short_description: "Right primitive, right library, nothing homemade"
default_prompt: "$cryptography-usage Encrypt these PII columns at rest in our Node app the right way" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.