Skip to solution
mediumLow-Level Design

Explain how the 'crypto' module secures sensitive data in Node.js

765 views
01

Understand the problem

Question presented to candidate: "You need to store a customer's API key encrypted at rest, so your own service can decrypt and use it later — this is different from password hashing, where you never need the original back. What does Node's crypto module actually give you for that, and how do you know the encrypted data has not been tampered with?"

What a strong answer should cover:

  • Node's crypto module (built-in, backed by OpenSSL) covers three genuinely distinct concerns: hashing (one-way, for passwords — covered fully in its own dedicated question), reversible encryption/decryption (for data the application genuinely needs to recover later, like the API key in the prompt), and generating cryptographically secure random values (crypto.randomBytes, for tokens, salts, IVs).
  • 📌 Verified, not assumed: a real createCipheriv/createDecipheriv round trip using AES-256-GCM correctly encrypted and decrypted a plaintext string — and, critically, tampering with a single byte of the ciphertext and attempting to decrypt it with the unmodified authentication tag was rejected with a real thrown error, rather than silently returning corrupted or wrong plaintext.
  • 📌 The specific reason that tamper-rejection matters: AES-GCM is an authenticated encryption mode — it produces both ciphertext and an authentication tag, and decryption fails loudly if the ciphertext (or the tag) has been altered. A non-authenticated mode (plain AES-CBC, for instance) would decrypt tampered ciphertext into garbage plaintext with no error at all — a real, meaningful security difference, not a minor implementation detail.
  • Encryption keys and IVs (initialization vectors) must be handled correctly: the key must be kept secret (never hardcoded in source, ideally from a secrets manager or environment variable); a fresh, random IV should be used for every encryption operation with the same key, since reusing an IV can catastrophically weaken many cipher modes' security guarantees.
  • A precise answer distinguishes this reversible-encryption use case from password hashing (scrypt/pbkdf2, covered in its own dedicated question, which is deliberately one-way) and from zlib compression (covered in its own dedicated question, which provides no confidentiality at all) — three genuinely different tools for three genuinely different problems, easily conflated under a vague "keep data secure" framing.
  • crypto.randomBytes/crypto.randomUUID provide cryptographically secure randomness, suitable for security-sensitive values (session tokens, password-reset tokens) — unlike Math.random(), which is not cryptographically secure and must never be used for anything security-sensitive.

Clarifying questions expected:

  • "Does the application genuinely need to recover the original value later, or only verify a guess against it?" — the deciding factor between reversible encryption and one-way hashing.
  • "Is the current implementation using an authenticated cipher mode (GCM), or an older, non-authenticated one?" — a real, meaningful security distinction worth checking explicitly.

Code / implementation expected: Yes — a real AES-256-GCM round trip, including the tamper-rejection demonstration, is the concrete, convincing proof of both correctness and the specific security property authenticated encryption provides.

cryptosecurityhashing
02

Attempt it yourself

Sketch your approach before reading the solution — that's what interviews test.

Nudge consolestandby

Stuck? Beam a request up — the console returns a conceptual nudge that guides your logic without spoiling the implementation.

03

Study the solution

Target Audience: Engineers preparing for Node.js security-focused interviews — assumes basic hashing/encryption vocabulary. Difficulty: Medium

How to read this doc: Concepts are explained in plain language first, then tagged with 📌 Interview term:. Both the successful decryption and the tam

Solution ready — 2 min read

Classified // press E to declassify

04

Read the code

A real AES-256-GCM round trip, verifying both correct decryption and rejection of tampered ciphertext
const crypto = require("crypto");
const key = crypto.randomBytes(32);
const iv = crypto.randomBytes(12);

function encrypt(plaintext) {
  const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
  const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
  return { encrypted, authTag: cipher.getAuthTag() };
}
function decrypt(encrypted, authTag) {
  const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
  decipher.setAuthTag(authTag);
  return Buffer.concat([decipher.update(encrypted), decipher.final()]).toString("utf8");
}

const { encrypted, authTag } = encrypt("sensitive-data-12345");
console.log(decrypt(encrypted, authTag)); // sensitive-data-12345

const tampered = Buffer.from(encrypted);
tampered[0] ^= 0xff; // flip one byte
try {
  decrypt(tampered, authTag);
} catch (e) {
  console.log("tampered ciphertext correctly REJECTED:", e.message);
  // tampered ciphertext correctly REJECTED: Unsupported state or unable to authenticate data
}
05

Join the discussion

Discussion (0)

Sign in to join the discussion.

No responses yet. Be the first to share what you think.

Transmission complete // awaiting log

KEEP THE
STREAK ALIVE.

Dossier 67 of 152 decoded in the Node.js track. One more won't hurt.

Back to track