Reference · Crypto
crypto.hash_password
crypto.hash_password(str) -> strWhat 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))$argon2id 97trueA 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:
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
- The same password hashed twice gives two different strings, because each has its own salt; both verify.
- The empty password hashes and verifies like any other; refuse it yourself where it is not wanted.
- The hash is text, not a secret: it is stored as it is and never needs decoding.
- Over 1,024 hashes waiting their turn, the call is a runtime error (
too many passwords are being checked at once) rather than a longer wait.
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))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.
print(crypto.hash_password(123))line 1: `crypto.hash_password` expects a str as argument 1, got intChaining
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]))$argon2idtrue falseSee also
crypto.verify_password, crypto.token, crypto.sha256, set_session
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