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.

  • Security
  • $cryptography-usage
  • 174 lines
  • 2 files
  • ~2,050 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- cryptography-usage

Installs to ~/.agents/skills/cryptography-usage/ for every project on this machine.

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

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.

~/.agents/skills/cryptography-usage/SKILL.md

--- name: cryptography-usage description: Chooses and applies cryptographic primitives and libraries correctly - AEAD encryption with AES-256-GCM or XChaCha20-Poly1305, HMAC, Ed25519… license: MIT ---

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

PurposeUseNot
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 APIlibsodium secretbox / crypto_aead_xchacha20poly1305, Tink Aead, Python cryptography AESGCM or FernetRaw block cipher APIs
Encrypt to a recipient’s public keylibsodium sealed boxes, HPKE (RFC 9180), age for filesRaw RSA without OAEP; RSA PKCS#1 v1.5 encryption
Integrity + authenticity, shared keyHMAC-SHA256sha256(key + msg) (length extension), CRC
Digital signaturesEd25519; ECDSA P-256 when interop requires it; RSA-PSS 3072+RSA PKCS#1 v1.5 for new designs, DSA
Key agreementX25519 (or ECDH P-256), then HKDFUsing the shared secret directly as a key
Derive keys from a keyHKDF-SHA256 with a distinct info per purposeReusing one key for encryption and MAC
Derive keys from a passwordArgon2id, scrypt (see $password-storage)PBKDF2 with low iterations, plain hashes
Hashing (non-password)SHA-256, SHA-512, SHA-3, BLAKE2b/BLAKE3MD5, SHA-1 for security
Random tokens, keys, noncesOS CSPRNG: secrets, crypto.randomBytes, crypto.getRandomValues, Go crypto/rand, Java SecureRandomMath.random, random, math/rand, time-seeded RNGs
Data at rest in the cloudEnvelope encryption: KMS key wraps per-object data keysOne static key in an env var for everything
TransportTLS 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 out AES/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.yamlgenerated from this page

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.

esc