Cryptography Basics skill for Claude Code

Right primitive, vetted library, no novel designs. Prevents the classic crypto mistakes before they ship. Claude maps each job to one vetted primitive and library, encrypts with AES-GCM or XChaCha20-Poly1305 using fresh nonces and associated data, versions ciphertext for rotation, wraps data keys with a KMS, and compares MACs in constant time. Reviews flag ECB, static IVs, MD5, hardcoded keys and disabled TLS checks.

  • namecryptography-basics
  • size179 lines · ≈2,200 tokens
  • files1
  • licenseMIT
  • updated

What Claude sees before loading it

This description is the only part of the skill that sits in context all the time. Claude reads it to decide when to load the full playbook below.

description: Applies cryptography correctly with vetted high-level libraries — choosing primitives (AEAD with AES-GCM or XChaCha20-Poly1305, Ed25519, X25519, HMAC, HKDF, argon2id), nonce and key handling, envelope encryption with a KMS, versioned ciphertext formats, key rotation, and a review list of things never to build yourself. Use when encrypting data at rest or fields in a database, signing or verifying messages, generating tokens, deriving keys, choosing a crypto library, or reviewing code that uses crypto, hashlib, WebCrypto, or libsodium.

Try asking

  • Encrypt the SSN column in our Postgres table properly
  • Review this module that encrypts API tokens with AES — is it safe?
  • How should we sign webhook payloads we send to customers?

Use it when

  • Encrypting fields, files, or backups in an application
  • Signing or verifying messages, tokens, or webhooks
  • Choosing between crypto libraries or algorithms
  • Reviewing code that touches hashing, ciphers, or random number generation

Skip it when

How to check the result

Tests show a flipped ciphertext byte or wrong associated data fails decryption, identical plaintexts encrypt differently, and data encrypted under an old key version still decrypts after rotation.

The common mistake it prevents

Reusing a fixed or counter-reset nonce with AES-GCM, which leaks plaintext relationships and lets an attacker forge authenticated messages.

~/.claude/skills/cryptography-basics/SKILL.md

--- name: cryptography-basics description: Applies cryptography correctly with vetted high-level libraries — choosing primitives (AEAD with AES-GCM or XChaCha20-Poly1305, Ed25519,… license: MIT ---

Done well means: you used a high-level, misuse-resistant API from a maintained library; every primitive matches its job (confidentiality and integrity, never one without the other); keys come from a CSPRNG or a KMS, are separated by purpose, and can rotate; nonces never repeat under a key; and nothing in the design is novel. If a requirement seems to need a custom construction, stop and escalate to someone who does this professionally.

Pick the primitive

JobUseNot
Store passwordsargon2id (or scrypt/bcrypt) — see auth-sessionsSHA-256, MD5, encryption
Encrypt data with a symmetric keyAEAD: AES-256-GCM or XChaCha20-Poly1305AES-ECB, AES-CBC without MAC, homemade encrypt+MAC
Encrypt to a recipient’s public keylibsodium sealed box / crypto_box (X25519 + XSalsa20-Poly1305), HPKE (RFC 9180), age for filesRaw RSA, RSA PKCS#1 v1.5 encryption
Sign / verifyEd25519; ECDSA P-256 where required; RSA-PSS ≥ 3072 for legacy interopRSA PKCS#1 v1.5 for new designs, DSA
Message integrity with a shared keyHMAC-SHA256 (compare in constant time)sha256(key + message) (length extension)
Derive keys from a master keyHKDF-SHA256 with a distinct info per purposeReusing one key for many purposes
Derive a key from a passwordargon2id with a random saltUsing the password bytes as the key
Random tokens, IDs, nonces, keysOS CSPRNG: secrets, crypto.randomBytes, crypto.getRandomValues, crypto/randMath.random, random, time-seeded PRNGs
Hash for integrity / dedupSHA-256, SHA-512/256, BLAKE2b/BLAKE3MD5, SHA-1 (collisions)
TransportTLS 1.3 (1.2 minimum) via the platform; certificate verification onCustom handshakes, disabled verification

Library defaults:

  • Python: cryptography (hazmat AEADs are fine when used as below) or PyNaCl.
  • JavaScript: WebCrypto (crypto.subtle) in browsers/Node/Deno/Workers; libsodium-wrappers for sodium APIs.
  • Go: standard library crypto/* and golang.org/x/crypto.
  • Java: Tink. .NET: System.Security.Cryptography (AesGcm, ChaCha20Poly1305).
  • Files and backups: age. Application-level envelope encryption: cloud KMS SDKs or Tink.

AEAD done right

Python (pip install cryptography):

import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

key = AESGCM.generate_key(bit_length=256)      # store in KMS/secret store, not code
aead = AESGCM(key)

def encrypt(plaintext: bytes, aad: bytes) -> bytes:
    nonce = os.urandom(12)                      # 96-bit random nonce, new every call
    return b"\x01" + nonce + aead.encrypt(nonce, plaintext, aad)   # version || nonce || ct+tag

def decrypt(blob: bytes, aad: bytes) -> bytes:
    if blob[:1] != b"\x01":
        raise ValueError("unknown ciphertext version")
    nonce, ct = blob[1:13], blob[13:]
    return aead.decrypt(nonce, ct, aad)         # raises InvalidTag if tampered

WebCrypto (browser, Node 20+, Workers):

const key = await crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, false, ["encrypt", "decrypt"]);
const iv = crypto.getRandomValues(new Uint8Array(12));
const ct = await crypto.subtle.encrypt({ name: "AES-GCM", iv, additionalData: aad }, key, plaintext);
// store iv alongside ct; decrypt with the same iv and additionalData

PyNaCl secret box (nonce generated and prepended for you):

import nacl.secret, nacl.utils
box = nacl.secret.SecretBox(nacl.utils.random(nacl.secret.SecretBox.KEY_SIZE))
blob = box.encrypt(b"data")          # nonce || ciphertext
plain = box.decrypt(blob)

Rules:

  • Never reuse a nonce with the same key. With AES-GCM, a repeat leaks the XOR of plaintexts and lets attackers forge messages. Random 96-bit nonces are safe up to about 2^32 messages per key; rotate keys before that, or use XChaCha20-Poly1305 (192-bit nonces, random is safe at any practical volume).
  • Use associated data (AAD) to bind ciphertext to its context: record ID, column name, tenant ID. This stops an attacker with DB write access from swapping encrypted values between rows.
  • Version the format (first byte or a key ID) so you can rotate algorithms and keys.
  • Decrypt failure means tampering or wrong key: fail closed, log, never return partial output.
  • AES-GCM authenticates but does not hide length; pad if message length is sensitive.

Envelope encryption and key management

For data at rest in your application:

  1. A key encryption key (KEK) lives in a KMS/HSM (AWS KMS, GCP Cloud KMS, Azure Key Vault, Vault Transit) and never leaves it.
  2. For each record, file, or tenant, generate a data encryption key (DEK) locally (KMS GenerateDataKey returns plaintext + wrapped copies), encrypt the data with the DEK using AEAD, store the wrapped DEK next to the ciphertext, and discard the plaintext DEK.
  3. To decrypt, unwrap the DEK through the KMS (audited, access-controlled), then decrypt locally. Cache unwrapped DEKs briefly if call volume demands it.

Key management rules:

  • Separate keys by purpose and environment (encryption vs. signing vs. HMAC; prod vs. staging).
  • Rotation: KMS key rotation rewraps nothing by itself — old ciphertexts keep their key version. Plan re-encryption jobs when policy or compromise requires.
  • Per-tenant DEKs enable crypto-shredding: delete a tenant’s key to make their data unreadable (useful for erasure requests, but backups still need a retention plan).
  • Restrict who can call Decrypt in IAM; alert on unusual decrypt volume.

Signing

from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
sk = Ed25519PrivateKey.generate()
sig = sk.sign(message)
sk.public_key().verify(sig, message)    # raises InvalidSignature
  • Sign a canonical byte representation. Signing JSON.stringify(obj) and verifying a re-serialized object fails or, worse, verifies something different. Sign the exact bytes you transmit.
  • Include a purpose/domain string and an expiry in signed payloads to prevent cross- protocol reuse and replay.

HMAC and token comparison

import hmac, hashlib
tag = hmac.new(key, msg, hashlib.sha256).digest()
hmac.compare_digest(tag, received)       # constant time

Node: crypto.timingSafeEqual(a, b) (buffers must be equal length — check length first).

Never build these yourself

  • Your own cipher, mode, padding scheme, or “encryption” via XOR/base64.
  • Encrypt-and-MAC combinations when an AEAD exists; MAC-then-encrypt.
  • A protocol (handshake, key exchange, secure channel). Use TLS, Noise via a library, or libsodium’s high-level boxes.
  • Password-based encryption without a slow KDF.
  • Deterministic encryption for searchability without understanding what it leaks; use a separate HMAC “blind index” column for equality lookups instead, keyed separately.
  • JWT/JWE libraries you write; use maintained ones (see jwt-handling).

Review checklist

grep -rniE "md5|sha1|des|rc4|ecb|pkcs1v15|Math\.random|random\.random|createCipher\(" --include=*.{py,js,ts,go,java,cs,rb} .
grep -rnE "(nonce|iv)\s*=\s*(b?['\"]|bytes\(|new Uint8Array\(\d+\)\s*;)" --include=*.{py,js,ts} .   # constant IVs
grep -rnE "verify\s*=\s*False|rejectUnauthorized:\s*false|InsecureSkipVerify|CERT_NONE" .
grep -rnE "(SECRET|KEY|PRIVATE)[A-Z_]*\s*=\s*['\"][A-Za-z0-9+/=]{16,}" .        # hardcoded keys
- [ ] Every encryption is AEAD; IV/nonce random per message or counter-managed; AAD used
- [ ] Keys from KMS/secret store; none in code, images, or logs; purpose-separated
- [ ] Ciphertext format versioned; rotation path documented
- [ ] MAC/signature comparisons constant-time; signatures over exact bytes
- [ ] Randomness from CSPRNG everywhere security-relevant
- [ ] TLS verification enabled in all clients

Gotchas

  • Node’s deprecated crypto.createCipher derived keys with MD5 and no IV; it is removed in modern Node. createCipheriv with GCM requires you to call getAuthTag() after final() and setAuthTag() before decrypting.
  • Base64 or hex encoding is not encryption; hashing is not encryption.
  • Post-quantum: TLS libraries are adopting hybrid key exchange (X25519 + ML-KEM-768). Prefer upgrading TLS stacks over custom PQ code; plan inventory of long-lived signatures and encrypted archives (“harvest now, decrypt later”).
  • Compliance (FIPS 140-3) may restrict you to AES-GCM, P-256, RSA, and approved modules; check before choosing ChaCha20 or Ed25519.

Verification

  • Round-trip tests, plus: flipped ciphertext byte → decrypt raises; wrong AAD → raises; two encryptions of the same plaintext produce different ciphertexts.
  • Rotation test: data encrypted with key v1 decrypts after v2 becomes primary.
  • Review greps produce no unexplained hits.

About this skill

Written and maintained by the Claude Skills editors to the same editorial standard as every skill in the directory: a selective trigger, a real workflow, known pitfalls and a verification step. First published , last updated . MIT licensed.

esc