The crypto Module
The crypto module is Node's built-in toolkit for cryptography — creating hashes and HMAC signatures, generating secure random values, and safely deriving and comparing password hashes.
Learn The crypto Module in our free Node.js course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick reference.
Part of the free Node.js course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
By the end of this lesson you'll be able to fingerprint data with SHA-256, sign messages with HMAC, generate secure tokens and UUIDs, store passwords the right way with scrypt and a salt, and compare secrets safely with timingSafeEqual.
What You'll Learn in This Lesson
1️⃣ Hashing & HMAC
A hash turns any input into a fixed-size fingerprint. The same input always yields the same digest, the tiniest change yields a wildly different one, and you can't run it backwards. The pattern in Node is always createHash(algo).update(data).digest('hex') .
HMAC is a keyed hash: it mixes in a secret so only someone holding that key can produce or verify the signature. It's how services sign webhooks and API requests so the receiver can trust the message wasn't forged or altered.
2️⃣ Secure Randomness
Never use Math.random() for anything security-related — it's predictable. The crypto module gives you cryptographically strong randomness: randomBytes(n) for raw tokens and salts, randomUUID() for standard v4 IDs, and randomInt(min, max) for a fair integer in a range.
Because these values are random by design, the example checks properties of the output (length, format, range) rather than printing the exact bytes — that way the output is reproducible while the values stay genuinely random.
3️⃣ Passwords Done Right
This is the most important section. Never store a password as a plain SHA-256 hash — it's far too fast, so attackers can guess billions per second. Use a deliberately slow key-derivation function like scrypt (or pbkdf2 ), with a unique random salt per user so identical passwords never share a hash.
When you verify a login, re-derive the hash from the attempt and the stored salt, then compare with crypto.timingSafeEqual — a constant-time compare that won't leak how close a guess was. (We use a fixed salt here only so the demo output is reproducible; real code uses randomBytes .)
Your turn. The program below works once you fill in the two blanks marked ___ . Follow the 👉 hints, then run it and compare with the expected digest.
Mini-Challenge: A Safe Password Check
No blanks this time — just a brief and an outline. Write it yourself, run it, and check your output against the example in the comments. This ties together scryptSync , salts, and timingSafeEqual .
Common Errors (and the fix)
- ❌ Storing passwords as sha256 — Too fast to be safe. ✅ Use scrypt , pbkdf2 , bcrypt, or argon2 with a salt.
- ❌ Reusing one salt for everyone — Identical passwords get identical hashes. ✅ A unique randomBytes salt per user, stored with the hash.
- ❌ Comparing secrets with === — Leaks timing information. ✅ Use crypto.timingSafeEqual on equal-length Buffers.
- ❌ Math.random() for tokens — Predictable, not secure. ✅ crypto.randomBytes or crypto.randomUUID .
- ❌ "digest already called" — A Hash object is single-use. ✅ Create a fresh createHash(...) for each value.
Pro Tips
- 💡 Prefer the async forms ( crypto.scrypt , crypto.randomBytes with a callback) in servers so a slow derivation doesn't block the event loop.
- 💡 Store salt + hash together , often as a single string like salt:hash , so verification has everything it needs.
- 💡 For real auth, consider a vetted library like argon2 or bcrypt — they bake in sensible defaults on top of these primitives.
📋 Quick Reference — crypto
Task
Call
Hash data
createHash('sha256').update(d).digest('hex')
Sign with a key
createHmac('sha256', key)...
Secure token
randomBytes(16).toString('hex')
UUID
randomUUID()
Password hash
scrypt(pw, salt, 32)
Compare secrets
timingSafeEqual(a, b)
Frequently Asked Questions
🎉 Lesson Complete!
- ✅ createHash('sha256') makes a one-way fingerprint of any data
- ✅ createHmac adds a secret key so a signature can be trusted
- ✅ randomBytes , randomUUID , and randomInt give secure randomness
- ✅ Passwords need a slow KDF ( scrypt / pbkdf2 ) and a unique salt — never plain sha256
- ✅ Compare secrets with timingSafeEqual , not ===
- ✅ Next lesson: Promises & async/await Deep Dive — master asynchronous control flow
Practice quiz
What is a hash?
- A reversible encryption of data
- A random number generator
- A one-way fingerprint that cannot be reversed
- A compression format
Answer: A one-way fingerprint that cannot be reversed. A hash maps input to a fixed-size fingerprint; the same input always yields the same digest and you cannot reverse it.
What is the Node pattern for a SHA-256 hex digest?
- createHash('sha256').update(data).digest('hex')
- hash('sha256', data)
- sha256(data).hex()
- crypto.encrypt(data, 'sha256')
Answer: createHash('sha256').update(data).digest('hex'). The pattern is always createHash(algo).update(data).digest('hex').
What does HMAC add compared to a plain hash?
- Reversibility
- Compression
- A timestamp
- A secret key, so only key holders can produce or verify it
Answer: A secret key, so only key holders can produce or verify it. HMAC mixes a secret key into the hash, used to sign webhooks and API requests so the receiver can trust them.
Which function generates cryptographically strong random bytes?
- Math.random()
- crypto.randomBytes(n)
- crypto.hash(n)
- crypto.salt(n)
Answer: crypto.randomBytes(n). crypto.randomBytes(n) returns strong random data for tokens and salts; never use Math.random() for security.
How many hex characters does crypto.randomBytes(16).toString('hex') produce?
- 32
- 16
- 64
- 8
Answer: 32. Each byte becomes two hex characters, so 16 bytes produce a 32-character hex string.
Why must you NOT store passwords as a plain SHA-256 hash?
- SHA-256 is reversible
- SHA-256 cannot hash text
- SHA-256 is too fast, so attackers can guess billions per second
- SHA-256 needs a network connection
Answer: SHA-256 is too fast, so attackers can guess billions per second. Fast hashes let attackers brute-force quickly; use a slow KDF like scrypt or pbkdf2 instead.
What is a salt and why is it unique per user?
- A secret shared by all users
- Random data per password so identical passwords get different hashes
- An encryption key
- The hashing algorithm name
Answer: Random data per password so identical passwords get different hashes. A unique random salt makes every stored hash different and defeats precomputed rainbow tables.
Which function derives a slow password hash in the lesson?
- crypto.createHash
- crypto.randomUUID
- crypto.createHmac
- crypto.scryptSync
Answer: crypto.scryptSync. scrypt is a deliberately slow key-derivation function; the demo uses crypto.scryptSync(password, salt, 32).
Why compare secrets with crypto.timingSafeEqual instead of ===?
- It is faster than ===
- It compares in constant time, avoiding timing side-channel leaks
- It works on strings only
- It encrypts the inputs first
Answer: It compares in constant time, avoiding timing side-channel leaks. === can stop at the first differing byte, leaking timing info; timingSafeEqual always takes the same time.
When should you use createHash versus createHmac?
- Always use createHash
- createHmac for file integrity only
- createHash for a plain fingerprint, createHmac when a shared secret is involved
- They are interchangeable
Answer: createHash for a plain fingerprint, createHmac when a shared secret is involved. Use createHash for integrity or cache keys with no secret; use createHmac to prove a message came from a key holder.
Continue this course
- Previous: Worker Threads & Cluster
- Next: Promises & async/await Deep Dive