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
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Sentinel errors let callers distinguish expected failures like bad credentials from unexpected system errors.
- 2Wrapping errors with %w preserves the chain so errors.Is can still match the underlying cause.
- 3Storing bcrypt's cost inside the hash lets you detect and upgrade weak hashes over time.
Related explainers
rust
use serde::Deserialize; #[derive(Debug, Deserialize)] #[serde(untagged)]
Parsing flexible JSON shapes with serde
deserialization
enums
json
Intermediate
6 steps
ruby
require "shellwords" require "open3" module Backup
Building safe shell commands in Ruby
shell-out
subprocess
command-injection
Intermediate
7 steps
go
package streaming import ( "bufio"
Streaming NDJSON logs over HTTP in Go
http-streaming
channels
select
Advanced
10 steps
go
package api import ( "crypto/sha256"
ETag conditional requests in Gin
http-caching
etag
conditional-requests
Intermediate
6 steps
go
func (w *Watcher) resetDebounce(d time.Duration) { if !w.timer.Stop() { select { case <-w.timer.C:
Debouncing a stream of events in Go
debounce
timers
channels
Advanced
7 steps
rust
use chrono::{Duration, NaiveDate}; #[derive(Debug)] pub struct DateRange {
Parsing and iterating date ranges in Rust
error-handling
iterators
parsing
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/hashing-and-verifying-passwords-with-bcrypt-in-go-explained-go-96d2/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.