Reference · Crypto

crypto.hash_password

signature
crypto.hash_password(str) -> str

What it does

A password's hash for storing, made with Argon2id and a fresh random salt: a string of 97 characters beginning $argon2id$, holding the parameters, the salt and the hash, which crypto.verify_password checks a password against later. Hashing takes tens of milliseconds and 19 MiB on purpose, runs off the event loop, and at most one per core at a time, so a flood of logins slows logins down rather than taking the server's memory. It needs the random effect, for the salt.

Examples

let h = crypto.hash_password("hunter2")print(slice(h, 0, 9), len(h))print(crypto.verify_password("hunter2", h))
output
$argon2id 97true

A sign-up page and the route its form posts to. The hash is what goes in the database, never the password; an email already taken fails the unique column, and the visitor gets a 409 with the text:

in a program
db.open("app.db")db.exec("create table if not exists users (id integer primary key, email text unique not null, password_hash text not null)")route GET /signup =    <form method="post" action="/signup">        <input name="email" type="email" required/>        <input name="password" type="password" minlength="10" required/>        <button>Create account</button>    </form>route POST /signup { form: { email: str, password: str } } {    if len(password) < 10 { reply 400 "Passwords are at least 10 characters" }   // minlength can be skipped    let r = db.exec("insert into users (email, password_hash) values (?, ?)", email, crypto.hash_password(password))    if is_err(r) { reply 409 "That email has an account" }    set_session({ user: r.last_id })    redirect("/")}

Signing in again checks the password against the stored hash: see crypto.verify_password.

Edge cases

let a = crypto.hash_password("hunter2")let b = crypto.hash_password("hunter2")print(a == b, crypto.verify_password("hunter2", a), crypto.verify_password("hunter2", b))print(crypto.verify_password("", crypto.hash_password("")))print(split(crypto.hash_password("x"), "$") | slice(0, 4))
output
false true truetrue["", "argon2id", "v=19", "m=19456,t=2,p=1"]

Errors

A password that is not a string (a number, null from a missing form field) is a runtime error, not an error value.

this one fails
print(crypto.hash_password(123))
output
line 1: `crypto.hash_password` expects a str as argument 1, got int

Chaining

The password is its only argument, so it pipes in, and the hash goes on to the database. Compare with verify_password, never with ==.

print("hunter2" | crypto.hash_password | slice(0, 9))let hashes = ["a", "b"] | map(crypto.hash_password)print(crypto.verify_password("b", hashes[1]), crypto.verify_password("b", hashes[0]))
output
$argon2idtrue false

See also

crypto.verify_password, crypto.token, crypto.sha256, set_session

Thread
6Useful? Like it, or add to its thread below.

Thread 0 posts

On crypto.hash_password(): questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.

    Edit this page on GitHub · every example's output was made by running it (tools/refcheck.mjs) · questions on Discord or r/quillo