Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

node:crypto basics

The node:crypto module handles cryptography: hashes, random values, encryption. The blog needs three things from it: unique ids, random tokens and safe storage of the admin password.

A random id

randomUUID() creates a UUID — a 36-character string that, for all practical purposes, never repeats:

import { randomUUID } from 'node:crypto';
 
const id = randomUUID();
 
console.log(id);        // for example 3b241101-e2bb-4255-8caf-4136c566a962
console.log(id.length); // 36

A UUID is handy as a comment id: it does not have to be computed from existing records.

A random token

randomBytes() returns the given number of random bytes. Turned into a string, they make a token — for example, for a password-reset link:

import { randomBytes } from 'node:crypto';
 
const token = randomBytes(32).toString('hex');
 
console.log(token.length); // 64

Each byte is written as two hexadecimal characters, so 32 bytes give 64 characters. Math.random() is not suitable for this: its values can be predicted.

A hash

A hash is a fixed-length "fingerprint" of data. The same data always gives the same hash, and the original data cannot be recovered from the hash:

import { createHash } from 'node:crypto';
 
const hash = (text) => createHash('sha256').update(text).digest('hex');
 
console.log(hash('Hello, Node.js').length);              // 64
console.log(hash('Hello, Node.js') === hash('Hello, Node.js')); // true
console.log(hash('Hello, Node.js') === hash('Hello, Node.JS')); // false

Hashes are used to check that a file has not changed, or to get a short cache key.

Passwords

For passwords plain sha256 is too fast: trying millions of guesses is easy. You need a slow function with a salt — a random addition unique to each password. The built-in scrypt function fits:

import { randomBytes, scrypt, timingSafeEqual } from 'node:crypto';
import { promisify } from 'node:util';
 
const scryptAsync = promisify(scrypt);
 
async function hashPassword(password) {
  const salt = randomBytes(16).toString('hex');
  const key = await scryptAsync(password, salt, 64);
  return `${salt}:${key.toString('hex')}`;
}
 
async function verifyPassword(password, stored) {
  const [salt, keyHex] = stored.split(':');
  const key = await scryptAsync(password, salt, 64);
  return timingSafeEqual(key, Buffer.from(keyHex, 'hex'));
}
 
const stored = await hashPassword('blog-admin-2025');
 
console.log(await verifyPassword('blog-admin-2025', stored)); // true
console.log(await verifyPassword('wrong', stored));           // false

The database stores the string salt:hash. At login the password is hashed with the same salt and compared. timingSafeEqual() compares in constant time, so the response speed does not reveal how many characters matched.

Common mistake

Storing passwords as plain text or as a simple unsalted hash is not allowed: if the database leaks, they are known at once. Even unsalted sha256 is cracked with ready-made tables of popular passwords. Use scrypt with a salt as above, or a proven library such as bcrypt.

Thanks for reading!

Donate to the site — we work hard for you.