-
Notifications
You must be signed in to change notification settings - Fork 165
/
pbkdf2.go
145 lines (120 loc) · 4.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
package hashing
import (
"bytes"
"crypto/rand"
"crypto/sha256"
"crypto/sha512"
"encoding/base64"
"fmt"
"math/big"
"strconv"
"strings"
log "github.com/sirupsen/logrus"
"golang.org/x/crypto/pbkdf2"
)
type pbkdf2Hasher struct {
saltSize int
iterations int
algorithm string
saltEncoding string
keyLen int
}
func NewPBKDF2Hasher(saltSize int, iterations int, algorithm string, saltEncoding string, keylen int) HashComparer {
return pbkdf2Hasher{
saltSize: saltSize,
iterations: iterations,
algorithm: algorithm,
saltEncoding: preferredEncoding(saltEncoding),
keyLen: keylen,
}
}
/*
* PBKDF2 methods are adapted from github.com/brocaar/chirpstack-application-server, some comments included.
*/
// Hash function reference may be found at https://github.com/brocaar/chirpstack-application-server/blob/master/internal/storage/user.go#L421.
// Generate the hash of a password for storage in the database.
// NOTE: We store the details of the hashing algorithm with the hash itself,
// making it easy to recreate the hash for password checking, even if we change
// the default criteria here.
func (h pbkdf2Hasher) Hash(password string) (string, error) {
// Generate a random salt value with the given salt size.
salt := make([]byte, h.saltSize)
_, err := rand.Read(salt)
// We need to ensure that salt doesn contain $, which is 36 in decimal.
// So we check if there'sbyte that represents $ and change it with a random number in the range 0-35
//// This is far from ideal, but should be good enough with a reasonable salt size.
for i := 0; i < len(salt); i++ {
if salt[i] == 36 {
n, err := rand.Int(rand.Reader, big.NewInt(35))
if err != nil {
return "", fmt.Errorf("read random byte error: %s", err)
}
salt[i] = byte(n.Int64())
break
}
}
if err != nil {
return "", fmt.Errorf("read random bytes error: %s", err)
}
return h.hashWithSalt(password, salt, h.iterations, h.algorithm, h.keyLen), nil
}
// HashCompare verifies that passed password hashes to the same value as the
// passed passwordHash.
// Reference: https://github.com/brocaar/chirpstack-application-server/blob/master/internal/storage/user.go#L458.
func (h pbkdf2Hasher) Compare(password string, passwordHash string) bool {
hashSplit := strings.Split(passwordHash, "$")
if len(hashSplit) != 5 {
log.Errorf("invalid PBKDF2 hash supplied, expected length 5, got: %d", len(hashSplit))
return false
}
algorithm := hashSplit[1]
iterations, err := strconv.Atoi(hashSplit[2])
if err != nil {
log.Errorf("iterations error: %s", err)
return false
}
var salt []byte
switch h.saltEncoding {
case UTF8:
salt = []byte(hashSplit[3])
default:
salt, err = base64.StdEncoding.DecodeString(hashSplit[3])
if err != nil {
log.Errorf("base64 salt error: %s", err)
return false
}
}
hashedPassword, err := base64.StdEncoding.DecodeString(hashSplit[4])
if err != nil {
log.Errorf("base64 hash decoding error: %s", err)
return false
}
keylen := len(hashedPassword)
return passwordHash == h.hashWithSalt(password, salt, iterations, algorithm, keylen)
}
// Reference: https://github.com/brocaar/chirpstack-application-server/blob/master/internal/storage/user.go#L432.
func (h pbkdf2Hasher) hashWithSalt(password string, salt []byte, iterations int, algorithm string, keylen int) string {
// Generate the hashed password. This should be a little painful, adjust ITERATIONS
// if it needs performance tweeking. Greatly depends on the hardware.
// NOTE: We store these details with the returned hashed, so changes will not
// affect our ability to do password compares.
shaHash := sha512.New
if algorithm == SHA256 {
shaHash = sha256.New
}
hashed := pbkdf2.Key([]byte(password), salt, iterations, keylen, shaHash)
var buffer bytes.Buffer
buffer.WriteString("PBKDF2$")
buffer.WriteString(fmt.Sprintf("%s$", algorithm))
buffer.WriteString(strconv.Itoa(iterations))
buffer.WriteString("$")
switch h.saltEncoding {
case UTF8:
buffer.WriteString(string(salt))
default:
buffer.WriteString(base64.StdEncoding.EncodeToString(salt))
}
buffer.WriteString("$")
buffer.WriteString(base64.StdEncoding.EncodeToString(hashed))
return buffer.String()
}