-
Notifications
You must be signed in to change notification settings - Fork 1
/
pbkdf2.go
161 lines (136 loc) · 5.15 KB
/
pbkdf2.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
// Package pbkdf2 provides a convience wrapper around Go's golang.org/x/crypto/pbkdf2
// implementation, making it simpler to securely hash and verify passwords
// using PBKDF2.
//
// It enforces use of the PBKDF2-HMAC-SHA512 algorithm variant and cryptographically-secure
// random salts.
package pbkdf2
import (
"crypto/rand"
"crypto/sha512"
"crypto/subtle"
"encoding/base64"
"errors"
"fmt"
"strings"
"golang.org/x/crypto/pbkdf2"
)
var (
// ErrInvalidHash in returned by ComparePasswordAndHash if the provided
// hash isn't in the expected format.
ErrInvalidHash = errors.New("pbkdf2: hash is not in the correct format")
// ErrIncompatibleVariant is returned by ComparePasswordAndHash if the
// provided hash was created using a unsupported variant of PBKDF2.
// Currently only PBKDF2-HMAC-SHA512 is supported by this package.
ErrIncompatibleVariant = errors.New("pbkdf2: incompatible variant of pbkdf2")
)
// DefaultParams provides some sane default parameters for hashing passwords.
//
// Follows recommendations given by the NIST.
//
// The default parameters should generally be used for development/testing purposes
// only. Custom parameters should be set for production applications depending on
// available memory/CPU resources and business requirements.
var DefaultParams = &Params{
Iterations: 210000,
SaltLength: 16,
KeyLength: 64,
}
// Params describes the input parameters used by the PBKDF2 algorithm. The
// Iterations parameter controls the computational cost of hashing
// the password. The higher this figure is, 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. Important note:
// Changing the value of the Iterations parameter changes the hash output.
//
// For guidance and an outline process for choosing appropriate parameters see
// https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2
type Params struct {
// The number of iterations.
Iterations uint32
// 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 PBKDF2-HMAC-SHA512 hash of a plain-text password using the
// provided algorithm parameters. The returned hash follows the format:
//
// $pbkdf2-sha512${Iterations}${b64Salt}${b64Key}
//
// It looks like this:
//
// $pbkdf2-sha512$210000$yvu2ZftdlhcP4Tbpe2TYqA$XJsU2xkzTyRZur3/+VW07FljLcgKGfmNw+en6y3WJ0JWHHEkn4e46VcaddErsqc9jkJC5IVl4XSlh4lgv0dlug
func CreateHash(password string, params *Params) (hash string, err error) {
salt, err := generateRandomBytes(params.SaltLength)
if err != nil {
return "", err
}
key := pbkdf2.Key([]byte(password), salt, int(params.Iterations), int(params.KeyLength), sha512.New)
b64Salt := base64.RawStdEncoding.EncodeToString(salt)
b64Key := base64.RawStdEncoding.EncodeToString(key)
hash = fmt.Sprintf("$pbkdf2-sha512$%d$%s$%s", params.Iterations, b64Salt, b64Key)
return hash, nil
}
// ComparePasswordAndHash performs a constant-time comparison between a
// plain-text password and PBKDF2-HMAC-SHA512 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) {
match, _, err = CheckHash(password, hash)
return match, err
}
// CheckHash is like ComparePasswordAndHash, except it also returns the params that the hash was
// created with. This can be useful if you want to update your hash params over time (which you
// should).
func CheckHash(password, hash string) (match bool, params *Params, err error) {
params, salt, key, err := DecodeHash(hash)
if err != nil {
return false, nil, err
}
otherKey := pbkdf2.Key([]byte(password), salt, int(params.Iterations), int(params.KeyLength), sha512.New)
keyLen := int32(len(key))
otherKeyLen := int32(len(otherKey))
if subtle.ConstantTimeEq(keyLen, otherKeyLen) == 0 {
return false, params, nil
}
if subtle.ConstantTimeCompare(key, otherKey) == 1 {
return true, params, nil
}
return false, params, nil
}
func generateRandomBytes(n uint32) ([]byte, error) {
b := make([]byte, n)
_, err := rand.Read(b)
if err != nil {
return nil, err
}
return b, nil
}
// DecodeHash expects a hash created from this package, and parses it to return the params used to
// create it, as well as the salt and key (password hash).
func DecodeHash(hash string) (params *Params, salt, key []byte, err error) {
vals := strings.Split(hash, "$")
if len(vals) != 5 {
return nil, nil, nil, ErrInvalidHash
}
if vals[1] != "pbkdf2-sha512" {
return nil, nil, nil, ErrIncompatibleVariant
}
params = &Params{}
_, err = fmt.Sscanf(vals[2], "%d", ¶ms.Iterations)
if err != nil {
return nil, nil, nil, err
}
salt, err = base64.RawStdEncoding.Strict().DecodeString(vals[3])
if err != nil {
return nil, nil, nil, err
}
params.SaltLength = uint32(len(salt))
key, err = base64.RawStdEncoding.Strict().DecodeString(vals[4])
if err != nil {
return nil, nil, nil, err
}
params.KeyLength = uint32(len(key))
return params, salt, key, nil
}