-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathargon2.go
131 lines (109 loc) · 3.08 KB
/
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package enshamir
import (
"crypto/subtle"
"encoding/base64"
"fmt"
"strings"
"golang.org/x/crypto/argon2"
)
type argon2idParams struct {
// memory 64 * 1024 sets the memory cost to ~64 MB
memory uint32
// The number of iterations over the memory.
times uint32
// the number of threads. It can be set to available cpus by `runtime.NumCPU()`.
thread uint8
saltLength uint32
// AES-256 needs 32-byte key slice
keyLength uint32
}
func (p argon2idParams) String() string {
return fmt.Sprintf("memory: %d, times: %d, thread: %d, saltLength: %d, keyLength: %d",
p.memory,
p.times,
p.thread,
p.saltLength,
p.keyLength,
)
}
var defaultArgon2idParams = argon2idParams{
memory: 2048 * 1024, // ~2GB
times: 4,
thread: 1,
saltLength: 16,
keyLength: 32,
}
// hashPassword hashes the passwords to a 32 bytes slice by argon2id which will be used in AES-256-GCM encryption.
func hashPasswordWithSalt(password, salt []byte) []byte {
return argon2.IDKey(
password,
salt,
defaultArgon2idParams.times,
defaultArgon2idParams.memory,
defaultArgon2idParams.thread,
defaultArgon2idParams.keyLength,
)
}
// verifyPassword verifies the password against the hash. Currently it's only used in unit test.
func verifyPassword(password, hash string) error {
p, s, k, err := decode(hash)
if err != nil {
return fmt.Errorf("unable to decode hash: %w", err)
}
newKey := argon2.IDKey(
[]byte(password),
s,
p.times,
p.memory,
p.thread,
p.keyLength,
)
if subtle.ConstantTimeCompare(k, newKey) == 0 {
return fmt.Errorf("password does not match")
}
return nil
}
// The returned hash will be encoded in the format:
// $argon2id$v=19$m=65536,t=3,p=2$c29tZXNhbHQ$RdescudvJCsgt3ub+b+dWRWJTmaaJObG
func encode(p argon2idParams, salt, key []byte) string {
return fmt.Sprintf("$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s",
argon2.Version,
p.memory,
p.times,
p.thread,
base64.RawStdEncoding.EncodeToString(salt),
base64.RawStdEncoding.EncodeToString(key))
}
func decode(hash string) (argon2idParams, []byte, []byte, error) {
var p argon2idParams
values := strings.Split(hash, "$")
if len(values) != 6 {
return p, nil, nil, fmt.Errorf("invalid hash format")
}
if values[1] != "argon2id" {
return p, nil, nil, fmt.Errorf("incompatible argon2 variant")
}
var version int
_, err := fmt.Sscanf(values[2], "v=%d", &version)
if err != nil {
return p, nil, nil, fmt.Errorf("invalid version: %w", err)
}
if version != argon2.Version {
return p, nil, nil, fmt.Errorf("incompatible argon2 version: %d", version)
}
_, err = fmt.Sscanf(values[3], "m=%d,t=%d,p=%d", &p.memory, &p.times, &p.thread)
if err != nil {
return p, nil, nil, fmt.Errorf("unable to parse argon2 parameters: %w", err)
}
salt, err := base64.RawStdEncoding.DecodeString(values[4])
if err != nil {
return p, nil, nil, fmt.Errorf("unable to decode salt: %w", err)
}
p.saltLength = uint32(len(salt))
key, err := base64.RawStdEncoding.DecodeString(values[5])
if err != nil {
return p, nil, nil, fmt.Errorf("unable to decode key: %w", err)
}
p.keyLength = uint32(len(key))
return p, salt, key, nil
}