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.

Go
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.

Python
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).

Node.js
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.)

.NET
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).

C++
#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.

Java
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".

Rust
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();
}