📦 The crypto Module
crypto is a built-in Node.js module for cryptographic operations — hashing, generating random values, and encrypting/decrypting data. It's what powers things like password hashing, secure tokens, and signed data under the hood.
const crypto = require('crypto');🔒 Hashing
A hash turns data into a fixed-length string. The same input always produces the same hash, but you can't reverse a hash back into the original data.
const hash = crypto.createHash('sha256').update('hello world').digest('hex');
console.log(hash);
// 'b94d27b9934d3e08a52e52d7da7dacefbd2af3a4c...'createHash('sha256')picks the hashing algorithm — SHA-256 is a common, secure choice..update(data)feeds in the data you want to hash..digest('hex')outputs the result as a readable hex string (you could also use'base64').Same input → same hash, every time. Useful for checking if a file changed, or comparing data without storing the original.
⚠️ Don't use plain hashing for passwords — it's fast, which makes it easy to brute-force. Use a slow, purpose-built algorithm instead (see below).
🔑 HMAC (Hashing with a Secret Key)
An HMAC is a hash that also requires a secret key — so only someone with the key can generate or verify it.
const hmac = crypto.createHmac('sha256', 'my-secret-key').update('hello world').digest('hex');
console.log(hmac);Same idea as
createHash, butcreateHmactakes a secret key as the second argument.Used to verify that a message came from someone who has the key, and wasn't tampered with — common in API request signing.
🎲 Random Values
const randomBytes = crypto.randomBytes(16).toString('hex');
console.log(randomBytes);
// e.g. 'a3f5c9d1e8b74f2a0c6d5e9f1b3a7c8d'randomBytes(16)generates 16 cryptographically secure random bytes — safe to use for tokens, session IDs, or password reset links, unlikeMath.random()..toString('hex')converts the raw bytes into a readable string.
🆔 Generating a UUID
const id = crypto.randomUUID();
console.log(id);
// e.g. '3b241101-e2bb-4255-8caf-4136c566a962'randomUUID()generates a unique ID in standard UUID format — handy for database record IDs or tracking requests.
🔐 Encrypting & Decrypting Data
Unlike hashing, encryption is reversible — anyone with the right key can decrypt the data back to its original form.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AES-256-CBC, automated</title>
</head>
<body>
<div class="cr-info" id="crinfo">
<style>
.cr-info {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft: #e3ebfd;
--line: #ddd6c9;
--display-font: 'Fraunces', Georgia, serif;
--body-font: 'Inter', system-ui, sans-serif;
--mono-font: 'IBM Plex Mono', monospace;
max-width: 880px;
margin: 0 auto;
box-sizing: border-box;
padding: 2rem;
font-family: var(--body-font);
color: var(--ink);
background: var(--paper);
}
.cr-info[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1c212b;
--accent: #5b8def;
--accent-soft: #202942;
--line: #2c313d;
}
.cr-info * { box-sizing: border-box; }
.cr-eyebrow {
font-family: var(--mono-font); font-size: 0.68rem; letter-spacing: 0.14em; text-transform: uppercase;
color: var(--accent); text-align: center; margin: 1.5rem 0 0.3rem;
}
.cr-title {
font-family: var(--display-font); font-size: clamp(1.4rem, 5vw, 1.85rem); text-align: center;
margin: 0 0 0.5rem; letter-spacing: -0.01em;
}
.cr-sub { color: var(--ink-soft); font-size: 0.88rem; text-align: center; margin: 0 0 1.3rem; }
.cr-code-wrap { background: #1e1e1e; border-radius: 8px; padding: 0.7rem 0; margin-bottom: 1rem; overflow-x: auto; }
.cr-code-line { display: flex; gap: 0.9rem; padding: 0.06rem 1rem; font-family: var(--mono-font); font-size: 0.71rem; line-height: 1.55; white-space: pre; transition: background 0.25s; }
.cr-code-line.is-current { background: rgba(97, 175, 239, 0.16); }
.cr-ln { color: #5a5a5a; user-select: none; width: 16px; text-align: right; flex-shrink: 0; }
.cr-code-line.is-current .cr-ln { color: #61afef; }
.cr-src { color: #d4d4d8; }
.tok-kw { color: #c678dd; }
.tok-fn { color: #61afef; }
.tok-str { color: #98c379; }
.tok-punc { color: #6b7280; }
.tok-comment { color: #6a9955; font-style: italic; }
.cr-field { margin-bottom: 0.9rem; }
.cr-field-label { font-family: var(--mono-font); font-size: 0.68rem; font-weight: 700; color: var(--ink-soft); margin-bottom: 0.3rem; display: block; }
.cr-input {
width: 100%; font-family: var(--mono-font); font-size: 0.85rem; padding: 0.55rem 0.75rem;
border: 1px solid var(--line); border-radius: 6px; background: var(--paper-alt); color: var(--ink);
}
.cr-input:focus { outline: none; border-color: var(--accent); }
.cr-input:disabled { opacity: 0.6; }
.cr-output { display: flex; flex-direction: column; gap: 0.5rem; margin-bottom: 0.6rem; }
.cr-out-row { background: #1e1e1e; border-radius: 6px; padding: 0.6rem 0.8rem; font-family: var(--mono-font); font-size: 0.74rem; line-height: 1.5; word-break: break-all; }
.cr-out-label { color: #6b7280; font-size: 0.64rem; text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 0.2rem; }
.cr-out-value { color: #d4d4d8; }
.cr-out-row.is-lock .cr-out-value { color: #e5c07b; }
.cr-out-row.is-unlock .cr-out-value { color: #7ee787; }
.cr-out-row.is-empty { color: var(--ink-soft); font-style: italic; background: var(--paper-alt); }
.cr-caption { text-align: center; font-size: 0.84rem; color: var(--ink-soft); line-height: 1.5; min-height: 2.6em; margin: 1rem 0; }
.cr-caption b { color: var(--ink); }
.cr-controls { display: flex; justify-content: center; align-items: center; gap: 0.7rem; margin-bottom: 0.5rem; }
.cr-btn {
font-family: var(--mono-font); font-size: 0.82rem; font-weight: 600; padding: 0.55rem 1.1rem;
border-radius: 5px; border: 1px solid var(--accent); background: var(--accent); color: #fff; cursor: pointer;
}
.cr-btn.is-ghost { background: var(--paper); color: var(--ink-soft); border-color: var(--line); }
.cr-btn:disabled { opacity: 0.4; cursor: default; }
.cr-step-count { font-family: var(--mono-font); font-size: 0.74rem; color: var(--ink-soft); }
.cr-verify { text-align: center; font-size: 0.82rem; font-weight: 600; color: var(--accent); min-height: 1.3em; margin: 0.3rem 0 0.5rem; opacity: 0; transition: opacity 0.3s; }
.cr-verify.is-shown { opacity: 1; }
.cr-hint { text-align: center; font-size: 0.78rem; color: var(--ink-soft); margin: 0.8rem 0 2rem; }
@media (prefers-reduced-motion: reduce) {
.cr-info *, .cr-info *::before, .cr-info *::after { transition-duration: 0.001ms !important; animation-duration: 0.001ms !important; }
}
</style>
<div class="cr-field">
<span class="cr-field-label">TEXT TO ENCRYPT</span>
<input class="cr-input" id="crPlaintext" value="hello world">
</div>
<div class="cr-code-wrap" id="crCode">
<div class="cr-code-line" data-line="1"><span class="cr-ln">1</span><span class="cr-src"><span class="tok-kw">const</span> crypto = <span class="tok-fn">require</span><span class="tok-punc">(</span><span class="tok-str">'crypto'</span><span class="tok-punc">);</span></span></div>
<div class="cr-code-line" data-line="2"><span class="cr-ln">2</span><span class="cr-src"> </span></div>
<div class="cr-code-line" data-line="3"><span class="cr-ln">3</span><span class="cr-src"><span class="tok-kw">const</span> algorithm = <span class="tok-str">'aes-256-cbc'</span><span class="tok-punc">;</span></span></div>
<div class="cr-code-line" data-line="4"><span class="cr-ln">4</span><span class="cr-src"><span class="tok-kw">const</span> key = crypto.<span class="tok-fn">randomBytes</span><span class="tok-punc">(</span><span class="tok-expr">32</span><span class="tok-punc">);</span> <span class="tok-comment">// secret key</span></span></div>
<div class="cr-code-line" data-line="5"><span class="cr-ln">5</span><span class="cr-src"><span class="tok-kw">const</span> iv = crypto.<span class="tok-fn">randomBytes</span><span class="tok-punc">(</span><span class="tok-expr">16</span><span class="tok-punc">);</span> <span class="tok-comment">// initialization vector</span></span></div>
<div class="cr-code-line" data-line="6"><span class="cr-ln">6</span><span class="cr-src"> </span></div>
<div class="cr-code-line" data-line="7"><span class="cr-ln">7</span><span class="cr-src"><span class="tok-comment">// Encrypt</span></span></div>
<div class="cr-code-line" data-line="8"><span class="cr-ln">8</span><span class="cr-src"><span class="tok-kw">const</span> cipher = crypto.<span class="tok-fn">createCipheriv</span><span class="tok-punc">(</span>algorithm, key, iv<span class="tok-punc">);</span></span></div>
<div class="cr-code-line" data-line="9"><span class="cr-ln">9</span><span class="cr-src"><span class="tok-kw">let</span> encrypted = cipher.<span class="tok-fn">update</span><span class="tok-punc">(</span><span class="tok-str">'hello world'</span><span class="tok-punc">,</span> <span class="tok-str">'utf8'</span><span class="tok-punc">,</span> <span class="tok-str">'hex'</span><span class="tok-punc">);</span></span></div>
<div class="cr-code-line" data-line="10"><span class="cr-ln">10</span><span class="cr-src">encrypted += cipher.<span class="tok-fn">final</span><span class="tok-punc">(</span><span class="tok-str">'hex'</span><span class="tok-punc">);</span></span></div>
<div class="cr-code-line" data-line="11"><span class="cr-ln">11</span><span class="cr-src">console.<span class="tok-fn">log</span><span class="tok-punc">(</span><span class="tok-str">'🔒 Encrypted:'</span><span class="tok-punc">,</span> encrypted<span class="tok-punc">);</span></span></div>
<div class="cr-code-line" data-line="12"><span class="cr-ln">12</span><span class="cr-src"> </span></div>
<div class="cr-code-line" data-line="13"><span class="cr-ln">13</span><span class="cr-src"><span class="tok-comment">// Decrypt</span></span></div>
<div class="cr-code-line" data-line="14"><span class="cr-ln">14</span><span class="cr-src"><span class="tok-kw">const</span> decipher = crypto.<span class="tok-fn">createDecipheriv</span><span class="tok-punc">(</span>algorithm, key, iv<span class="tok-punc">);</span></span></div>
<div class="cr-code-line" data-line="15"><span class="cr-ln">15</span><span class="cr-src"><span class="tok-kw">let</span> decrypted = decipher.<span class="tok-fn">update</span><span class="tok-punc">(</span>encrypted, <span class="tok-str">'hex'</span><span class="tok-punc">,</span> <span class="tok-str">'utf8'</span><span class="tok-punc">);</span></span></div>
<div class="cr-code-line" data-line="16"><span class="cr-ln">16</span><span class="cr-src">decrypted += decipher.<span class="tok-fn">final</span><span class="tok-punc">(</span><span class="tok-str">'utf8'</span><span class="tok-punc">);</span></span></div>
<div class="cr-code-line" data-line="17"><span class="cr-ln">17</span><span class="cr-src">console.<span class="tok-fn">log</span><span class="tok-punc">(</span><span class="tok-str">'🔓 Decrypted:'</span><span class="tok-punc">,</span> decrypted<span class="tok-punc">);</span></span></div>
</div>
<div class="cr-output">
<div class="cr-out-row is-empty" id="crKeyRow">— not generated yet —</div>
<div class="cr-out-row is-empty" id="crIvRow">— not generated yet —</div>
<div class="cr-out-row is-empty" id="crEncRow">— not encrypted yet —</div>
<div class="cr-out-row is-empty" id="crDecRow">— not decrypted yet —</div>
</div>
<p class="cr-caption" id="crCaption">Press Next to run the first line.</p>
<p class="cr-verify" id="crVerify"> </p>
<div class="cr-controls">
<button type="button" class="cr-btn is-ghost" id="crResetBtn">↻ Restart</button>
<button type="button" class="cr-btn" id="crNextBtn">▶ Play</button>
</div>
<div class="cr-controls">
<span class="cr-step-count" id="crStepCount">Step 0 / 10</span>
</div>
<p class="cr-hint">The key, IV, and ciphertext are genuinely computed by your browser — restart and they’ll be different.</p>
</div>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Fraunces:wght@500;600&family=Inter:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet">
<script>
(function () {
var root = document.getElementById('crinfo');
if (!root) return;
var bodyAttr = document.body.getAttribute('data-darkmode');
var dark;
if (bodyAttr === 'true') dark = true;
else if (bodyAttr === 'false') dark = false;
else {
var match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) root.setAttribute('data-theme', 'dark');
var plaintextInput = root.querySelector('#crPlaintext');
var codeLines = root.querySelectorAll('.cr-code-line');
var keyRow = root.querySelector('#crKeyRow');
var ivRow = root.querySelector('#crIvRow');
var encRow = root.querySelector('#crEncRow');
var decRow = root.querySelector('#crDecRow');
var caption = root.querySelector('#crCaption');
var verify = root.querySelector('#crVerify');
var nextBtn = root.querySelector('#crNextBtn');
var resetBtn = root.querySelector('#crResetBtn');
var stepCount = root.querySelector('#crStepCount');
function bufToHex(buf) {
return Array.prototype.map.call(new Uint8Array(buf), function (b) {
return b.toString(16).padStart(2, '0');
}).join('');
}
function highlightLine(n) {
codeLines.forEach(function (l) {
l.classList.toggle('is-current', l.getAttribute('data-line') === String(n));
});
}
function setRow(el, label, value, cls) {
el.className = 'cr-out-row' + (cls ? ' ' + cls : '');
el.innerHTML = '<div class="cr-out-label">' + label + '</div><div class="cr-out-value">' + value + '</div>';
}
/* state carried between steps */
var keyBytes, ivBytes, cryptoKey, encryptedBuf, encryptedHex, plaintext;
var steps = [
{ line: 1, caption: 'Node\u2019s built-in <b>crypto</b> module — here, run through the browser\u2019s real Web Crypto API.' },
{ line: 3, caption: '<b>AES-256-CBC</b>: AES with a 256-bit key, in CBC mode.' },
{ line: 4, caption: 'Generates a real 256-bit key — 32 genuinely random bytes, right now.', action: function () {
keyBytes = crypto.getRandomValues(new Uint8Array(32));
setRow(keyRow, 'KEY (32 real random bytes)', bufToHex(keyBytes));
} },
{ line: 5, caption: 'Another 16 random bytes — the IV, so the same text never encrypts the same way twice.', action: function () {
ivBytes = crypto.getRandomValues(new Uint8Array(16));
setRow(ivRow, 'IV (16 real random bytes)', bufToHex(ivBytes));
} },
{ line: 8, caption: 'Sets up the cipher with that exact key and IV.', action: function () {
return crypto.subtle.importKey('raw', keyBytes, { name: 'AES-CBC' }, false, ['encrypt', 'decrypt'])
.then(function (k) { cryptoKey = k; });
} },
{ line: 9, caption: 'Encrypts your text — this is real AES-256-CBC running right now, not a stand-in.', action: function () {
plaintext = plaintextInput.value;
plaintextInput.disabled = true;
var bytes = new TextEncoder().encode(plaintext);
return crypto.subtle.encrypt({ name: 'AES-CBC', iv: ivBytes }, cryptoKey, bytes).then(function (buf) {
encryptedBuf = buf;
encryptedHex = bufToHex(buf);
});
} },
{ line: 10, caption: 'The final block is flushed out here, completing the ciphertext.' },
{ line: 11, caption: 'Logs the result.', action: function () {
setRow(encRow, '\uD83D\uDD12 ENCRYPTED', encryptedHex, 'is-lock');
} },
{ line: 14, caption: 'Same algorithm, same key, same IV — set up to reverse the encryption.' },
{ line: 15, caption: 'Decrypts it back — real decryption of the real ciphertext above.', action: function () {
return crypto.subtle.decrypt({ name: 'AES-CBC', iv: ivBytes }, cryptoKey, encryptedBuf).then(function (buf) {
var text = new TextDecoder().decode(buf);
setRow(decRow, '\uD83D\uDD13 DECRYPTED', text, 'is-unlock');
if (text === plaintext) {
verify.textContent = '\u2713 Round-trip verified \u2014 decrypted text exactly matches the input';
verify.classList.add('is-shown');
}
});
} }
];
var stepIndex = 0;
var total = steps.length;
var busy = false;
function reset() {
stepIndex = 0;
busy = false;
keyBytes = ivBytes = cryptoKey = encryptedBuf = encryptedHex = plaintext = undefined;
codeLines.forEach(function (l) { l.classList.remove('is-current'); });
keyRow.className = 'cr-out-row is-empty'; keyRow.textContent = '\u2014 not generated yet \u2014';
ivRow.className = 'cr-out-row is-empty'; ivRow.textContent = '\u2014 not generated yet \u2014';
encRow.className = 'cr-out-row is-empty'; encRow.textContent = '\u2014 not encrypted yet \u2014';
decRow.className = 'cr-out-row is-empty'; decRow.textContent = '\u2014 not decrypted yet \u2014';
caption.innerHTML = 'Press Play to run the whole trace, start to finish.';
verify.classList.remove('is-shown');
verify.textContent = '\u00a0';
stepCount.textContent = 'Step 0 / ' + total;
nextBtn.disabled = false;
nextBtn.textContent = '\u25B6 Play';
plaintextInput.disabled = false;
}
function delay(ms) { return new Promise(function (res) { setTimeout(res, ms); }); }
function runStep(i) {
var s = steps[i];
highlightLine(s.line);
caption.innerHTML = s.caption;
var result = s.action ? s.action() : null;
return Promise.resolve(result);
}
function playAll() {
if (busy) return;
if (!window.crypto || !window.crypto.subtle) {
caption.textContent = 'Web Crypto API not available in this browser.';
return;
}
busy = true;
nextBtn.disabled = true;
nextBtn.textContent = 'Playing\u2026';
var i = 0;
function next() {
if (i >= total) {
busy = false;
nextBtn.disabled = true;
nextBtn.textContent = 'Done';
highlightLine(null);
return;
}
runStep(i).then(function () {
i++;
stepIndex = i;
stepCount.textContent = 'Step ' + stepIndex + ' / ' + total;
return delay(1300);
}).then(next).catch(function (err) {
caption.textContent = 'Error: ' + err.message;
busy = false;
nextBtn.disabled = false;
});
}
next();
}
nextBtn.addEventListener('click', playAll);
resetBtn.addEventListener('click', reset);
reset();
})();
</script>
</body>
</html>keyandiv(initialization vector) are both required to encrypt and decrypt — losing either means the data can't be recovered.The
ivdoesn't need to be secret, but it must be random and unique for every encryption.cipher.update()processes the data, andcipher.final()wraps up the encryption — both parts are needed to get the complete result.In a real app,
keyandivare generated once and stored securely (e.g. in environment variables), not regenerated on every run — otherwise you can't decrypt data encrypted earlier.
💡 Why Use crypto?
🔒 Hashing — checksums, data integrity checks, non-reversible fingerprints of data
🔑 HMAC — verifying a message's sender and that it wasn't altered
🎲 Random values — secure tokens, session IDs, UUIDs
🔐 Encryption — protecting sensitive data (files, messages) that needs to be readable again later
⚠️ For passwords specifically, use a dedicated library like bcrypt instead of crypto directly — it's built to be slow on purpose, which makes brute-forcing much harder.