forked from alexedwards/argon2id
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathargon2id.go
160 lines (134 loc) · 5.1 KB
/
argon2id.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
// Package argon2id provides a convience wrapper around Go's golang.org/x/crypto/argon2
// implementation, making it simpler to securely hash and verify passwords
// using Argon2.
//
// It enforces use of the Argon2id algorithm variant and cryptographically-secure
// random salts.
package argon2id
import (
"crypto/rand"
"crypto/subtle"
"encoding/base64"
"errors"
"fmt"
"strings"
"golang.org/x/crypto/argon2"
)
var (
// ErrInvalidHash in returned by ComparePasswordAndHash if the provided
// hash isn't in the expected format.
ErrInvalidHash = errors.New("argon2id: hash is not in the correct format")
// ErrIncompatibleVersion in returned by ComparePasswordAndHash if the
// provided hash was created using a different version of Argon2.
ErrIncompatibleVersion = errors.New("argon2id: incompatible version of argon2")
)
// DefaultParams provides some sane default parameters for hashing passwords.
// You are encouraged to change the Memory, Iterations and Parallelism parameters
// to values appropraite for the environment that your code will be running in.
var DefaultParams = &Params{
Memory: 64 * 1024,
Iterations: 3,
Parallelism: 2,
SaltLength: 16,
KeyLength: 32,
}
// Params describes the input parameters used by the Argon2id algorithm. The
// Memory and Iterations parameters control the computational cost of hashing
// the password. The higher these figures are, the greater the cost of generating
// the hash and the longer the runtime. It also follows that the greater the cost
// will be for any attacker trying to guess the password. If the code is running
// on a machine with multiple cores, then you can decrease the runtime without
// reducing the cost by increasing the Parallelism parameter. This controls the
// number of threads that the work is spread across. Important note: Changing the
// value of the Parallelism parameter changes the hash output.
//
// For guidance and an outline process for choosing appropriate parameters see
// https://tools.ietf.org/html/draft-irtf-cfrg-argon2-04#section-4
type Params struct {
// The amount of memory used by the algorithm (in kibibytes).
Memory uint32
// The number of iterations over the memory.
Iterations uint32
// The number of threads (or lanes) used by the algorithm.
Parallelism uint8
// Length of the random salt. 16 bytes is recommended for password hashing.
SaltLength uint32
// Length of the generated key. 16 bytes or more is recommended.
KeyLength uint32
}
// CreateHash returns a Argon2id hash of a plain-text password using the
// provided algorithm parameters. The returned hash follows the format used by
// the Argon2 reference C implementation and contains the base64-encoded Argon2id d
// derived key prefixed by the salt and parameters. It looks like this:
//
// $argon2id$v=19$m=65536,t=3,p=2$c29tZXNhbHQ$RdescudvJCsgt3ub+b+dWRWJTmaaJObG
//
func CreateHash(password string, params *Params) (hash string, err error) {
salt, err := generateRandomBytes(params.SaltLength)
if err != nil {
return "", err
}
key := argon2.IDKey([]byte(password), salt, params.Iterations, params.Memory, params.Parallelism, params.KeyLength)
b64Salt := base64.RawStdEncoding.EncodeToString(salt)
b64Key := base64.RawStdEncoding.EncodeToString(key)
hash = fmt.Sprintf("$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s", argon2.Version, params.Memory, params.Iterations, params.Parallelism, b64Salt, b64Key)
return hash, nil
}
// ComparePasswordAndHash performs a constant-time comparison between a
// plain-text password and Argon2id hash, using the parameters and salt
// contained in the hash. It returns true if they match, otherwise it returns
// false.
func ComparePasswordAndHash(password, hash string) (match bool, err error) {
params, salt, key, err := decodeHash(hash)
if err != nil {
return false, err
}
otherKey := argon2.IDKey([]byte(password), salt, params.Iterations, params.Memory, params.Parallelism, params.KeyLength)
keyLen := int32(len(key))
otherKeyLen := int32(len(otherKey))
if subtle.ConstantTimeEq(keyLen, otherKeyLen) == 0 {
return false, nil
}
if subtle.ConstantTimeCompare(key, otherKey) == 1 {
return true, nil
}
return false, nil
}
func generateRandomBytes(n uint32) ([]byte, error) {
b := make([]byte, n)
_, err := rand.Read(b)
if err != nil {
return nil, err
}
return b, nil
}
func decodeHash(hash string) (params *Params, salt, key []byte, err error) {
vals := strings.Split(hash, "$")
if len(vals) != 6 {
return nil, nil, nil, ErrInvalidHash
}
var version int
_, err = fmt.Sscanf(vals[2], "v=%d", &version)
if err != nil {
return nil, nil, nil, err
}
if version != argon2.Version {
return nil, nil, nil, ErrIncompatibleVersion
}
params = &Params{}
_, err = fmt.Sscanf(vals[3], "m=%d,t=%d,p=%d", ¶ms.Memory, ¶ms.Iterations, ¶ms.Parallelism)
if err != nil {
return nil, nil, nil, err
}
salt, err = base64.RawStdEncoding.DecodeString(vals[4])
if err != nil {
return nil, nil, nil, err
}
params.SaltLength = uint32(len(salt))
key, err = base64.RawStdEncoding.DecodeString(vals[5])
if err != nil {
return nil, nil, nil, err
}
params.KeyLength = uint32(len(key))
return params, salt, key, nil
}