We're sorry but this app doesn't work properly without JavaScript enabled. Please enable it to continue.

This lesson's interactive features are locked, please to keep using them

Argon2 – Storing Passwords

KDFs are the best way to store passwords in web applications! As a back-end developer, this is critical to understand.

Can I Store Passwords in Plain Text?

Storing passwords in a database in plain text is a huge security risk. If someone gets access to your database, they can see all of your users' passwords.

Can I Hash Passwords With SHA-256?

No. SHA-256 is a hash function, but it's not a KDF. SHA-256 is very fast. Good KDFs like Argon2 are designed to be slow and memory-hard. That makes large-scale cracking far more expensive for attackers.

Why Argon2 and Not Bcrypt?

Argon2 is a modern memory-hard KDF that's resistant to GPU/ASIC cracking due to tunable memory use. It combines the benefits of Argon2i and Argon2d and is the generally recommended variant for password storage.

Why the Format Is Different from Bcrypt

With bcrypt in Go, the library returns a self-contained string (algorithm + cost + salt + hash).
With Argon2 in Go, you just get the raw derived key. You must also store the salt and the parameters used to derive it.

The PHC string format solves this neatly: it's a standardized, self-describing string that encodes:

  • the algorithm (argon2id)
  • the version (v=19)
  • the parameters (m memory in KiB, t time/iterations, p parallelism)
  • the salt (base64, no padding)
  • the hash (base64, no padding)

Because the parameters are embedded, you can tune them over time and still verify old hashes correctly.

Why Phc?

Go's Argon2 returns only the derived key. The PHC string format is a self-describing container that also includes algorithm, version, parameters, and salt, so you can change parameters later and still verify old hashes.

PHC layout:

$argon2id$v=19$m=<memKiB>,t=<time>,p=<parallelism>$<saltBase64>$<hashBase64>

argon2id: the Argon2 variant to use during verification (must match exactly).

v=19: the Argon2 spec version (decimal 19 = version 1.3). This ensures the verifier applies the correct algorithm rules.

Assignment

At Passly, we store passwords securely (it would be sad if we didn't). Each user has a master password that they use to log into their cloud account. That password is hashed with Argon2id before being stored.

Use the golang.org/x/crypto/argon2 package to complete the hashPassword() and checkPasswordHash() functions. You do not need to modify the function signatures – just implement the Argon2id API, and handle random salt generation.

Use the constants already provided.

Start With the hashPassword Function

salt := make([]byte, 16)
// use crypto/rand.Read(salt)
b64 := base64.RawStdEncoding
$argon2id$v=19$m=32768,t=3,p=4$<saltBase64>$<hashBase64>

Continue With the checkPasswordHash Function

// Expect: ["", "argon2id", "v=19", "m=..,t=..,p=..", "<saltB64>", "<hashB64>"]
parts := strings.Split(hash, "$")
if len(parts) != 6 || parts[1] != "argon2id" || parts[2] != "v=19" { return false }

Parse the parameters

Use strconv.ParseUint to parse the values to the expected types:

  • m and t to uint32
  • p to uint8

Decode the information you have:

b64 := base64.RawStdEncoding
salt, err := b64.DecodeString(parts[4]); if err != nil { return false }
want, err := b64.DecodeString(parts[5]); if err != nil { return false }

Now you need to use IDKey again to recompute the data, and compare:

got := argon2.IDKey([]byte(password), salt, tU32, mU32, pU8, uint32(len(want)))
// Avoid timing leaks:
return subtle.ConstantTimeCompare(got, want) == 1