-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
argon2.go
168 lines (146 loc) · 4.36 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
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
package password
import (
"bytes"
"encoding/base64"
"strconv"
"strings"
"golang.org/x/crypto/argon2"
)
type Argon2Password struct {
Time uint32
Memory uint32
Parallelism uint8
KeyLen uint32
Salt []byte
plaintext []byte
password []byte
key []byte
useRandomSalt bool
}
// NewArgon2idPlaintext creates a plaintext password that can be hashed with Argon2id.
func NewArgon2idPlaintext(plaintext string, opts ...Argon2PasswordOption) (Plaintext, error) {
p := &Argon2Password{
Time: 2,
Memory: 64 * 1024,
Parallelism: 1,
KeyLen: 32,
Salt: nil, // lazy initialization
plaintext: []byte(plaintext),
password: nil,
key: nil,
useRandomSalt: true,
}
for _, opt := range opts {
opt(p)
}
if p.Salt == nil && p.useRandomSalt {
salt, err := RandomSalt(16)
if err != nil {
return nil, err
}
p.Salt = salt
}
return p, nil
}
// Password generates a password hash.
func (p *Argon2Password) Password() (string, error) {
key := argon2.IDKey(p.plaintext, p.Salt, p.Time, p.Memory, p.Parallelism, p.KeyLen)
parts := []string{
"argon2id", // algo
strconv.Itoa(argon2.Version), // version
strconv.Itoa(int(p.Time)), // time
strconv.Itoa(int(p.Memory)), // memory
strconv.Itoa(int(p.Parallelism)), // parallelism
strconv.Itoa(int(p.KeyLen)), // keylen
base64.RawStdEncoding.EncodeToString(p.Salt), // salt
base64.RawStdEncoding.EncodeToString(key), // key
}
return "$" + strings.Join(parts, "$"), nil
}
// NewArgon2idPassword loads a password hash and can be used to verify a plaintext.
func NewArgon2idPassword(password string) Password {
return &Argon2Password{
password: []byte(password),
}
}
func (p *Argon2Password) lazyInit() error {
if p.key != nil {
return nil
}
password := string(p.password)
if !strings.HasPrefix(password, "$argon2id$") {
return ErrNotArgon2idPassword
}
parts := strings.Split(password, "$")
if len(parts) != 9 {
return ErrMalformedPassword
}
var (
version, _ = strconv.Atoi(parts[2])
time, _ = strconv.Atoi(parts[3])
memory, _ = strconv.Atoi(parts[4])
parallelism, _ = strconv.Atoi(parts[5])
keyLen, _ = strconv.Atoi(parts[6])
salt, _ = base64.RawStdEncoding.DecodeString(parts[7])
key, _ = base64.RawStdEncoding.DecodeString(parts[8])
)
if version != argon2.Version {
return ErrUnsupportedAlgoVersion
}
p.Time = uint32(time)
p.Memory = uint32(memory)
p.Parallelism = uint8(parallelism)
p.KeyLen = uint32(keyLen)
p.Salt = salt
p.key = key
return nil
}
// Verify verifies the plaintext with the password hash.
func (p *Argon2Password) Verify(plaintext string) error {
if err := p.lazyInit(); err != nil {
return err
}
key := argon2.IDKey([]byte(plaintext), p.Salt, p.Time, p.Memory, p.Parallelism, p.KeyLen)
if bytes.Equal(key, p.key) {
return nil
}
return ErrMissmatchedPassword
}
// Argon2PasswordOption is a function that can be used to configure a Argon2Password.
type Argon2PasswordOption func(*Argon2Password)
// Argon2Salt sets the `salt` parameter for Argon2 algorithm.
// Recommended value is a 16 bytes random secret.
func Argon2Salt(salt []byte) Argon2PasswordOption {
return func(p *Argon2Password) {
p.Salt = salt
p.useRandomSalt = false
}
}
// Argon2Time sets the `time` parameter for Argon2 algorithm, which is the number of iterations.
// Recommended value is 2.
func Argon2Time(time uint32) Argon2PasswordOption {
return func(p *Argon2Password) {
p.Time = time
}
}
// Argon2Memory sets the `memory` parameter for Argon2 algorithm, which is the memory cost, in KiB.
// Recommended value is 64 * 1024, i.e. 64 MB.
func Argon2Memory(memory uint32) Argon2PasswordOption {
return func(p *Argon2Password) {
p.Memory = memory
}
}
// Argon2Parallelism sets the `parallelism` parameter for Argon2 algorithm, which is the number of threads.
// Recommended value is 1.
func Argon2Parallelism(parallelism uint8) Argon2PasswordOption {
return func(p *Argon2Password) {
p.Parallelism = parallelism
}
}
// Argon2KeyLen sets the `keylen` parameter for Argon2 algorithm, the desired length of the returned hash.
// Recommended value is 32.
func Argon2KeyLen(keyLen uint32) Argon2PasswordOption {
return func(p *Argon2Password) {
p.KeyLen = keyLen
}
}