cryptography
Use this skill when implementing encryption, hashing, TLS configuration, JWT tokens, or key management. Triggers on encryption, hashing, bcrypt, AES, RSA, TLS certificates, JWT signing, HMAC, key rotation, digital signatures, and any task requiring cryptographic implementation or protocol selection.
engineering cryptographyencryptionhashingtlsjwtkey-managementWhat is cryptography?
Use this skill when implementing encryption, hashing, TLS configuration, JWT tokens, or key management. Triggers on encryption, hashing, bcrypt, AES, RSA, TLS certificates, JWT signing, HMAC, key rotation, digital signatures, and any task requiring cryptographic implementation or protocol selection.
cryptography
cryptography is a production-ready AI agent skill for claude-code, gemini-cli, openai-codex. Implementing encryption, hashing, TLS configuration, JWT tokens, or key management.
Quick Facts
| Field | Value |
|---|---|
| Category | engineering |
| Version | 0.1.0 |
| Platforms | claude-code, gemini-cli, openai-codex |
| License | MIT |
How to Install
- Make sure you have Node.js installed on your machine.
- Run the following command in your terminal:
npx skills add AbsolutelySkilled/AbsolutelySkilled --skill cryptography- The cryptography skill is now available in your AI coding agent (Claude Code, Gemini CLI, OpenAI Codex, etc.).
Overview
A practical cryptography guide for engineers who need to implement encryption, hashing, signing, and key management correctly. This skill covers the seven most common cryptographic tasks with production-ready TypeScript/Node.js code, opinionated algorithm choices, and a clear anti-patterns table. Designed for engineers who understand the basics but need confident, safe defaults.
Tags
cryptography encryption hashing tls jwt key-management
Platforms
- claude-code
- gemini-cli
- openai-codex
Related Skills
Pair cryptography with these complementary skills:
Frequently Asked Questions
What is cryptography?
Use this skill when implementing encryption, hashing, TLS configuration, JWT tokens, or key management. Triggers on encryption, hashing, bcrypt, AES, RSA, TLS certificates, JWT signing, HMAC, key rotation, digital signatures, and any task requiring cryptographic implementation or protocol selection.
How do I install cryptography?
Run npx skills add AbsolutelySkilled/AbsolutelySkilled --skill cryptography in your terminal. The skill will be immediately available in your AI coding agent.
What AI agents support cryptography?
This skill works with claude-code, gemini-cli, openai-codex. Install it once and use it across any supported AI coding agent.
Maintainers
Generated from AbsolutelySkilled
SKILL.md
Cryptography
A practical cryptography guide for engineers who need to implement encryption, hashing, signing, and key management correctly. This skill covers the seven most common cryptographic tasks with production-ready TypeScript/Node.js code, opinionated algorithm choices, and a clear anti-patterns table. Designed for engineers who understand the basics but need confident, safe defaults.
When to use this skill
Trigger this skill when the user:
- Hashes or stores passwords (bcrypt, argon2, any hashing question)
- Encrypts or decrypts data at rest or in transit (AES, RSA, envelope encryption)
- Implements JWT signing, verification, or refresh token flows
- Configures TLS certificates on servers, proxies, or mutual TLS
- Implements HMAC signatures for webhooks or API request signing
- Designs or implements a key rotation strategy
- Generates cryptographically secure random tokens, IDs, or salts
- Chooses between symmetric vs asymmetric, hashing vs encryption, or any algorithm
Do NOT trigger this skill for:
- General security posture or authentication/authorization flows - use the backend-engineering security reference instead
- Building a custom cryptographic algorithm, cipher, or protocol - that is always wrong; redirect the user immediately
Key principles
Never invent your own crypto primitives - Do not implement block ciphers, hash functions, key derivation, or signature schemes. The gap between "looks correct" and "is correct" is where attackers live. Use audited libraries (Node.js
crypto,bcrypt,jose,argon2) that encode decades of research.Use the highest-level API available - If a library has a
hashPassword()function, use it over constructing the primitive manually. High-level APIs embed safe defaults. Low-level APIs require you to know every parameter that matters.Rotate keys regularly and plan for it upfront - Key rotation is not an afterthought. Envelope encryption makes rotation cheap: re-encrypt only the data key, not the data. Design systems with rotation in mind before writing the first line of code.
Hash passwords with bcrypt or argon2 - never MD5 or SHA* - MD5 and SHA-family hashes are fast by design. Fast hashes mean fast brute force. Password hashing needs to be slow. Argon2id is the current standard. bcrypt with cost 12+ is the safe fallback.
TLS 1.3 is the minimum - Disable TLS 1.0 and 1.1 everywhere. They have known attacks (BEAST, POODLE). TLS 1.2 is acceptable only as a fallback for legacy clients. TLS 1.3 removes the broken cipher suites entirely and has mandatory forward secrecy.
Core concepts
Symmetric vs asymmetric encryption - Symmetric uses one key for both encrypt and decrypt (AES). Fast, suitable for bulk data. The hard problem is securely sharing the key. Asymmetric uses a key pair: public key encrypts, private key decrypts (RSA, ECDH). Slower, but solves the key distribution problem. In practice, use asymmetric to exchange a symmetric key, then use symmetric for the actual data (this is what TLS does).
Hashing vs encryption - Hashing is one-way: you can verify but not reverse. Encryption is two-way: you can recover the original with the key. Use hashing for passwords (you verify, never recover). Use encryption for data you need to read back (PII, configuration secrets).
Digital signatures - Asymmetric operation where the private key signs and the public key verifies. Proves authenticity (this came from the private key holder) and integrity (data was not modified). Used in JWTs (RS256, ES256), code signing, and document verification.
Key derivation functions (KDF) - Transform a low-entropy input (password) into a high-entropy key using a slow, memory-hard algorithm. PBKDF2, bcrypt, scrypt, and Argon2 are KDFs. Do not use raw SHA-256 to derive a key from a password.
Envelope encryption - The pattern for production key management. Encrypt data with a data encryption key (DEK). Encrypt the DEK with a key encryption key (KEK) stored in a KMS. Store the encrypted DEK alongside the ciphertext. To rotate: ask KMS to re-wrap the DEK with the new KEK. The data itself never needs re-encryption.
Common tasks
Hash passwords with bcrypt / argon2
Use argon2 (preferred) or bcrypt (widely supported). Never use crypto.createHash
for passwords.
import argon2 from 'argon2';
// Hash a password
export async function hashPassword(password: string): Promise<string> {
return argon2.hash(password, {
type: argon2.argon2id,
memoryCost: 65536, // 64 MB
timeCost: 3,
parallelism: 1,
});
}
// Verify a password
export async function verifyPassword(hash: string, password: string): Promise<boolean> {
return argon2.verify(hash, password);
}// bcrypt fallback (cost 12+ required in production)
import bcrypt from 'bcrypt';
const SALT_ROUNDS = 12;
export async function hashPassword(password: string): Promise<string> {
return bcrypt.hash(password, SALT_ROUNDS);
}
export async function verifyPassword(hash: string, password: string): Promise<boolean> {
return bcrypt.compare(password, hash);
}Always use
argon2.verify/bcrypt.comparefor comparison - they are constant-time. Never use===to compare hashes.
Encrypt data with AES-256-GCM
AES-256-GCM is authenticated encryption: it provides both confidentiality and integrity. Always use GCM mode, not CBC (CBC requires a separate MAC and is error-prone).
import { randomBytes, createCipheriv, createDecipheriv } from 'crypto';
const ALGORITHM = 'aes-256-gcm';
const KEY_LENGTH = 32; // 256 bits
const IV_LENGTH = 12; // 96 bits - recommended for GCM
const TAG_LENGTH = 16; // 128 bits
export function encrypt(plaintext: string, key: Buffer): {
ciphertext: string;
iv: string;
tag: string;
} {
const iv = randomBytes(IV_LENGTH);
const cipher = createCipheriv(ALGORITHM, key, iv, { authTagLength: TAG_LENGTH });
const encrypted = Buffer.concat([
cipher.update(plaintext, 'utf8'),
cipher.final(),
]);
return {
ciphertext: encrypted.toString('base64'),
iv: iv.toString('base64'),
tag: cipher.getAuthTag().toString('base64'),
};
}
export function decrypt(
ciphertext: string,
iv: string,
tag: string,
key: Buffer
): string {
const decipher = createDecipheriv(
ALGORITHM,
key,
Buffer.from(iv, 'base64'),
{ authTagLength: TAG_LENGTH }
);
decipher.setAuthTag(Buffer.from(tag, 'base64'));
return Buffer.concat([
decipher.update(Buffer.from(ciphertext, 'base64')),
decipher.final(),
]).toString('utf8');
}
// Generate a key (store in KMS, never hardcode)
export function generateKey(): Buffer {
return randomBytes(KEY_LENGTH);
}Never reuse an IV with the same key. Generate a fresh random IV for every encryption operation and store it alongside the ciphertext.
Implement JWT signing and verification
Use the jose library. It enforces algorithm allowlisting, handles key rotation via
JWKS, and is actively maintained.
import { SignJWT, jwtVerify, generateKeyPair } from 'jose';
// Generate a key pair once (store private key in secrets manager)
export async function generateJwtKeys() {
return generateKeyPair('ES256'); // prefer ES256 over RS256 - smaller, faster
}
// Sign a JWT
export async function signToken(
payload: Record<string, unknown>,
privateKey: CryptoKey
): Promise<string> {
return new SignJWT(payload)
.setProtectedHeader({ alg: 'ES256' })
.setIssuedAt()
.setExpirationTime('15m') // short-lived access tokens
.setIssuer('https://your-api.example.com')
.setAudience('https://your-api.example.com')
.sign(privateKey);
}
// Verify a JWT
export async function verifyToken(
token: string,
publicKey: CryptoKey
): Promise<Record<string, unknown>> {
const { payload } = await jwtVerify(token, publicKey, {
issuer: 'https://your-api.example.com',
audience: 'https://your-api.example.com',
algorithms: ['ES256'], // explicit allowlist - never omit this
});
return payload as Record<string, unknown>;
}Always specify an
algorithmsallowlist injwtVerify. The historicalg: "none"bypass happened because libraries trusted the header blindly.
Configure TLS certificates
For Node.js HTTPS servers, enforce TLS 1.3 and remove weak cipher suites.
import https from 'https';
import fs from 'fs';
const server = https.createServer(
{
key: fs.readFileSync('/etc/ssl/private/server.key'),
cert: fs.readFileSync('/etc/ssl/certs/server.crt'),
ca: fs.readFileSync('/etc/ssl/certs/ca.crt'), // optional: chain file
minVersion: 'TLSv1.3',
// If TLSv1.2 is required for legacy clients:
// minVersion: 'TLSv1.2',
// ciphers: [
// 'TLS_AES_256_GCM_SHA384',
// 'TLS_CHACHA20_POLY1305_SHA256',
// 'ECDHE-RSA-AES256-GCM-SHA384',
// ].join(':'),
honorCipherOrder: true,
},
app
);For certificate rotation without downtime, use Let's Encrypt with certbot and
configure auto-renewal. Point your server at the live symlink
(/etc/letsencrypt/live/<domain>/). On renewal, reload the process (SIGHUP for
nginx; graceful restart for Node).
Mutual TLS (mTLS) for service-to-service: add
requestCert: trueandrejectUnauthorized: trueto require client certificates.
Implement HMAC for webhook verification
Webhooks deliver signed payloads. HMAC-SHA256 lets receivers verify authenticity without asymmetric keys.
import { createHmac, timingSafeEqual } from 'crypto';
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET!;
const TIMESTAMP_TOLERANCE_SECONDS = 300; // 5 minutes - prevent replay attacks
export function signWebhookPayload(payload: string, timestamp: number): string {
const message = `${timestamp}.${payload}`;
return createHmac('sha256', WEBHOOK_SECRET).update(message).digest('hex');
}
export function verifyWebhookSignature(
payload: string,
signature: string,
timestamp: number
): boolean {
// Reject stale requests
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - timestamp) > TIMESTAMP_TOLERANCE_SECONDS) {
return false;
}
const expected = signWebhookPayload(payload, timestamp);
const expectedBuf = Buffer.from(expected, 'hex');
const receivedBuf = Buffer.from(signature, 'hex');
// Buffers must be equal length for timingSafeEqual
if (expectedBuf.length !== receivedBuf.length) {
return false;
}
return timingSafeEqual(expectedBuf, receivedBuf);
}Always use
timingSafeEqualfor signature comparison. String===is vulnerable to timing attacks that leak whether the prefix matched.
Set up key rotation strategy
Envelope encryption makes rotation low-risk and incremental.
// Pseudo-implementation showing the envelope encryption + rotation pattern
interface EncryptedRecord {
ciphertext: string;
iv: string;
tag: string;
encryptedDek: string; // DEK wrapped by KEK from KMS
keyVersion: string; // which KEK version was used
}
// Encryption: generate a fresh DEK per record (or per session)
async function encryptWithEnvelope(plaintext: string, kmsClient: KMSClient): Promise<EncryptedRecord> {
const dek = generateKey(); // random 256-bit DEK
const { ciphertext, iv, tag } = encrypt(plaintext, dek);
// KMS wraps (encrypts) the DEK - the DEK never leaves your process in plaintext
const { encryptedDek, keyVersion } = await kmsClient.encryptKey(dek);
dek.fill(0); // zero out DEK from memory immediately after use
return { ciphertext, iv, tag, encryptedDek, keyVersion };
}
// Key rotation: re-wrap the DEK with the new KEK version, no data re-encryption needed
async function rotateKey(record: EncryptedRecord, kmsClient: KMSClient): Promise<EncryptedRecord> {
const dek = await kmsClient.decryptKey(record.encryptedDek, record.keyVersion);
const { encryptedDek, keyVersion } = await kmsClient.encryptKey(dek, 'latest');
dek.fill(0);
return { ...record, encryptedDek, keyVersion };
}Rotation strategy: when a new KEK version is available, re-wrap DEKs lazily on access or proactively in a background job. Retire old KEK versions only after all DEKs have been re-wrapped.
Generate secure random tokens
For session IDs, API keys, password reset tokens, and CSRF tokens, use
crypto.randomBytes. Do not use Math.random.
import { randomBytes } from 'crypto';
// URL-safe base64 token (default 32 bytes = 256 bits)
export function generateToken(bytes = 32): string {
return randomBytes(bytes).toString('base64url');
}
// Hex token (for systems that require hex)
export function generateHexToken(bytes = 32): string {
return randomBytes(bytes).toString('hex');
}
// Numeric OTP (e.g., 6-digit)
export function generateOtp(digits = 6): string {
const max = 10 ** digits;
const value = Number(randomBytes(4).readUInt32BE(0)) % max;
return value.toString().padStart(digits, '0');
}32 bytes (256 bits) is the minimum for tokens used as secret keys or long-lived credentials. 16 bytes (128 bits) is acceptable for CSRF tokens where the attack surface is limited.
Anti-patterns
| Anti-pattern | Why it is dangerous | What to do instead |
|---|---|---|
MD5 or SHA-256 for passwords |
Fast hashes enable brute force at billions of attempts/sec | Use argon2id or bcrypt (cost >= 12) |
| Reusing an IV/nonce with the same key | Catastrophically breaks GCM confidentiality and integrity | Generate a fresh randomBytes(12) IV for every encrypt call |
alg: "none" in JWT or omitting algorithm allowlist |
Allows token forgery by stripping the signature | Always pass algorithms: ['ES256'] (or your chosen alg) to jwtVerify |
Comparing signatures with === |
String comparison short-circuits, leaking timing information | Use crypto.timingSafeEqual for all secret/signature comparisons |
Math.random() for tokens or keys |
Predictable PRNG, not suitable for security-sensitive values | Use crypto.randomBytes() |
| Encrypting passwords instead of hashing | Encrypted passwords are recoverable if the key leaks | Hash passwords; never encrypt them |
Gotchas
IV reuse with AES-256-GCM is catastrophically insecure - Reusing an IV (nonce) with the same key in GCM mode allows an attacker to recover the plaintext and the authentication key. This is not a theoretical risk - it is a known attack. Generate a fresh
randomBytes(12)IV for every single encrypt call without exception.alg: "none"JWT bypass via missing algorithm allowlist - If you calljwtVerifywithout passing an explicitalgorithmsallowlist, some library versions accept a token withalg: "none"in the header, bypassing signature verification entirely. Always passalgorithms: ['ES256'](or your chosen algorithm) to every verification call.Timing attacks on signature comparison - Using
===or.equals()to compare HMAC signatures or password hashes leaks timing information: the comparison short-circuits as soon as it finds a mismatch, revealing how many prefix bytes matched. Always usecrypto.timingSafeEqual()for any security-sensitive comparison.bcrypt silently truncates passwords at 72 bytes - bcrypt only processes the first 72 bytes of a password. A user with a 100-byte password gets the same hash as if they used the first 72 bytes. This is not a bug per se, but it means bcrypt does not protect extremely long passwords. Pre-hash with SHA-256 before bcrypt if you need to support passwords beyond 72 bytes.
Storing the DEK in plaintext next to the ciphertext - Envelope encryption only works if the data encryption key (DEK) is itself encrypted by a KMS-managed key. Storing an unencrypted DEK in the same database column as the ciphertext provides zero additional security over not encrypting at all.
References
references/algorithm-guide.md- when to use which algorithm: AES vs RSA vs ECDH, SHA-256 vs Argon2, ES256 vs RS256, and cipher mode comparisons
Load the references file only when deeper algorithm selection guidance is needed. It is detailed and will consume additional context.
References
algorithm-guide.md
Algorithm Selection Guide
Opinionated guidance on which cryptographic algorithm to pick for each use case. When in doubt, choose the recommended algorithm and move on. Do not bikeshed crypto.
1. Password Hashing
| Algorithm | Verdict | When to use |
|---|---|---|
| Argon2id | Recommended | New systems. Winner of Password Hashing Competition. Memory-hard + side-channel resistant |
| bcrypt | Acceptable | Established systems with existing bcrypt hashes. Cost factor >= 12 |
| scrypt | Acceptable | When Argon2 is unavailable. N=2^15, r=8, p=1 minimum |
| PBKDF2-SHA256 | Legacy only | Only for FIPS-required environments. Use iterations >= 600,000 (NIST 2023) |
| SHA-256 / SHA-512 | Never | Fast hashes - billions of attempts per second with a GPU |
| MD5 | Never | Broken, fast, no salt by default |
Argon2id parameters for production:
memoryCost: 65536 (64 MB - higher is better, balance against your server RAM)
timeCost: 3 (iterations)
parallelism: 1 (match to CPU cores if you want to use them)
hashLength: 32 (output bytes)Increase memoryCost as hardware becomes cheaper. The goal is to keep hashing time
around 100-500ms on your server. Benchmark on your target hardware before deploying.
2. Symmetric Encryption
| Algorithm | Verdict | Notes |
|---|---|---|
| AES-256-GCM | Recommended | Authenticated encryption. Single operation for confidentiality + integrity |
| ChaCha20-Poly1305 | Recommended | Preferred on hardware without AES-NI (mobile, IoT). Same security level |
| AES-128-GCM | Acceptable | 128-bit key has a smaller security margin but is still secure today |
| AES-256-CBC | Avoid | Unauthenticated. Requires separate HMAC. Padding oracle attacks if misimplemented |
| AES-256-ECB | Never | No IV, identical blocks produce identical ciphertext |
| DES / 3DES | Never | Deprecated. 3DES officially disallowed by NIST as of 2024 |
AES-GCM vs ChaCha20-Poly1305:
- Use AES-GCM when running on x86/x64 with AES-NI hardware acceleration (cloud servers, modern laptops). Fastest option by far.
- Use ChaCha20-Poly1305 on ARM/embedded or any hardware without AES-NI. Equal security, avoids timing side-channels from software AES.
- Both provide authenticated encryption (AEAD). Both are in TLS 1.3.
IV/nonce rules:
| Algorithm | Nonce size | Uniqueness requirement |
|---|---|---|
| AES-GCM | 96 bits (12 bytes) | Must be unique per key. Reuse catastrophically breaks security |
| ChaCha20-Poly1305 | 96 bits (12 bytes) | Must be unique per key. Random is safe |
| AES-CBC | 128 bits (16 bytes) | Must be unpredictable (random). Reuse leaks XOR of plaintexts |
Always generate nonces with a cryptographically secure RNG (crypto.randomBytes).
Do not use a counter unless you have strict control over the key lifetime.
3. Asymmetric Encryption and Key Exchange
| Algorithm | Verdict | Use case |
|---|---|---|
| ECDH (P-256 / X25519) | Recommended | Key exchange, ephemeral session keys |
| RSA-OAEP (2048-bit+) | Acceptable | Encrypting small keys/secrets when ECDH is not available |
| RSA-PKCS1v1.5 | Never | Padding oracle (PKCS#1 v1.5 is broken for encryption) |
| Raw RSA (textbook) | Never | No padding - trivially broken |
ECDH vs RSA for key exchange:
- ECDH with P-256 or X25519 provides the same security as RSA-3072 at a fraction of the key size and computation cost.
- Prefer X25519 when you control both ends - it was designed to avoid implementation mistakes (no cofactor issues, constant-time by construction).
- Use P-256 (prime256v1) for broad compatibility (FIPS environments, TLS, JWT).
When to use asymmetric encryption:
Asymmetric encryption is for small payloads only (a key, a token, a hash). For bulk data, use hybrid encryption: generate an ephemeral symmetric key, encrypt it with the recipient's public key, encrypt the data with the symmetric key, discard the symmetric key.
Sender:
1. Generate random DEK (AES-256 key)
2. Encrypt plaintext with DEK (AES-256-GCM)
3. Encrypt DEK with recipient's public key (RSA-OAEP or ECIES)
4. Send: encrypted DEK + IV + ciphertext + auth tag
Recipient:
1. Decrypt DEK with private key
2. Decrypt ciphertext with DEK
3. Zero DEK from memory4. Digital Signatures
| Algorithm | Verdict | Notes |
|---|---|---|
| Ed25519 | Recommended | Fast, small signatures (64 bytes), safe by design. Use for new systems |
| ES256 (ECDSA P-256) | Recommended | Widely supported in JWT, TLS, browser Web Crypto API |
| RS256 (RSA-PSS SHA-256) | Acceptable | Use RSA-PSS, not PKCS1v1.5. Required for some compliance environments |
| RS256 with PKCS1v1.5 | Avoid | Deterministic, not IND-CCA2 secure for signatures |
| ECDSA without deterministic K | Never | If random K is weak/reused, private key is recoverable |
JWT algorithm selection:
ES256 - P-256 curve, SHA-256. Good balance. Supported everywhere.
ES384 - P-384 curve, SHA-384. Higher security margin, slightly slower.
EdDSA - Ed25519. Best choice for new systems where library support exists.
RS256 - RSA 2048+, SHA-256. Choose only for FIPS compliance or legacy clients.
Never use:
HS256 (symmetric HMAC) for multi-party JWTs - any party can forge tokens
none (no signature) - allows trivial forgery
RS256 with 1024-bit keys - broken key sizeSignature size comparison (for bandwidth/storage consideration):
| Algorithm | Signature size |
|---|---|
| Ed25519 | 64 bytes |
| ES256 | ~72 bytes (variable DER encoding) |
| RS256 (2048-bit) | 256 bytes |
| RS256 (4096-bit) | 512 bytes |
5. Hashing (Non-Password)
| Algorithm | Verdict | Use case |
|---|---|---|
| SHA-256 | Recommended | Integrity checks, checksums, HMAC, key derivation input |
| SHA-384 / SHA-512 | Recommended | Higher security margin, use where output size is acceptable |
| SHA-3 (Keccak) | Recommended | When SHA-2 is specifically excluded (some FIPS contexts) |
| BLAKE3 | Recommended | Fastest modern hash. Use for non-FIPS high-throughput scenarios |
| SHA-1 | Never | Collision attacks demonstrated (SHAttered, 2017) |
| MD5 | Never | Collision attacks since 2004 |
When to use which hash function:
- Content integrity / checksums - SHA-256. Standard, universally supported.
- HMAC signatures - HMAC-SHA256. Industry standard for API signing, webhooks.
- Key derivation from another key - HKDF-SHA256. Extract entropy, then expand.
- Password hashing - Do NOT use any hash here. Use Argon2id (see section 1).
- High-throughput content addressing - BLAKE3. 3-4x faster than SHA-256 in software.
6. Key Derivation
| Function | Verdict | Use case |
|---|---|---|
| HKDF | Recommended | Derive multiple keys from one master key or shared secret |
| Argon2id | Recommended | Derive key from a password (slow by design) |
| PBKDF2 | Legacy | FIPS-required environments only |
crypto.scrypt |
Acceptable | Alternative to Argon2 when Argon2 library unavailable |
| Raw SHA-256(password) | Never | No key stretching - fast and vulnerable to brute force |
HKDF usage pattern (Node.js):
import { hkdfSync } from 'crypto';
// Derive a 256-bit encryption key and a 256-bit MAC key from one master secret
const masterSecret = Buffer.from(process.env.MASTER_SECRET!, 'base64');
const salt = randomBytes(32); // optional but recommended; store alongside derived output
const info = Buffer.from('app-v1-encryption-key', 'utf8');
const encryptionKey = Buffer.from(
hkdfSync('sha256', masterSecret, salt, info, 32)
);7. TLS Configuration
Protocol versions:
| Version | Status | Action |
|---|---|---|
| TLS 1.3 | Current standard | Enable, prefer |
| TLS 1.2 | Acceptable fallback | Allow only if legacy clients require it |
| TLS 1.1 | Deprecated (RFC 8996) | Disable |
| TLS 1.0 | Deprecated (RFC 8996) | Disable |
| SSL 3.0 | Broken (POODLE) | Disable |
Recommended cipher suites for TLS 1.2 fallback:
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
Disable:
Any cipher with RC4, DES, 3DES, NULL, EXPORT, or anon
ECDHE-RSA-AES*-CBC* (unauthenticated CBC modes)
Any DHE with DH params < 2048 bits (Logjam attack)TLS 1.3 cipher suites are non-negotiable and always safe - they are the only ones the protocol supports.
Certificate key sizes:
| Key type | Minimum | Recommended |
|---|---|---|
| RSA | 2048 bits | 4096 bits for new CAs; 2048 acceptable for leaf certs |
| ECDSA | P-256 | P-256 (equivalent to RSA-3072) |
| Ed25519 | - | Preferred where supported |
Use ECDSA P-256 leaf certificates for new deployments - smaller, faster TLS handshakes, same or better security than RSA-2048.
Quick Decision Tree
What are you protecting?
PASSWORDS
-> Argon2id (preferred) or bcrypt (cost 12+)
-> NEVER SHA-*, MD5, or custom hashing
DATA AT REST
-> AES-256-GCM (or ChaCha20-Poly1305 on non-AES-NI hardware)
-> Fresh random IV per encryption
-> Store IV + auth tag + ciphertext together
DATA IN TRANSIT
-> TLS 1.3 minimum
-> ECDSA P-256 cert
SIGNING A TOKEN (JWT)
-> ES256 (ECDSA P-256) for new systems
-> RS256 (RSA-PSS 2048+) only for FIPS compliance
-> Always specify algorithm allowlist on verification
SIGNING AN API REQUEST / WEBHOOK
-> HMAC-SHA256
-> Include timestamp to prevent replay
-> Compare with timingSafeEqual
KEY EXCHANGE
-> X25519 (ECDH) or P-256 ECDH
-> Hybrid: ECDH for key exchange, AES-GCM for data
INTEGRITY CHECK (not passwords)
-> SHA-256 for files, checksums, content addressing
-> HMAC-SHA256 if authenticity matters too
RANDOM TOKEN / SESSION ID
-> crypto.randomBytes(32) minimum
-> base64url encode for URL-safe output Frequently Asked Questions
What is cryptography?
Use this skill when implementing encryption, hashing, TLS configuration, JWT tokens, or key management. Triggers on encryption, hashing, bcrypt, AES, RSA, TLS certificates, JWT signing, HMAC, key rotation, digital signatures, and any task requiring cryptographic implementation or protocol selection.
How do I install cryptography?
Run npx skills add AbsolutelySkilled/AbsolutelySkilled --skill cryptography in your terminal. The skill will be immediately available in your AI coding agent.
What AI agents support cryptography?
cryptography works with claude-code, gemini-cli, openai-codex. Install it once and use it across any supported AI coding agent.