Documentation
¶
Overview ¶
Package password hashes and verifies passwords with argon2id.
Three decisions are worth knowing about before reading further.
The stored value is a PHC string — the same format argon2's reference tools print — so a hash carries its own salt and cost and can be verified by anything that speaks the format, including a future version of this package that has raised the cost.
The cost is stored alongside it in its own columns, which looks redundant and is not: "which accounts still have a hash below the current cost" has to be a query rather than a scan through a parser, or nobody ever runs it.
And verification reports whether the stored hash is now too cheap, so login can quietly upgrade it. A cost you can raise but never apply to existing accounts is a cost you have not raised.
Index ¶
Constants ¶
const Algorithm = "argon2id"
Algorithm is the only one this package writes. It is stored explicitly so that adding a second one later is a migration rather than an archaeology project.
Variables ¶
var ErrMalformed = errors.New("password: the stored hash is not a valid argon2id string")
ErrMalformed means a stored hash could not be read.
It is distinct from a wrong password on purpose: one is somebody typing, the other is a corrupted row, and treating the second as the first would leave an account permanently unable to log in with no sign of why.
Functions ¶
This section is empty.
Types ¶
type BreachChecker ¶
type BreachChecker interface {
// Count returns the number of times the password appears in breach
// corpora, or zero if it does not appear.
//
// An implementation that cannot reach its source returns an error rather
// than zero. The caller decides what to do about it; a checker that
// silently answers "not breached" when it is offline is worse than none,
// because it looks like it is working.
Count(ctx context.Context, plain string) (int, error)
}
BreachChecker reports how many times a password has appeared in a known breach.
type Credential ¶
type Credential struct {
// Encoded is the PHC string. It is the whole truth: salt, cost, and hash.
Encoded string
// Algorithm and Params duplicate what Encoded already says, so that
// finding every credential due for a rehash is an indexed query rather
// than a scan.
Algorithm string
Params Params
}
Credential is what a row stores.
type HIBP ¶
type HIBP struct {
// Client defaults to one with a short timeout. A password check is on the
// sign-up path, and a hung request there is worse than a skipped check.
Client *http.Client
// BaseURL defaults to the public API. It is settable so a test can serve
// its own answers and an air-gapped deployment can host a mirror.
BaseURL string
}
HIBP checks a password against Have I Been Pwned's range API.
The password never leaves the process. Only the first five hex digits of its SHA-1 are sent; the service answers with every suffix it holds under that prefix — several hundred of them — and the match happens here. That is what makes asking a third party about a password defensible: the third party learns that somebody, somewhere, has a password whose hash starts with those five characters, which is true of millions of people.
SHA-1 is not a choice; it is the format the range API speaks. It is being used as a lookup key against a public corpus, not to protect anything.
type Hasher ¶
type Hasher struct {
// contains filtered or unexported fields
}
Hasher hashes at a fixed cost.
func New ¶
New builds a hasher. Zero-valued parameters fall back to the defaults, so a caller who only wants to raise the memory can say just that.
func (*Hasher) Dummy ¶
Dummy returns a hash to verify against when there is no such account.
Verifying it is how a login for an unknown address spends the same time as one for a known address. Skipping the work turns response time into a membership oracle, and an attacker who can enumerate your users has done most of the work already.
It is derived once, at this hasher's cost, so it stays a fair stand-in when the cost is raised.
func (*Hasher) Hash ¶
func (h *Hasher) Hash(plain string) (Credential, error)
Hash derives a credential from a plaintext password.
type Params ¶
type Params struct {
// Memory is the working set in KiB. It is the parameter that actually
// costs an attacker something: iterations can be parallelized across
// cheap cores, memory cannot.
Memory uint32 `json:"memory"`
// Iterations is how many passes over that memory.
Iterations uint32 `json:"iterations"`
// Parallelism is how many lanes. It should not exceed the cores a request
// may reasonably occupy.
Parallelism uint8 `json:"parallelism"`
// SaltLength and KeyLength are in bytes.
SaltLength uint32 `json:"salt_length"`
KeyLength uint32 `json:"key_length"`
}
Params are argon2id's cost parameters.
func DefaultParams ¶
func DefaultParams() Params
DefaultParams follow the OWASP recommendation for argon2id: 64 MiB, three passes, two lanes.
They cost roughly 50ms on a current server, which is the point — it is slow enough to make an offline guess expensive and fast enough that a login does not feel like one. Raise Memory first if you raise anything.
type Policy ¶
type Policy struct {
// MinLength is in characters, not bytes, so a passphrase in a language
// that needs more than one byte per character is not penalized for it.
MinLength int
// MaxLength bounds the work. It is not about password strength: argon2id
// over a ten-megabyte "password" is a denial of service anyone can send,
// and the limit is what stops it.
MaxLength int
// Breached, when set, rejects passwords known to have leaked. See [HIBP].
Breached BreachChecker
}
Policy is what a new password must satisfy.
There are no composition rules, and that is the recommendation rather than an omission: demanding an uppercase letter, a digit, and a symbol pushes people toward Password1! and a sticky note, which is worse on both counts than a longer passphrase. Length and a breach check are what actually help.
func DefaultPolicy ¶
func DefaultPolicy() Policy
DefaultPolicy is twelve characters, no composition rules, no breach check.
The breach check is off by default because it talks to a third party, and that is a decision an application makes rather than one a library makes for it.