go 47 lines · 7 steps

Hashing and verifying passwords with bcrypt in Go

A small auth package that hashes passwords, checks them safely, and knows when a stored hash needs upgrading.

Explained by highlit
1package auth
2 
3import (
4 "errors"
5 "fmt"
6 
7 "golang.org/x/crypto/bcrypt"
8)
9 
10const minPasswordLength = 8
11 
12var (
13 ErrPasswordTooShort = errors.New("password must be at least 8 characters")
14 ErrInvalidCredentials = errors.New("invalid email or password")
15)
16 
17func HashPassword(password string) (string, error) {
18 if len(password) < minPasswordLength {
19 return "", ErrPasswordTooShort
20 }
21 
22 hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
23 if err != nil {
24 return "", fmt.Errorf("hashing password: %w", err)
25 }
26 
27 return string(hash), nil
28}
29 
30func VerifyPassword(hash, password string) error {
31 err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
32 if errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) {
33 return ErrInvalidCredentials
34 }
35 if err != nil {
36 return fmt.Errorf("comparing password: %w", err)
37 }
38 return nil
39}
40 
41func NeedsRehash(hash string) bool {
42 cost, err := bcrypt.Cost([]byte(hash))
43 if err != nil {
44 return true
45 }
46 return cost < bcrypt.DefaultCost
47}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Sentinel errors let callers distinguish expected failures like bad credentials from unexpected system errors.
  2. 2Wrapping errors with %w preserves the chain so errors.Is can still match the underlying cause.
  3. 3Storing bcrypt's cost inside the hash lets you detect and upgrade weak hashes over time.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Hashing and verifying passwords with bcrypt in Go — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code