crypto.scrypt (parole) în Node.js

Transformă o parolă într-o cheie greu de spart, cu sare aleatorie: modul corect de a salva parolele cu modulele Node.js.

Sintaxă
import { scrypt, scryptSync, randomBytes, timingSafeEqual } from "node:crypto";
scrypt(parolă, sare, lungimeCheie, [opțiuni], callback)
scryptSync(parolă, sare, lungimeCheie, [opțiuni])

Ce face crypto.scrypt (parole)

Parolele nu se salvează niciodată ca text și nici ca hash simplu (SHA-256), pentru că acestea se calculează foarte repede și pot fi încercate miliarde de variante pe secundă. scrypt este o funcție gândită să fie lentă și să consume memorie, ceea ce face ghicirea mult mai scumpă.

Pentru fiecare utilizator generezi o sare aleatorie (randomBytes(16)) și o salvezi lângă rezultat. Sarea face ca doi utilizatori cu aceeași parolă să aibă rezultate diferite. La autentificare, calculezi din nou scrypt cu parola introdusă și sarea salvată și compari cu timingSafeEqual.

Costul se reglează prin opțiunile N (implicit 16384, putere a lui 2), r și p. Varianta scryptSync blochează firul principal pe durata calculului; într-un server folosește varianta asincronă (cu util.promisify).

Parametri

Parametru Descriere
parolă Parola, ca șir sau Buffer.
sare Valoare aleatorie unică per utilizator, de cel puțin 16 octeți.
lungimeCheie Câți octeți să aibă rezultatul (de obicei 32 sau 64).
opțiuni.N, r, p Costul de calcul și de memorie.

Ce returnează

Un Buffer cu cheia derivată (prin callback sau, la scryptSync, direct).

Exemple

Exemplul 1: Salvare și verificare parolă

La înregistrare, sarea se generează cu randomBytes(16). Aici este fixă, ca rezultatul să fie același la fiecare rulare.

import { scrypt, timingSafeEqual } from "node:crypto";
import { promisify } from "node:util";

const scryptAsync = promisify(scrypt);

async function hashParola(parola, sare) {
  const cheie = await scryptAsync(parola, sare, 32);
  return `${sare.toString("hex")}:${cheie.toString("hex")}`;
}

async function verificaParola(parola, salvat) {
  const [sareHex, cheieHex] = salvat.split(":");
  const cheie = await scryptAsync(parola, Buffer.from(sareHex, "hex"), 32);
  return timingSafeEqual(cheie, Buffer.from(cheieHex, "hex"));
}

const sare = Buffer.from("a1b2c3d4e5f60718293a4b5c6d7e8f90", "hex"); // în realitate: randomBytes(16)
const salvat = await hashParola("Iasi2026!", sare);
console.log(salvat);
console.log(await verificaParola("Iasi2026!", salvat));
console.log(await verificaParola("iasi2026!", salvat));

Rezultat

a1b2c3d4e5f60718293a4b5c6d7e8f90:29a9d7695fef23bbb8bfa69d19478e6f57497fc5a12bbdd3588dc4b3c8495986
true
false

Exemplul 2: Aceeași parolă, sare diferită

import { scryptSync } from "node:crypto";

const a = scryptSync("parola123", "sare-ana", 16).toString("hex");
const b = scryptSync("parola123", "sare-ion", 16).toString("hex");
console.log(a);
console.log(b);
console.log("identice:", a === b);

Rezultat

73916c0d937b659443e8d9738adcaa07
e05a4cd0d5bd62778a3a27f4b2340981
identice: false

Greșeli frecvente

  • Folosești aceeași sare pentru toți utilizatorii (sau deloc): parolele identice dau rezultate identice, iar un atacator le sparge pe toate odată.
  • Compari cheile cu === pe textul hex: folosește timingSafeEqual pe Buffer-e de aceeași lungime.
  • Folosești scryptSync la fiecare cerere de autentificare dintr-un server: blochează serverul zeci de milisecunde de fiecare dată.

Comenzi înrudite

  • crypto.randomBytes / randomUUID / randomInt: Valori aleatorii sigure pentru criptografie: octeți pentru chei și token-uri, UUID-uri, numere întregi într-un interval.
  • crypto.createHash / createHmac: Calculează amprenta (hash) unui text sau fișier cu SHA-256 și semnături HMAC pentru verificarea mesajelor.
  • util.promisify: Transformă o funcție cu callback în stil Node.js (eroare, rezultat) într-o funcție care întoarce o promisiune.