From harness-claude
Applies cryptographic best practices for password hashing (bcrypt/Argon2), AES-256-GCM encryption, secure random generation, HMAC, digital signing, and key management using Node.js crypto.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Apply cryptographic best practices for password hashing, data encryption, digital signing, and key management
Implements Node.js crypto patterns for hashing, HMAC signatures, scrypt password hashing, secure random generation, and AES-256-GCM encryption/decryption. Useful for data integrity, API auth, and sensitive data protection.
Provides cryptography guidance on encryption (AES-256-GCM, ChaCha20), password hashing (Argon2id, bcrypt), signatures (Ed25519), TLS config, key management. Use for implementing or reviewing crypto.
Guides secure cryptography: hashing (Argon2id, bcrypt), encryption (AES-256-GCM), key management, JWT signing, TLS hardening, digital signatures for sensitive data.
Share bugs, ideas, or general feedback.
Apply cryptographic best practices for password hashing, data encryption, digital signing, and key management
import bcrypt from 'bcrypt';
const SALT_ROUNDS = 12; // Adjust for ~250ms hash time on your hardware
async function hashPassword(plaintext: string): Promise<string> {
return bcrypt.hash(plaintext, SALT_ROUNDS);
}
async function verifyPassword(plaintext: string, hash: string): Promise<boolean> {
return bcrypt.compare(plaintext, hash);
}
For higher security requirements, use Argon2id:
import argon2 from 'argon2';
async function hashPassword(plaintext: string): Promise<string> {
return argon2.hash(plaintext, {
type: argon2.argon2id,
memoryCost: 65536, // 64 MB
timeCost: 3,
parallelism: 4,
});
}
import crypto from 'node:crypto';
const ALGORITHM = 'aes-256-gcm';
const IV_LENGTH = 12; // 96 bits for GCM
const TAG_LENGTH = 16; // 128 bits
function encrypt(plaintext: string, key: Buffer): string {
const iv = crypto.randomBytes(IV_LENGTH);
const cipher = crypto.createCipheriv(ALGORITHM, key, iv, { authTagLength: TAG_LENGTH });
const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
const tag = cipher.getAuthTag();
// Prepend IV and tag to ciphertext for storage
return Buffer.concat([iv, tag, encrypted]).toString('base64');
}
function decrypt(encoded: string, key: Buffer): string {
const data = Buffer.from(encoded, 'base64');
const iv = data.subarray(0, IV_LENGTH);
const tag = data.subarray(IV_LENGTH, IV_LENGTH + TAG_LENGTH);
const ciphertext = data.subarray(IV_LENGTH + TAG_LENGTH);
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv, { authTagLength: TAG_LENGTH });
decipher.setAuthTag(tag);
return decipher.update(ciphertext) + decipher.final('utf8');
}
crypto.randomBytes() or crypto.randomUUID() — never Math.random() for security purposes.import crypto from 'node:crypto';
// Random bytes for keys, IVs, tokens
const token = crypto.randomBytes(32).toString('hex');
// Random UUID
const id = crypto.randomUUID();
function createHmac(data: string, secret: string): string {
return crypto.createHmac('sha256', secret).update(data).digest('hex');
}
function verifyHmac(data: string, secret: string, expectedMac: string): boolean {
const computed = createHmac(data, secret);
return crypto.timingSafeEqual(Buffer.from(computed), Buffer.from(expectedMac));
}
=== — it is vulnerable to timing attacks. Use crypto.timingSafeEqual().function safeCompare(a: string, b: string): boolean {
if (a.length !== b.length) return false;
return crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b));
}
function deriveKey(password: string, salt: Buffer): Promise<Buffer> {
return new Promise((resolve, reject) => {
crypto.scrypt(password, salt, 32, { N: 16384, r: 8, p: 1 }, (err, key) => {
if (err) reject(err);
else resolve(key);
});
});
}
// Generate key pair
const { publicKey, privateKey } = crypto.generateKeyPairSync('ed25519');
// Sign
const signature = crypto.sign(null, Buffer.from(data), privateKey);
// Verify
const isValid = crypto.verify(null, Buffer.from(data), publicKey, signature);
interface EncryptedField {
keyVersion: number;
ciphertext: string;
}
Algorithm selection guide:
| Purpose | Recommended | Avoid |
|---|---|---|
| Password hashing | Argon2id, bcrypt, scrypt | MD5, SHA-*, plain text |
| Symmetric encryption | AES-256-GCM | AES-ECB, DES, 3DES, RC4 |
| Hashing (non-password) | SHA-256, SHA-3 | MD5, SHA-1 |
| Signing | Ed25519, RSA-PSS (2048+) | RSA-PKCS1v15 |
| Key derivation | scrypt, PBKDF2 (600K+ iterations) | Single-pass hash |
| Random generation | crypto.randomBytes | Math.random |
Why GCM over CBC: GCM is an authenticated encryption mode — it detects tampering. CBC requires a separate HMAC step for integrity, and incorrect implementations lead to padding oracle attacks.
IV/nonce rules: Never reuse an IV with the same key. For AES-GCM, IV reuse completely breaks security. Generate a random IV for every encrypt call.
Common mistakes:
=== instead of timingSafeEqualhttps://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html