Argon2 costs memory, not just time.
Bcrypt makes each guess slow. Argon2 makes each guess slow and also makes it allocate real memory, which is the expensive part of building cracking hardware. That is why it won the Password Hashing Competition and why it is the first recommendation for new projects.
Everything here runs locally
Check a password against an Argon2 hash
The encoded string carries its own parameters, so verification reads them back out and needs nothing else from you.
Verification also runs locally
Reading the encoded string
Everything needed to verify sits in the one value, the same way it does with bcrypt. The difference is that Argon2 has three parameters to record instead of one.
$argon2id$v=19$m=19456,t=2,p=1$c29tZXNhbHQ$RdescudvJCsgt3ub+b+dWRWJTmaaJObG
└───┬───┘ └─┬─┘ └──────┬──────┘ └───┬────┘ └───────────────┬──────────────┘
│ │ │ │ digest
│ │ │ salt, base64
│ │ memory in KiB, iterations, lanes
│ the Argon2 version, 19 is 0x13
variantIn your own code
Node.js
import argon2 from 'argon2';
const hash = await argon2.hash(password, {
type: argon2.argon2id,
memoryCost: 19456,
timeCost: 2,
parallelism: 1
});
const ok = await argon2.verify(hash, candidate);PHP
$hash = password_hash($password, PASSWORD_ARGON2ID, [
'memory_cost' => 19456,
'time_cost' => 2,
'threads' => 1,
]);
if (password_verify($candidate, $hash)) { }Python
from argon2 import PasswordHasher
ph = PasswordHasher(memory_cost=19456, time_cost=2, parallelism=1)
hash = ph.hash(password)
ph.verify(hash, candidate)Widen the password column before you deploy any of this. An encoded Argon2id hash runs to about 100 characters, and a column sized for bcrypt at 60 will truncate it without complaining.