-
Notifications
You must be signed in to change notification settings - Fork 1
/
argon.go
276 lines (231 loc) · 6.18 KB
/
argon.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
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
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
// +build go1.11
package passwd
import (
"bytes"
"crypto/rand"
"crypto/subtle"
"fmt"
"strconv"
"golang.org/x/crypto/argon2"
)
const (
// Argon2id constant is to select the argon flavor in Argon2Params version field
Argon2id = iota // default
// Argon2i constant is to select argon flavor in Argon2Params version field
Argon2i
)
const (
idArgon2i = "2i"
idArgon2id = "2id"
)
var (
/*
not everything is clear in the draft:
https://datatracker.ietf.org/doc/draft-irtf-cfrg-argon2/?include_text=1
however
9.4. Recommendations
The Argon2id variant with t=1 and maximum available memory is
recommended as a default setting for all environments. This setting
is secure against side-channel attacks and maximizes adversarial
costs on dedicated bruteforce hardware.
we're running with concurrent authentication requests, I cannot suck as much memory,
default safety will be 64MB in cloud environment.
if you're using this for storing password for a more dedicated resource and you wish to put pain
on attackers, go for the paranoid or a custom definition.
the purpose of the package is to help with comparison and hashing + format, nothing more nothing less
*/
argonMinParameters = Argon2Params{
Version: Argon2id,
Time: 1,
Memory: 16 * 1024,
Thread: 8,
Saltlen: 16,
Keylen: 16,
//salt
//masked: false,
}
// XXX need more test/analysis
argonCommonParameters = Argon2Params{
Version: Argon2id,
Time: 1,
Memory: 64 * 1024,
Thread: 16,
Saltlen: 16,
Keylen: 32,
//salt
//masked: false,
}
// XXX need more test/analysis
argonParanoidParameters = Argon2Params{
Version: Argon2id,
Time: 2,
Memory: 512 * 1024,
Thread: 32,
Saltlen: 32,
Keylen: 64,
//salt
//masked: false,
}
)
// Argon2Params are the parameters for the argon2 key derivation.
type Argon2Params struct {
Version int
Time uint32
Memory uint32
Saltlen uint32
Keylen uint32
Thread uint8
Masked bool // are parameters private
salt []byte // on compare only..
}
// [0] password: 'prout' hashed: '$2id$aiOE.rPFUFkkehxc6utWY.$1$65536$8$32$Wv1IMP6xwaqVaQGOX6Oxe.eSEbozeRJLzln8ZlthZfS'
func newArgon2ParamsFromFields(fields []string) (*Argon2Params, error) {
if len(fields) != 6 {
return nil, ErrParse
}
// salt
salt, err := base64Decode([]byte(fields[0])) // process the salt
if err != nil {
return nil, ErrParse
}
saltlen := uint32(len(salt))
// ARGON FIELD: ["mezIC/cmChATxAfFFe9ele" "2" "65536" "8" "32" "omYy81uRZcZv6JkbH17wA0s1CSpH4UQttXBB42oKMXK"]
timeint, err := strconv.ParseInt(fields[1], 10, 32)
if err != nil {
return nil, ErrParse
}
time := uint32(timeint)
memoryint, err := strconv.ParseInt(fields[2], 10, 32)
if err != nil {
return nil, ErrParse
}
memory := uint32(memoryint)
threadint, err := strconv.ParseInt(fields[3], 10, 32)
if err != nil {
return nil, ErrParse
}
thread := uint8(threadint)
keylenint, err := strconv.ParseInt(fields[4], 10, 32)
if err != nil {
return nil, ErrParse
}
keylen := uint32(keylenint)
// we just what we need.
ap := Argon2Params{
Version: Argon2id, // default for now..
Time: time,
Memory: memory,
Thread: thread,
Saltlen: saltlen,
Keylen: keylen,
salt: salt,
}
return &ap, nil
}
// function that validate custom parameters and minimal security is ok.
// will upgrade over the years
// XXX TODO
func (p *Argon2Params) validate(min *Argon2Params) error {
// XXX TODO
return nil
}
func (p *Argon2Params) compare(hashed, password []byte) error {
compared, err := p.generateFromParams(password)
if err != nil {
return ErrMismatch
}
// yes in case things are padded by mistake depending on storage
// whatever.. the params tells us what to verify.
//
// we had a subtle bug where a shorter salt with the same
// password encrypted would still match, as such you could have
// potentially generated thousands of small salted password
// to bruteforce and ran against the comparison function to
// find a collision which requires less power salts HAVE to
// be the same size that's it.
hashlen := uint32(len(compared))
if uint32(len(hashed)) < hashlen || len(p.salt) != int(p.Saltlen) {
return ErrMismatch
}
//fmt.Printf("COMPARE %s vs %s\n", hashed, compared)
if subtle.ConstantTimeCompare(compared, hashed[:hashlen]) == 1 {
return nil
}
return ErrMismatch
}
func (p *Argon2Params) deriveFromPassword(password []byte) (key []byte, err error) {
err = p.validate(&argonMinParameters)
if err != nil {
return nil, err
}
switch p.Version {
case Argon2i:
key = argon2.Key(password, p.salt, p.Time, p.Memory, p.Thread, p.Keylen)
case Argon2id:
fallthrough
default:
key = argon2.IDKey(password, p.salt, p.Time, p.Memory, p.Thread, p.Keylen)
}
return key, nil
}
func (p *Argon2Params) generateFromParams(password []byte) ([]byte, error) {
var key []byte
var id, params string
var hash bytes.Buffer
err := p.validate(&argonMinParameters)
if err != nil {
return nil, err
}
// need to b64.
//salt64 := base64.StdEncoding.EncodeToString(salt)
salt64 := base64Encode(p.salt)
// params
if !p.Masked {
params = fmt.Sprintf("%c%d%c%d%c%d%c%d",
separatorRune, p.Time,
separatorRune, p.Memory,
separatorRune, p.Thread,
separatorRune, p.Keylen)
}
switch p.Version {
case Argon2i:
id = idArgon2i
key = argon2.Key(password, p.salt, p.Time, p.Memory, p.Thread, p.Keylen)
case Argon2id:
fallthrough
default:
id = idArgon2id
key = argon2.IDKey(password, p.salt, p.Time, p.Memory, p.Thread, p.Keylen)
}
// encode the key
key64 := base64Encode(key)
passwordStr := fmt.Sprintf("%c%s%c%s%s%c%s",
separatorRune, id,
separatorRune, salt64,
params,
separatorRune, key64)
_, err = hash.WriteString(passwordStr)
if err != nil {
return nil, err
}
// $ID$b64(SALT)$TIME$MEM$THREAD$KEYLEN$b64(ENCRYPTED)
// ID:
// $2D == ARGON2D
// $2ID == Argon2id
return hash.Bytes(), nil
}
func (p *Argon2Params) getSalt() error {
p.salt = make([]byte, p.Saltlen)
n, err := rand.Read(p.salt)
if err != nil || n != int(p.Saltlen) {
return ErrHash
}
return nil
}
func (p *Argon2Params) generateFromPassword(password []byte) ([]byte, error) {
err := p.getSalt()
if err != nil {
return nil, err
}
return p.generateFromParams(password)
}