# Hashing

> Hash passwords with Argon2id, verify them, rehash them when settings change, and move users from scrypt or bcrypt as they sign in.

This guide covers password hashing in Tinka. You will learn how to:

- Hash passwords and verify them at sign-in
- Tune the hashing cost, and cap how much memory it uses
- Rehash passwords when your settings change
- Move users from scrypt or bcrypt hashes made by another system
- Add a pepper, and keep tests fast

## Overview

A password hash turns a password into a string that can't be turned back: you store the hash, and at sign-in you check a password against it. Unlike [encryption](encryption.md), hashing is one-way, so a stolen database doesn't reveal your users' passwords. Hashing is deliberately slow and memory-hungry, so guessing passwords by brute force, even on GPUs, costs an attacker more than it's worth.

The `password` package (`github.com/mucolabs/tinka/password`) hashes with Argon2id, the winner of the Password Hashing Competition and the algorithm RFC 9106 recommends. Each hash is stored as a PHC string that carries the algorithm and its parameters:

```text
$argon2id$v=19$m=65536,t=3,p=4$Jtxi46WJ26OQ0khsYLLlnw$knXGfuRFsSjXdj88JydPOnUIglvm1S8Wq3nzn7R8vEA
```

## Hash and verify passwords

Create one `Hasher` when the App starts, and share it: it's safe for concurrent use. `Hash` returns the string to store, and `Verify` checks a password against it:

```go title="accounts.go"
package main

import (
	"context"
	"errors"

	"github.com/mucolabs/tinka/password"
)

type Accounts struct {
	Hasher *password.Hasher
	Users  *UserStore
}

func (a Accounts) Register(ctx context.Context, email, plain string) error {
	hashed, err := a.Hasher.Hash(ctx, plain)
	if err != nil {
		return err
	}
	return a.Users.Create(ctx, email, hashed)
}

var ErrWrongPassword = errors.New("wrong email or password")

func (a Accounts) SignIn(ctx context.Context, email, attempt string) (*User, error) {
	user, err := a.Users.FindByEmail(ctx, email)
	if err != nil {
		return nil, err
	}
	ok, err := a.Hasher.Verify(ctx, user.PasswordHash, attempt)
	if err != nil {
		return nil, err
	}
	if !ok {
		return nil, ErrWrongPassword
	}
	return user, nil
}
```

`Verify` returns `false` for a wrong password, and an error only when the stored hash is in a format it can't read (`password.ErrUnsupported`) or the context ended. It compares in constant time, so how long it takes reveals nothing about the password.

## Configure the cost

The zero `Config` uses RFC 9106's second recommended parameters: 64 MiB of memory, 3 passes and 4 lanes, about 85 ms per hash on a current server CPU. Raise them as hardware gets faster:

::::options

:::option{name="Memory" type="uint32"}
Memory each hash uses, in KiB. Defaults to `65536` (64 MiB). The most effective parameter against GPU attacks.
:::

:::option{name="Iterations" type="uint32"}
Passes over the memory. Defaults to `3`.
:::

:::option{name="Parallelism" type="uint8"}
Lanes, computed in parallel. Defaults to `4`.
:::

:::option{name="SaltLength, KeyLength" type="uint32"}
Lengths of the random salt and of the hash, in bytes. Default to `16` and `32`.
:::

:::option{name="MaxConcurrent" type="int"}
How many hashes are computed at once. Defaults to `runtime.GOMAXPROCS(0)`.
:::

:::option{name="Pepper" type="env.Secret"}
A secret mixed into every password. See [add a pepper](#add-a-pepper).
:::

::::

Each hash uses `Memory` while it's computed, so a burst of sign-ins could use it many times over. `MaxConcurrent` caps how many run at once: other callers wait their turn, or until their context ends, so a flood of sign-in attempts can't exhaust the server's memory. With the defaults, 8 hashes at once use 512 MiB; size `MaxConcurrent` to the memory you can spare.

## Rehash when settings change

Because a hash carries its parameters, raising them doesn't break existing hashes: `Verify` reads each hash's own parameters. `NeedsRehash` reports whether a hash was made with other settings, or another algorithm. Check it after a successful sign-in, the only time you have the password:

```go title="accounts.go" ins={13-17}
func (a Accounts) SignIn(ctx context.Context, email, attempt string) (*User, error) {
	user, err := a.Users.FindByEmail(ctx, email)
	if err != nil {
		return nil, err
	}
	ok, err := a.Hasher.Verify(ctx, user.PasswordHash, attempt)
	if err != nil {
		return nil, err
	}
	if !ok {
		return nil, ErrWrongPassword
	}
	if a.Hasher.NeedsRehash(user.PasswordHash) {
		if hashed, err := a.Hasher.Hash(ctx, attempt); err == nil {
			_ = a.Users.UpdatePasswordHash(ctx, user.ID, hashed)
		}
	}
	return user, nil
}
```

As users sign in, their hashes move to the current settings.

## Move users from another system

`Verify` also reads the hashes other systems store: scrypt PHC strings (`$scrypt$n=16384,r=8,p=1$…`) and bcrypt hashes (`$2a$`, `$2b$` and `$2y$`), the format most web frameworks use. Import the hashes as they are. A stored hash may cost at most 4 times what the `Hasher`'s own hashes cost (and at least 64 MiB and 10 passes are always allowed), and a bcrypt hash a cost of 15, so a corrupted or planted hash can't stall the server; `Verify` rejects anything costlier with `password.ErrUnsupported`. `NeedsRehash` reports `true` for every one of them, so the rehash above moves each user to Argon2id at their next sign-in, with no password reset.

## Add a pepper

A **pepper** is a secret mixed into every password before hashing. Unlike the salt, it isn't stored with the hash: it lives in the environment, so an attacker who steals the database but not the pepper can't guess passwords at all.

```go title="main.go"
	hasher := password.New(password.Config{
		Pepper: e.Secret("PASSWORD_PEPPER"),
	})
```

:::warning
Adding, changing or losing the pepper makes every existing Argon2id hash fail to verify, locking users out. Set it before the App stores its first password, generate it like `APP_KEY` with `act generate:key --show`, and back it up with your other production secrets.
:::

The pepper applies to the Argon2id hashes the `Hasher` makes; imported scrypt and bcrypt hashes verify without it, and get it when they're rehashed.

## Keep tests fast

Hashing at production cost makes tests that create many users slow. `password.Fake()` returns a `Hasher` with the cheapest parameters Argon2 allows, which hashes in microseconds. Use it in tests only: its hashes crack in seconds.

```go title="accounts_test.go"
func newAccounts(t *testing.T) Accounts {
	return Accounts{Hasher: password.Fake(), Users: newUserStore(t)}
}
```

See also: [Encryption](encryption.md), [Configuration & Environment](../../configuration.md)
