Crypto recipe
Password hashing with Argon2id
Store passwords safely with Argon2id — the slow, salted, memory-hard standard.
Passwords are low-entropy secrets, so you must store them under a slow, salted, memory-hard function that resists brute force against a stolen database. The modern default is Argon2id; bcrypt and scrypt are acceptable established alternatives.
Each library produces a single self-describing PHC string (it begins with $argon2id$) that bundles the algorithm, parameters, salt, and hash. Store that whole string. Never store a bare hash, and never invent your own salting scheme.
Get it right
- Use Argon2id (or bcrypt / scrypt). Never SHA-256 or MD5 for passwords.
- Store the full encoded PHC string — the salt and parameters live inside it.
- Tune parameters so one hash takes roughly 0.25–0.5s on your hardware.
- Verify with the library's verify function; rehash on login when you raise parameters.
- Never log or cache the plaintext; clear the buffer where the language lets you.
Implementation
Setup go get github.com/alexedwards/argon2id — a thin, PHC-string wrapper over the standard golang.org/x/crypto/argon2.
package main
import (
"fmt"
"github.com/alexedwards/argon2id"
)
func main() {
// At registration. Returns a self-describing PHC string embedding the
// algorithm, parameters, salt, and hash — store this whole string.
hash, err := argon2id.CreateHash("correct horse battery staple", argon2id.DefaultParams)
if err != nil {
panic(err)
}
fmt.Println(hash) // $argon2id$v=19$m=65536,t=1,p=2$...
// At login.
ok, err := argon2id.ComparePasswordAndHash("correct horse battery staple", hash)
if err != nil {
panic(err)
}
fmt.Println("password ok:", ok)
} Setup pip install argon2-cffi.
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
ph = PasswordHasher() # sensible Argon2id defaults
# At registration — store this whole string (it embeds params + salt).
hash = ph.hash("correct horse battery staple")
# At login.
try:
ph.verify(hash, "correct horse battery staple")
if ph.check_needs_rehash(hash):
hash = ph.hash("correct horse battery staple") # params raised; re-store it
except VerifyMismatchError:
print("wrong password") Setup npm install argon2 (native Argon2id; node:crypto ships only scrypt & PBKDF2 built in).
import argon2 from 'argon2';
// At registration — returns a self-describing PHC string ($argon2id$...). argon2id
// is the default. Store this whole string.
const hash = await argon2.hash('correct horse battery staple');
// At login.
const ok = await argon2.verify(hash, 'correct horse battery staple'); If you must avoid a dependency, the built-in crypto.scryptSync is an acceptable scrypt alternative — but Argon2id is preferred.
Setup dotnet add package Isopoh.Cryptography.Argon2. (.NET has no built-in Argon2; ASP.NET Core Identity's PasswordHasher is a framework-native PBKDF2 option.)
using Isopoh.Cryptography.Argon2;
// At registration — Hash returns an encoded PHC string (params + salt embedded).
string hash = Argon2.Hash("correct horse battery staple");
// At login.
bool ok = Argon2.Verify(hash, "correct horse battery staple"); Setup libsodium (crypto_pwhash is Argon2id by default).
#include <sodium.h>
#include <cstring>
int main() {
if (sodium_init() < 0) return 1;
const char *password = "correct horse battery staple";
char hash[crypto_pwhash_STRBYTES]; // holds the encoded PHC string
// At registration. INTERACTIVE limits suit a login form; use _MODERATE or
// _SENSITIVE for higher-value secrets.
if (crypto_pwhash_str(hash, password, std::strlen(password),
crypto_pwhash_OPSLIMIT_INTERACTIVE,
crypto_pwhash_MEMLIMIT_INTERACTIVE) != 0) {
return 1; // out of memory
}
// At login.
bool ok = crypto_pwhash_str_verify(hash, password, std::strlen(password)) == 0;
} Setup de.mkammerer:argon2-jvm (bundles a native Argon2). Spring users can use Spring Security's Argon2PasswordEncoder.
import de.mkammerer.argon2.Argon2;
import de.mkammerer.argon2.Argon2Factory;
Argon2 argon2 = Argon2Factory.create(Argon2Factory.Argon2Types.ARGON2id);
char[] password = "correct horse battery staple".toCharArray();
try {
// iterations, memory (KiB), parallelism — tune to ~0.5s on your hardware.
String hash = argon2.hash(3, 64 * 1024, 2, password); // encoded PHC string
boolean ok = argon2.verify(hash, password);
} finally {
argon2.wipeArray(password); // clear the plaintext from memory
} Setup Cargo.toml: argon2 = "0.5".
use argon2::password_hash::{rand_core::OsRng, PasswordHash, SaltString};
use argon2::{Argon2, PasswordHasher, PasswordVerifier};
fn main() {
let argon2 = Argon2::default(); // Argon2id, sensible defaults
// At registration.
let salt = SaltString::generate(&mut OsRng);
let hash = argon2
.hash_password(b"correct horse battery staple", &salt)
.unwrap()
.to_string(); // PHC string — store this
// At login.
let parsed = PasswordHash::new(&hash).unwrap();
let ok = argon2
.verify_password(b"correct horse battery staple", &parsed)
.is_ok();
}