import bcrypt from 'bcrypt';
import argon2 from 'argon2';
async function verifyAndUpgradePassword(
plainText: string,
storedHash: string,
userId: string
): Promise<boolean> {
// Verificăm dacă hash-ul existent este deja Argon2id
if (storedHash.startsWith('$argon2id$')) {
return await argon2.verify(storedHash, plainText);
}
// Dacă este un hash vechi bcrypt ($2a$, $2b$, $2y$)
if (storedHash.startsWith('$2')) {
const isValid = await bcrypt.compare(plainText, storedHash);
if (isValid) {
// Re-hash automat cu Argon2id și salvare în fundal
const newHash = await argon2.hash(plainText, {
type: argon2.argon2id,
memoryCost: 65536, // 64 MB
timeCost: 3,
parallelism: 4
});
await db.user.update({
where: { id: userId },
data: { passwordHash: newHash }
});
}
return isValid;
}
return false;
}Am dat recent peste un microserviciu de auth scos din producție în 2020 și repornit acum. Avea bcrypt cu cost factor 10, iar la un test rapid de securitate, pe un GPU modern am scos cifre de-a dreptul înfricoșătoare la rata de hash-uri per secundă. Dacă încă ai bcrypt cu cost factor sub 12 în producție, ai o problemă reală de securitate pe care trebuie să o rezolvi azi.
De ce pierde bcrypt teren în fața Argon2id
Bcrypt e din 1999 și a făcut o treabă excelentă. Problema e că e un algoritm limitat din construcție la o amprentă de memorie minusculă (doar 4 KB). Pe vremea aia era suficient, dar plăcile video moderne au gigabiți întregi de VRAM extrem de rapid. Un atacator poate rula mii de fire de execuție în paralel pe un GPU fără să fie limitat de memorie.
Aici intervine Argon2id (câștigătorul Password Hashing Competition). El este memory-hard. Asta înseamnă că îl poți configura să consume, de exemplu, 64 MB de RAM pentru un singur hash. Când un GPU încearcă să facă atac cu forță brută, rămâne rapid fără memorie și rata de încercări se prăbușește drastic.
Trade-off-ul? Argon2id consumă resurse pe bune și pe serverul tău. La un proiect de-al meu cu 50k utilizatori activi lunar, un spike de autentificări la 9 dimineața a ridicat consumul de RAM al containerului de Node.js cu aproape 1.5 GB. Trebuie să calculezi atent capacitatea procesorului și a memoriei înainte să arunci parametrii din ghiduri pe mediul de producție.
Ce parametri alegi concret
Dacă rămâi deocamdată pe bcrypt, cost factor-ul minim în 2026 este 12 sau 13. La factor 10 obții un timp de calcul de sub 50ms, ceea ce e mult prea ieftin pentru atacatori. La factor 12 ajungi undeva la 250-300ms pe un vCPU standard de cloud.
Pentru Argon2id, o configurație echilibrată cu care am avut rezultate bune pe servere cu 2 vCPU este:
- Memory: 64 MB (
65536KB) - Time (iterations): 3
- Parallelism: 4
Scopul este ca operațiunea de hashing să dureze în jur de 300ms. Orice e sub 100ms e prea slab, iar ce depășește 800ms va frustra utilizatorii și te expune la atacuri de tip DoS pe ruta de login.
Migrarea treptată (lazy migration)
Nu poți converti toate parolele din baza de date dintr-o dată pentru că nu le cunoști în clar. Strategia pe care am aplicat-o cu succes la ultimele două migrări este verificarea dinamică la login.
Când un user bagă parola, verifici mai întâi ce tip de hash ai în DB (se vede ușor după prefix: $2b$ e bcrypt, $argon2id$ e Argon2). Dacă e hash vechi, validezi cu bcrypt. Dacă autentificarea reușește, generezi pe loc un hash nou cu Argon2id și actualizezi rândul din baza de date. În 3-6 luni, 80-90% din userii activi vor avea hash-ul nou, fără ca cineva să fi primit vreun mail deranjant cu „resetează-ți parola”.
Voi ce timpi aveți în prezent pe endpoint-ul de login și ce soluție folosiți pentru protecția împotriva atacurilor DoS pe hashing?