Raw
  1package password
  2
  3import (
  4	"context"
  5	"crypto/rand"
  6	"crypto/subtle"
  7	"encoding/base64"
  8	"errors"
  9	"fmt"
 10	"runtime"
 11	"strconv"
 12	"strings"
 13	"sync"
 14
 15	"golang.org/x/crypto/argon2"
 16)
 17
 18var (
 19	ErrMalformed = errors.New("password: malformed stored hash")
 20	ErrBusy      = errors.New("password: verifier busy")
 21)
 22
 23var errEmptyPassword = errors.New("password: empty password refused")
 24
 25const (
 26	defaultMemory  = 19456
 27	defaultTime    = 2
 28	defaultThreads = 1
 29	keyLength      = 32
 30	saltLength     = 16
 31
 32	minMemory  = 8192
 33	maxMemory  = 65536
 34	minTime    = 1
 35	maxTime    = 16
 36	minThreads = 1
 37	maxThreads = 16
 38	minBytes   = 16
 39	maxBytes   = 64
 40	maxDigits  = 7
 41)
 42
 43const variantField = "argon2id"
 44
 45var versionField = "v=" + strconv.Itoa(argon2.Version)
 46
 47var b64 = base64.RawStdEncoding.Strict()
 48
 49var kdf = argon2.IDKey
 50
 51var sem = make(chan struct{}, semaphoreSize(runtime.GOMAXPROCS(0)))
 52
 53var dummy = sync.OnceValue(newDummy)
 54
 55type phc struct {
 56	memory  uint32
 57	time    uint32
 58	threads uint8
 59	salt    []byte
 60	key     []byte
 61}
 62
 63func semaphoreSize(gomaxprocs int) int {
 64	return min(gomaxprocs, 3)
 65}
 66
 67func Hash(password string) ([]byte, error) {
 68	if password == "" {
 69		return nil, errEmptyPassword
 70	}
 71	salt := make([]byte, saltLength)
 72	rand.Read(salt)
 73	key := kdf([]byte(password), salt, defaultTime, defaultMemory, defaultThreads, keyLength)
 74	return encode(phc{memory: defaultMemory, time: defaultTime, threads: defaultThreads, salt: salt, key: key}), nil
 75}
 76
 77func HashContext(ctx context.Context, password string) ([]byte, error) {
 78	if password == "" {
 79		return nil, errEmptyPassword
 80	}
 81	if err := acquire(ctx); err != nil {
 82		return nil, err
 83	}
 84	defer release()
 85	return Hash(password)
 86}
 87
 88func Verify(ctx context.Context, stored []byte, password string) (bool, error) {
 89	h, ok := decode(stored)
 90	if !ok {
 91		return false, ErrMalformed
 92	}
 93	if err := acquire(ctx); err != nil {
 94		return false, err
 95	}
 96	defer release()
 97	got := kdf([]byte(password), h.salt, h.time, h.memory, h.threads, uint32(len(h.key)))
 98	return subtle.ConstantTimeCompare(got, h.key) == 1, nil
 99}
100
101func VerifyAbsent(ctx context.Context, password string) error {
102	_, err := Verify(ctx, dummy(), password)
103	return err
104}
105
106func Unusable() []byte {
107	return newDummy()
108}
109
110func newDummy() []byte {
111	salt := make([]byte, saltLength)
112	key := make([]byte, keyLength)
113	rand.Read(salt)
114	rand.Read(key)
115	return encode(phc{memory: defaultMemory, time: defaultTime, threads: defaultThreads, salt: salt, key: key})
116}
117
118func acquire(ctx context.Context) error {
119	if ctx.Err() != nil {
120		busy.Add(1)
121		return ErrBusy
122	}
123	select {
124	case sem <- struct{}{}:
125		return nil
126	default:
127	}
128	waits.Add(1)
129	select {
130	case sem <- struct{}{}:
131		return nil
132	case <-ctx.Done():
133		busy.Add(1)
134		return ErrBusy
135	}
136}
137
138func release() {
139	<-sem
140}
141
142func encode(h phc) []byte {
143	return fmt.Appendf(nil, "$%s$%s$m=%d,t=%d,p=%d$%s$%s", variantField, versionField,
144		h.memory, h.time, h.threads, b64.EncodeToString(h.salt), b64.EncodeToString(h.key))
145}
146
147func decode(stored []byte) (phc, bool) {
148	fields := strings.Split(string(stored), "$")
149	if len(fields) != 6 || fields[0] != "" || fields[1] != variantField || fields[2] != versionField {
150		return phc{}, false
151	}
152	params := strings.Split(fields[3], ",")
153	if len(params) != 3 {
154		return phc{}, false
155	}
156	m, okM := parseParam(params[0], "m=")
157	t, okT := parseParam(params[1], "t=")
158	p, okP := parseParam(params[2], "p=")
159	if !okM || !okT || !okP {
160		return phc{}, false
161	}
162	if m < minMemory || m > maxMemory || t < minTime || t > maxTime || p < minThreads || p > maxThreads {
163		return phc{}, false
164	}
165	salt, okS := decodeSegment(fields[4])
166	key, okK := decodeSegment(fields[5])
167	if !okS || !okK {
168		return phc{}, false
169	}
170	return phc{memory: uint32(m), time: uint32(t), threads: uint8(p), salt: salt, key: key}, true
171}
172
173func parseParam(field, prefix string) (uint64, bool) {
174	digits, ok := strings.CutPrefix(field, prefix)
175	if !ok || digits == "" || len(digits) > maxDigits || (len(digits) > 1 && digits[0] == '0') {
176		return 0, false
177	}
178	for i := 0; i < len(digits); i++ {
179		if digits[i] < '0' || digits[i] > '9' {
180			return 0, false
181		}
182	}
183	v, err := strconv.ParseUint(digits, 10, 32)
184	return v, err == nil
185}
186
187func decodeSegment(segment string) ([]byte, bool) {
188	raw, err := b64.DecodeString(segment)
189	if err != nil || len(raw) < minBytes || len(raw) > maxBytes || b64.EncodeToString(raw) != segment {
190		return nil, false
191	}
192	return raw, true
193}