import * as argon2 from 'argon2';
import * as bcrypt from 'bcrypt';
async function verifyAndMigratePassword(password: string, userHash: string, userId: string): Promise<boolean> {
// 1. Verificăm dacă e deja Argon2id
if (userHash.startsWith('$argon2id$')) {
return argon2.verify(userHash, password);
}
// 2. Fallback pe bcrypt pentru hash-uri vechi
if (userHash.startsWith('$2a$') || userHash.startsWith('$2b$')) {
const isValid = await bcrypt.compare(password, userHash);
if (isValid) {
// 3. Re-hash transparent cu Argon2id și update asincron în DB
const newHash = await argon2.hash(password, {
type: argon2.argon2id,
memoryCost: 65536, // 64 MB
timeCost: 3,
parallelism: 1,
});
await db.user.update({ where: { id: userId }, data: { passwordHash: newHash } });
}
return isValid;
}
return false;
}Am auditat recent un monolit Node.js cu vreo 45k de utilizatori activi și încă hash-uiau parolele cu bcrypt.hash(pass, 10) — practic valoarea default de prin 2012. Pe un RTX 4090 de azi, un cost factor de 10 cade la un atac offline de dicționar mult prea repede. Discuția nu mai e dacă Argon2id e superior (e recomandarea OWASP de ani buni), ci cum facem trecerea în producție fără să resetăm parolele oamenilor și fără să prăjim CPU-ul.
De ce pierde bcrypt teren în fața GPU-urilor
Bcrypt a fost genial la lansare, dar are o limitare arhitecturală: e strict CPU-bound și folosește doar 4 KB de memorie RAM. Asta convenea procesoarelor din 1999, însă pe hardware-ul modern reprezintă o vulnerabilitate. Un atacator cu câteva plăci video dedicate sau un ASIC dedicat poate paraleliza milioane de încercări pe secundă fără să se lovească de memoria video.
Argon2id rezolvă problema asta prin conceptul de memory-hardness. Îl forțezi să aloce zeci de megabytes de RAM pentru un singur calcul. Dacă o placă grafică are 24 GB VRAM și tu ceri 64 MB per hash, atacatorul e limitat fizic la maximum 375 de calcule în paralel pe acel GPU, indiferent câte nuclee CUDA are disponibile. Diferența de cost economic pentru un brute-force devine astronomică.
Parametrii corecți (nu copia orbește de pe StackOverflow)
Am văzut echipe care au pus timeCost: 10 și apoi se mirau de ce crapă containerul la primul val de 50 de login-uri simultane. Regula de aur rămâne aceeași: calculul unui hash ar trebui să dureze între 250ms și 500ms pe mașina de producție, nu pe laptopul tău de dev.
Pentru Argon2id, valorile rezonabile în producție pe un backend tipic sunt:
memoryCost: 65536 KiB (64 MB). Dacă rulezi microservicii pe VPS-uri minuscule de 512 MB RAM, coboară la 32 MB (32768 KiB), dar nu mai jos.timeCost: 2 sau 3 iterații.parallelism: 1 thread (sau câte thread-uri reale aloci per worker Node/Go/Rust).
Dacă infrastructura te forțează să rămâi pe bcrypt (librării C++ legacy sau runtime-uri restrictive), urcă cost factor-ul măcar la 12. La 12 ai aproximativ 250ms latență pe un CPU modern de server. Sub 11 e neglijent în 2026.
Cum faci migrarea la login, fără downtime
Evident, nu poți converti baza de date printr-un simplu script SQL de noapte fiindcă nu ai parolele în clar. Soluția curată este migrarea oportunistă direct în middleware-ul de autentificare.
Când userul trimite formularul de login, logica e simplă: verifici formatul hash-ului existent. Dacă începe cu prefixul $2a$ sau $2b$, verifici parola folosind bcrypt. Dacă parola e validă, generezi instant un hash nou cu Argon2id și suprascrii câmpul din baza de date înainte să întorci token-ul JWT sau sesiunea. Într-o lună sau două, 80-90% din utilizatorii activi vor avea parolele convertite transparent, fără să fi primit niciun mail de reset.
Trade-off-ul sincer? Argon2 mănâncă memorie reală. Dacă nu ai un rate-limiting serios pe endpoint-ul de /login, 200 de request-uri concomitente trimise de un bot îți bagă procesul direct în Out Of Memory (OOM). Bcrypt te proteja mai bine de OOM prin simplitatea lui.
Voi ce folosiți în stack-ul curent — ați făcut tranziția completă sau ați mărit doar cost-ul pe bcrypt?