| name | stanford-crypto-patterns |
| description | Side-channel-resistant cryptography patterns from Stanford SJCL. Constant-time operations, AES-CCM authenticated encryption, PBKDF2 key derivation, random number generation, and timing-attack-safe comparisons. Sources: bitwiseshiftleft/sjcl (Stanford JavaScript Crypto Library). |
/stanford-crypto-patterns
When to Use
- Environments where timing-attack resistance is critical (secret comparison)
- AES-CCM authenticated encryption (alternative to GCM with simpler nonce handling)
- SJCL PRNG for cryptographic random number generation
- Academic/compliance contexts requiring vetted, peer-reviewed crypto library
Do NOT use for
- New Node.js code — prefer built-in
crypto module (WebCrypto API) for standard cases
- Performance-critical bulk encryption (pure JS SJCL is slower than native C bindings)
Constant-time comparison (timing-attack safe)
import sjcl from 'sjcl'
function constantTimeEqual(a: string, b: string): boolean {
if (a.length !== b.length) return false
const aWords = sjcl.codec.utf8String.toBits(a)
const bWords = sjcl.codec.utf8String.toBits(b)
let diff = 0
for (let i = 0; i < aWords.length; i++) {
diff |= aWords[i] ^ bWords[i]
}
return diff === 0
}
AES-CCM authenticated encryption
import sjcl from 'sjcl'
function encryptSJCL(plaintext: string, passphrase: string): string {
const encrypted = sjcl.encrypt(passphrase, plaintext, {
ks: 256,
ts: 128,
iter: 100_000,
mode: 'ccm',
})
return encrypted
}
function decryptSJCL(encrypted: string, passphrase: string): string {
try {
return sjcl.decrypt(passphrase, encrypted)
} catch (e) {
throw new Error('[sjcl] decryption failed — wrong passphrase or tampered data')
}
}
Cryptographic random bytes
function secureRandom(bytes: number): string {
const wordCount = Math.ceil(bytes / 4)
const words = sjcl.random.randomWords(wordCount, 10)
return sjcl.codec.hex.fromBits(words).slice(0, bytes * 2)
}
const nonce = secureRandom(16)
PBKDF2 key derivation
function deriveKeySJCL(passphrase: string, saltHex: string, iterations = 100_000): string {
const salt = sjcl.codec.hex.toBits(saltHex)
const key = sjcl.misc.pbkdf2(passphrase, salt, iterations, 256)
return sjcl.codec.hex.fromBits(key)
}
SHA-256 / HMAC
const hash = sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash('data'))
const key = sjcl.codec.utf8String.toBits(process.env.HMAC_KEY!)
const mac = sjcl.codec.hex.fromBits(new sjcl.misc.hmac(key).encrypt('message'))
Anti-Fake-Pass Checklist
❌ sjcl.random.randomWords without seeding → throws on missing entropy in Node.js (use node-crypto seed)
❌ sjcl.encrypt paranoia < 6 → low entropy tolerance warning; use 8-10 for production
❌ Comparing MAC tags with === → timing attack; use constantTimeEqual above
❌ AES-CCM nonce reused → catastrophic, breaks authentication (SJCL uses random IV by default)
❌ sjcl in browser vs Node.js → window.crypto auto-seed in browser, must manually add entropy in Node
❌ sjcl output is JSON object — stringify before storing, parse before decrypting