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.
- name
cryptography-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
- You are storing user passwords — use auth-sessions instead
- The question is where to keep and rotate API keys — use secrets-management instead
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.
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
| Job | Use | Not |
|---|---|---|
| Store passwords | argon2id (or scrypt/bcrypt) — see auth-sessions | SHA-256, MD5, encryption |
| Encrypt data with a symmetric key | AEAD: AES-256-GCM or XChaCha20-Poly1305 | AES-ECB, AES-CBC without MAC, homemade encrypt+MAC |
| Encrypt to a recipient’s public key | libsodium sealed box / crypto_box (X25519 + XSalsa20-Poly1305), HPKE (RFC 9180), age for files | Raw RSA, RSA PKCS#1 v1.5 encryption |
| Sign / verify | Ed25519; ECDSA P-256 where required; RSA-PSS ≥ 3072 for legacy interop | RSA PKCS#1 v1.5 for new designs, DSA |
| Message integrity with a shared key | HMAC-SHA256 (compare in constant time) | sha256(key + message) (length extension) |
| Derive keys from a master key | HKDF-SHA256 with a distinct info per purpose | Reusing one key for many purposes |
| Derive a key from a password | argon2id with a random salt | Using the password bytes as the key |
| Random tokens, IDs, nonces, keys | OS CSPRNG: secrets, crypto.randomBytes, crypto.getRandomValues, crypto/rand | Math.random, random, time-seeded PRNGs |
| Hash for integrity / dedup | SHA-256, SHA-512/256, BLAKE2b/BLAKE3 | MD5, SHA-1 (collisions) |
| Transport | TLS 1.3 (1.2 minimum) via the platform; certificate verification on | Custom 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-wrappersfor sodium APIs. - Go: standard library
crypto/*andgolang.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:
- A key encryption key (KEK) lives in a KMS/HSM (AWS KMS, GCP Cloud KMS, Azure Key Vault, Vault Transit) and never leaves it.
- For each record, file, or tenant, generate a data encryption key (DEK) locally
(KMS
GenerateDataKeyreturns plaintext + wrapped copies), encrypt the data with the DEK using AEAD, store the wrapped DEK next to the ciphertext, and discard the plaintext DEK. - 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
Decryptin 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.createCipherderived keys with MD5 and no IV; it is removed in modern Node.createCipherivwith GCM requires you to callgetAuthTag()afterfinal()andsetAuthTag()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.