-
-
Notifications
You must be signed in to change notification settings - Fork 478
/
argon2id.go
146 lines (119 loc) · 3.95 KB
/
argon2id.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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package argon2id
import (
"crypto/rand"
"crypto/subtle"
"encoding/base64"
"fmt"
"strings"
"golang.org/x/crypto/argon2"
)
var (
// ErrInvalidHash is returned if the required parameters can not be obtained
// from the hash.
ErrInvalidHash = fmt.Errorf("argon2id: invalid hash format")
// ErrVersionIncompatible is returned if the Argon2id version generating
// the hash does not match the version validating it.
ErrVersionIncompatible = fmt.Errorf("argon2id: incompatible version")
// Prefix is set to be compatible with Dovecot. Can be set to an empty string.
Prefix = "{ARGON2ID}"
)
// DefaultParams provides sane default parameters for password hashing as of
// 2021. Depending on your environment you will need to adjust these.
var DefaultParams = &Params{
Memory: 512 * 1024,
Iterations: 3,
Parallelism: 4,
SaltLen: 32,
KeyLen: 32,
}
// Params contains the input parameters for the Argon2id algorithm. Memory and
// Iterations tweak the computational cost. If you have more cores available
// you can change the parallelism to reduce runtime without reducing cost. But
// note that this will change the hash.
//
// See https://tools.ietf.org/html/draft-irtf-cfrg-argon2-04#section-4
type Params struct {
Memory uint32
Iterations uint32
Parallelism uint8
SaltLen uint32
KeyLen uint32
}
// Generate generates a new Argon2ID hash with recommended values for it's
// complexity parameters. By default the generated hash is compatible with
// the Dovecot Password Scheme.
//
// See https://doc.dovecot.org/configuration_manual/authentication/password_schemes/
//
// It looks like this
//
// {ARGON2ID}$argon2id$v=19$m=524288,t=3,p=4$464unwkIcBGXjqWBZ0A5FWClURgYdWFqRlQaBJOE5fs$5ofdht4OkXsg/tftXGgxNchAdgHzpe+QJyizabiKZFk
//
func Generate(password string, saltLen uint32) (string, error) {
params := DefaultParams
if saltLen > 0 {
params.SaltLen = saltLen
}
salt := make([]byte, params.SaltLen)
_, err := rand.Read(salt)
if err != nil {
return "", err
}
hash := argon2.IDKey([]byte(password), salt, params.Iterations, params.Memory, params.Parallelism, params.KeyLen)
b64hash := base64.RawStdEncoding.EncodeToString(hash)
b64salt := base64.RawStdEncoding.EncodeToString(salt)
return fmt.Sprintf(Prefix+"$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s", argon2.Version, params.Memory, params.Iterations, params.Parallelism, b64salt, b64hash), nil
}
// Validate unpacks the parameters from the hash, computes the hash of the given
// password with these parameters and performs a constant time comparison between
// both hashes.
func Validate(password string, hash string) (bool, error) {
params, salt, key, err := unpackHash(hash)
if err != nil {
return false, err
}
otherKey := argon2.IDKey([]byte(password), salt, params.Iterations, params.Memory, params.Parallelism, params.KeyLen)
keyLen := int32(len(key))
otherKeyLen := int32(len(otherKey))
if subtle.ConstantTimeEq(keyLen, otherKeyLen) == 0 {
return false, nil
}
if subtle.ConstantTimeCompare(key, otherKey) == 1 {
return true, nil
}
return false, nil
}
func unpackHash(hash string) (*Params, []byte, []byte, error) {
hash = strings.TrimPrefix(hash, Prefix)
p := strings.Split(hash, "$")
if len(p) != 6 {
return nil, nil, nil, ErrInvalidHash
}
if p[1] != "argon2id" {
return nil, nil, nil, ErrInvalidHash
}
var version int
_, err := fmt.Sscanf(p[2], "v=%d", &version)
if err != nil {
return nil, nil, nil, err
}
if version != argon2.Version {
return nil, nil, nil, ErrVersionIncompatible
}
params := &Params{}
_, err = fmt.Sscanf(p[3], "m=%d,t=%d,p=%d", ¶ms.Memory, ¶ms.Iterations, ¶ms.Parallelism)
if err != nil {
return nil, nil, nil, err
}
salt, err := base64.RawStdEncoding.DecodeString(p[4])
if err != nil {
return nil, nil, nil, err
}
params.SaltLen = uint32(len(salt))
key, err := base64.RawStdEncoding.DecodeString(p[5])
if err != nil {
return nil, nil, nil, err
}
params.KeyLen = uint32(len(key))
return params, salt, key, nil
}