-
-
Notifications
You must be signed in to change notification settings - Fork 106
/
hasher_argon2.go
107 lines (87 loc) · 3.08 KB
/
hasher_argon2.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
package hasherx
import (
"bytes"
"context"
"crypto/rand"
"encoding/base64"
"fmt"
"time"
"github.com/inhies/go-bytesize"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"github.com/pkg/errors"
"golang.org/x/crypto/argon2"
)
var (
ErrInvalidHash = errors.New("the encoded hash is not in the correct format")
ErrIncompatibleVersion = errors.New("incompatible version of argon2")
ErrMismatchedHashAndPassword = errors.New("passwords do not match")
)
type (
// Argon2Config is the configuration for a Argon2 hasher.
Argon2Config struct {
// Memory is the amount of memory to use.
Memory bytesize.ByteSize `json:"memory"`
// Iterations is the number of iterations to use.
Iterations uint32 `json:"iterations"`
// Parallelism is the number of threads to use.
Parallelism uint8 `json:"parallelism"`
// SaltLength is the length of the salt to use.
SaltLength uint32 `json:"salt_length"`
// KeyLength is the length of the key to use.
KeyLength uint32 `json:"key_length"`
// ExpectedDuration is the expected duration of the hash.
ExpectedDuration time.Duration `json:"expected_duration"`
// ExpectedDeviation is the expected deviation of the hash.
ExpectedDeviation time.Duration `json:"expected_deviation"`
// DedicatedMemory is the amount of dedicated memory to use.
DedicatedMemory bytesize.ByteSize `json:"dedicated_memory"`
}
// Argon2 is a hasher that uses the Argon2 algorithm.
Argon2 struct {
c Argon2Configurator
}
// Argon2Configurator is a function that returns the Argon2 configuration.
Argon2Configurator interface {
HasherArgon2Config(ctx context.Context) *Argon2Config
}
)
func NewHasherArgon2(c Argon2Configurator) *Argon2 {
return &Argon2{c: c}
}
func toKB(mem bytesize.ByteSize) uint32 {
return uint32(mem / bytesize.KB)
}
// Generate generates a hash for the given password.
func (h *Argon2) Generate(ctx context.Context, password []byte) ([]byte, error) {
ctx, span := otel.GetTracerProvider().Tracer(tracingComponent).Start(ctx, "hash.Argon2.Generate")
defer span.End()
p := h.c.HasherArgon2Config(ctx)
span.SetAttributes(attribute.String("argon2.config", fmt.Sprintf("#%v", p)))
salt := make([]byte, p.SaltLength)
if _, err := rand.Read(salt); err != nil {
return nil, err
}
// Pass the plaintext password, salt and parameters to the argon2.IDKey
// function. This will generate a hash of the password using the Argon2id
// variant.
hash := argon2.IDKey([]byte(password), salt, p.Iterations, toKB(p.Memory), p.Parallelism, p.KeyLength)
var b bytes.Buffer
if _, err := fmt.Fprintf(
&b,
"$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s",
argon2.Version, toKB(p.Memory), p.Iterations, p.Parallelism,
base64.RawStdEncoding.EncodeToString(salt),
base64.RawStdEncoding.EncodeToString(hash),
); err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
return nil, errors.WithStack(err)
}
return b.Bytes(), nil
}
// Understands checks if the given hash is in the correct format.
func (h *Argon2) Understands(hash []byte) bool {
return IsArgon2idHash(hash)
}