Newer
Older
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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
package pwhash
import (
"crypto/subtle"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"log"
"strconv"
"strings"
"golang.org/x/crypto/argon2"
)
var (
argonKeyLen uint32 = 32
argonSaltLen = 16
)
// Argon2PasswordHash uses the Argon2 hashing algorithm.
type argon2PasswordHash struct {
// Encoding parameters.
params argon2Params
// Codec for string encoding.
codec argon2Codec
}
// newArgon2PasswordHash returns an Argon2i-based PasswordHash using the
// specified parameters for time, memory, and number of threads.
func newArgon2PasswordHash(time, mem uint32, threads uint8, codec argon2Codec) PasswordHash {
return &argon2PasswordHash{
params: argon2Params{
Time: time,
Memory: mem,
Threads: threads,
},
codec: codec,
}
}
// NewArgon2 returns an Argon2i-based PasswordHash using the default parameters.
func NewArgon2() PasswordHash {
return NewArgon2WithParams(
defaultArgon2Params.Time,
defaultArgon2Params.Memory,
defaultArgon2Params.Threads,
)
}
// NewArgon2WithParams returns an Argon2i-based PasswordHash using the
// specified parameters for time, memory, and number of threads.
func NewArgon2WithParams(time, mem uint32, threads uint8) PasswordHash {
return newArgon2PasswordHash(time, mem, threads, &a2Codec{})
}
// NewArgon2Std returns an Argon2i-based PasswordHash that conforms
// to the reference implementation encoding, using default parameters.
func NewArgon2Std() PasswordHash {
return NewArgon2StdWithParams(
defaultArgon2Params.Time,
defaultArgon2Params.Memory,
defaultArgon2Params.Threads,
)
}
// NewArgon2StdWithParams returns an Argon2i-based PasswordHash using
// the specified parameters for time, memory, and number of
// threads. This will use the string encoding ("$argon2$") documented
// in the argon2 reference implementation.
func NewArgon2StdWithParams(time, mem uint32, threads uint8) PasswordHash {
return newArgon2PasswordHash(time, mem, threads, &argon2StdCodec{})
}
// ComparePassword returns true if the given password matches the
// encrypted one.
func (s *argon2PasswordHash) ComparePassword(encrypted, password string) bool {
params, salt, dk, err := s.codec.decodeArgon2Hash(encrypted)
if err != nil {
return false
}
dk2 := argon2.Key([]byte(password), salt, params.Time, params.Memory, params.Threads, argonKeyLen)
return subtle.ConstantTimeCompare(dk, dk2) == 1
}
// Encrypt the given password with the Argon2 algorithm.
func (s *argon2PasswordHash) Encrypt(password string) string {
salt := getRandomBytes(argonSaltLen)
dk := argon2.Key([]byte(password), salt, s.params.Time, s.params.Memory, s.params.Threads, argonKeyLen)
return s.codec.encodeArgon2Hash(s.params, salt, dk)
}
type argon2Params struct {
Time uint32
Memory uint32
Threads uint8
}
// Default Argon2 parameters are tuned for a high-traffic
// authentication service (<1ms per operation).
var defaultArgon2Params = argon2Params{
Time: 1,
Memory: 4 * 1024,
Threads: 4,
}
type argon2Codec interface {
encodeArgon2Hash(argon2Params, []byte, []byte) string
decodeArgon2Hash(string) (argon2Params, []byte, []byte, error)
}
type a2Codec struct{}
func (*a2Codec) encodeArgon2Hash(params argon2Params, salt, dk []byte) string {
return fmt.Sprintf("$a2$%d$%d$%d$%x$%x", params.Time, params.Memory, params.Threads, salt, dk)
}
func (*a2Codec) decodeArgon2Hash(s string) (params argon2Params, salt []byte, dk []byte, err error) {
if !strings.HasPrefix(s, "$a2$") {
err = errors.New("not an Argon2 password hash")
return
}
parts := strings.SplitN(s[4:], "$", 5)
if len(parts) != 5 {
err = errors.New("bad encoding")
return
}
var i uint64
if i, err = strconv.ParseUint(parts[0], 10, 32); err != nil {
return
}
params.Time = uint32(i)
if i, err = strconv.ParseUint(parts[1], 10, 32); err != nil {
return
}
params.Memory = uint32(i)
if i, err = strconv.ParseUint(parts[2], 10, 8); err != nil {
return
}
params.Threads = uint8(i)
salt, err = hex.DecodeString(parts[3])
if err != nil {
return
}
dk, err = hex.DecodeString(parts[4])
return
}
type argon2StdCodec struct{}
func (*argon2StdCodec) encodeArgon2Hash(params argon2Params, salt, dk []byte) string {
encSalt := base64.RawStdEncoding.EncodeToString(salt)
encDK := base64.RawStdEncoding.EncodeToString(dk)
return fmt.Sprintf("$argon2i$v=19$m=%d,t=%d,p=%d$%s$%s", params.Memory, params.Time, params.Threads, encSalt, encDK)
}
func parseArgon2HashParams(s string) (params argon2Params, err error) {
params = defaultArgon2Params
parts := strings.Split(s, ",")
for _, ss := range parts {
kv := strings.SplitN(ss, "=", 2)
if len(kv) != 2 {
err = errors.New("bad parameter encoding")
return
}
var i uint64
switch kv[0] {
case "t":
i, err = strconv.ParseUint(kv[1], 10, 32)
params.Time = uint32(i)
case "m":
i, err = strconv.ParseUint(kv[1], 10, 32)
params.Memory = uint32(i)
case "p":
i, err = strconv.ParseUint(kv[1], 10, 8)
params.Threads = uint8(i)
default:
err = errors.New("unknown parameter in hash")
}
if err != nil {
return
}
}
return
}
func (*argon2StdCodec) decodeArgon2Hash(s string) (params argon2Params, salt []byte, dk []byte, err error) {
if !strings.HasPrefix(s, "$argon2i$") {
err = errors.New("not an Argon2 password hash")
return
}
parts := strings.SplitN(s[9:], "$", 4)
if len(parts) != 4 {
err = errors.New("bad encoding")
return
}
if parts[0] != "v=19" {
err = errors.New("bad argon2 hash version")
return
}
params, err = parseArgon2HashParams(parts[1])
if err != nil {
return
}
if salt, err = base64.RawStdEncoding.DecodeString(parts[2]); err != nil {
return
}
dk, err = base64.RawStdEncoding.DecodeString(parts[3])
log.Printf("params: %+v", params)
return
}