-
Notifications
You must be signed in to change notification settings - Fork 0
/
argon2.go
103 lines (82 loc) · 2.76 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
package crypto
import (
"crypto/subtle"
"encoding/base64"
"errors"
"fmt"
"strings"
"golang.org/x/crypto/argon2"
)
type ArgonParams struct {
memory uint32
iterations uint32
parallelism uint8
saltLength uint32
keyLength uint32
}
const (
dummyText = "immunkarte-for-the-win-12345"
dummyHash = "$argon2id$v=19$m=65536,t=3,p=2$Woo1mErn1s7AHf96ewQ8Uw$D4TzIwGO4XD2buk96qAP+Ed2baMo/KbTRMqXX00wtsU"
)
var (
ErrInvalidHash = errors.New("invalid_argon_hash")
ErrIncompatibleVersion = errors.New("incompatible_argon_version")
DefaultArgonParams = ArgonParams{
memory: 64 * 1024,
iterations: 3,
parallelism: 2,
saltLength: 16,
keyLength: 32,
}
)
func ArgonHash(password string, p ArgonParams) (string, error) {
n, err := RandomBytes(uint(p.saltLength))
if err != nil {
return "", fmt.Errorf("failed to get random bytes: %w", err)
}
var (
h = argon2.IDKey([]byte(password), n, p.iterations, p.memory, p.parallelism, p.keyLength)
b64Salt = base64.RawStdEncoding.EncodeToString(n)
b64Hash = base64.RawStdEncoding.EncodeToString(h)
)
return fmt.Sprintf("$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s", argon2.Version, p.memory, p.iterations, p.parallelism, b64Salt, b64Hash), nil
}
func ArgonCompareHash(password, encodedPassword string) (bool, error) {
p, salt, hash, err := decodeHash(encodedPassword)
if err != nil {
return false, fmt.Errorf("failed to decode argon2 hash: %w", err)
}
otherHash := argon2.IDKey([]byte(password), salt, p.iterations, p.memory, p.parallelism, p.keyLength)
return subtle.ConstantTimeCompare(hash, otherHash) == 1, nil
}
func ArgonDummyCompare() {
_, _ = ArgonCompareHash(dummyText, dummyHash)
}
func decodeHash(encodedHash string) (params ArgonParams, salt, hash []byte, err error) {
vals := strings.Split(encodedHash, "$")
if len(vals) != 6 {
return ArgonParams{}, nil, nil, ErrInvalidHash
}
var version int
if _, err = fmt.Sscanf(vals[2], "v=%d", &version); err != nil {
return ArgonParams{}, nil, nil, fmt.Errorf("failed to scan argon2 version: %w", err)
}
if version != argon2.Version {
return ArgonParams{}, nil, nil, ErrIncompatibleVersion
}
var p ArgonParams
if _, err = fmt.Sscanf(vals[3], "m=%d,t=%d,p=%d", &p.memory, &p.iterations, &p.parallelism); err != nil {
return ArgonParams{}, nil, nil, fmt.Errorf("failed to scan argon2 params: %w", err)
}
salt, err = base64.RawStdEncoding.Strict().DecodeString(vals[4])
if err != nil {
return ArgonParams{}, nil, nil, fmt.Errorf("failed to decode argon2 salt: %w", err)
}
p.saltLength = uint32(len(salt))
hash, err = base64.RawStdEncoding.Strict().DecodeString(vals[5])
if err != nil {
return ArgonParams{}, nil, nil, fmt.Errorf("failed to decode argon2 hash: %w", err)
}
p.keyLength = uint32(len(hash))
return p, salt, hash, nil
}