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
|
package user
import (
"crypto/rand"
"crypto/subtle"
"encoding/base64"
"errors"
"fmt"
"strings"
"golang.org/x/crypto/argon2"
)
var (
errInvalidHash = errors.New("provided hash is wrong format")
errHashesNotEqual = errors.New("secret is not equal to encoded hash")
errIncorrectVersion = errors.New("incorrect version of Argon2")
)
type hashconf struct {
memory uint32
iterations uint32
parallelism uint8
keyLength uint32
saltLength uint32
}
func hashSecret(secret string) (string, error) {
hc := &hashconf{
memory: 64 * 1024,
iterations: 3,
parallelism: 2,
keyLength: 12,
saltLength: 16,
}
salt := make([]byte, hc.saltLength)
_, err := rand.Read(salt)
if err != nil {
return "", err
}
hash := hashArgon2(secret, salt, hc)
b64Salt := base64.RawStdEncoding.EncodeToString(salt)
b64Hash := base64.RawStdEncoding.EncodeToString(hash)
encodedHash := fmt.Sprintf(
"$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s",
argon2.Version,
hc.memory,
hc.iterations,
hc.parallelism,
b64Salt,
b64Hash,
)
return encodedHash, nil
}
func compareSecretToHash(secret, encoded string) (bool, error) {
// decode the encoded hash
hc, salt, comparehash, err := decodeHash(encoded)
if err != nil {
return false, err
}
// encode the secret
verifyhash := hashArgon2(secret, salt, hc)
// compare the hashes using constant time comparison
// to prevent timing attacks. if not equal, then return false
if subtle.ConstantTimeCompare(comparehash, verifyhash) != 1 {
return false, errHashesNotEqual
}
return true, nil
}
func hashArgon2(secret string, salt []byte, hc *hashconf) []byte {
hash := argon2.IDKey(
[]byte(secret),
salt,
hc.iterations,
hc.memory,
hc.parallelism,
hc.keyLength,
)
return hash
}
func decodeHash(encoded string) (hc *hashconf, salt, decodedhash []byte, err error) {
params := strings.Split(encoded, "$")
// check we have enough params
if len(params) != 6 {
return nil, nil, nil, errInvalidHash
}
// check the argon2 version matches
var version int
_, err = fmt.Sscanf(params[2], "v=%d", &version)
if err != nil {
return nil, nil, nil, err
}
if version != argon2.Version {
return nil, nil, nil, errIncorrectVersion
}
// parse hashconf params to be returned
hc = &hashconf{}
_, err = fmt.Sscanf(params[3], "m=%d,t=%d,p=%d", &hc.memory, &hc.iterations, &hc.parallelism)
if err != nil {
return nil, nil, nil, err
}
// decode the salt
salt, err = base64.RawStdEncoding.Strict().DecodeString(params[4])
if err != nil {
return nil, nil, nil, err
}
hc.saltLength = uint32(len(salt))
// decode the hash
decodedhash, err = base64.RawStdEncoding.Strict().DecodeString(params[5])
if err != nil {
return nil, nil, nil, err
}
hc.keyLength = uint32(len(decodedhash))
return hc, salt, decodedhash, nil
}
|