-
Notifications
You must be signed in to change notification settings - Fork 339
/
authenticate.go
211 lines (183 loc) · 5.95 KB
/
authenticate.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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
package auth
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import (
"crypto/rand"
"crypto/subtle"
"encoding/base64"
"errors"
"fmt"
"strconv"
"strings"
"golang.org/x/crypto/scrypt"
)
// SCRYPTComponents the input parameters to the Scrypt encryption key format
type SCRYPTComponents struct {
Algorithm string // The SCRYPT algorithm prefix
N int // CPU/memory cost parameter (logN)
R int // block size parameter (octets)
P int // parallelization parameter (positive int)
Salt []byte // salt value
SaltLen int // bytes to use as salt (octets)
DK []byte // derived key value
DKLen int // length of the derived key (octets)
}
const KEY_DELIM = ":"
// The SCRYPT functionality defined in this package is derived based upon the following
// references:
// https://pkg.go.dev/golang.org/x/crypto/scrypt
// https://www.tarsnap.com/scrypt/scrypt.pdf
var DefaultParams = SCRYPTComponents{
Algorithm: "SCRYPT",
N: 16384,
R: 8,
P: 1,
SaltLen: 16,
DKLen: 64}
// DerivePassword uses the https://pkg.go.dev/golang.org/x/crypto/scrypt package to
// return an encrypted password that is compatible with the
// Perl CPAN library Crypt::ScryptKDF for backward compatibility
// to authenticate through the Perl API the same way.
// See: http://cpansearch.perl.org/src/MIK/Crypt-ScryptKDF-0.010/lib/Crypt/ScryptKDF.pm
func DerivePassword(password string) (string, error) {
var salt []byte
var err error
salt, err = generateSalt(DefaultParams.DKLen)
if err != nil {
return "", errors.New("generating salt: " + err.Error())
}
key, err := scrypt.Key([]byte(password), salt, DefaultParams.N, DefaultParams.R, DefaultParams.P, DefaultParams.DKLen)
if err != nil {
return "", err
}
nStr := strconv.Itoa(DefaultParams.N)
if err != nil {
return "", errors.New("converting N: " + err.Error())
}
rStr := strconv.Itoa(DefaultParams.R)
pStr := strconv.Itoa(DefaultParams.P)
saltBase64 := base64.StdEncoding.EncodeToString(salt)
keyBase64 := base64.StdEncoding.EncodeToString(key)
// The SCRYPT prefix is added because the Mojolicious Perl library adds this as a
// prefix to every password in the database. So it's added for compatibility.
scryptPass := []string{DefaultParams.Algorithm, nStr, rStr, pStr, saltBase64, keyBase64}
return strings.Join(scryptPass, KEY_DELIM), nil
}
// VerifySCRYPTPassword parses the original Derived Key (DK) from the SCRYPT password
// so that it can compare that with the password/scriptPassword param
func VerifySCRYPTPassword(password string, scryptPassword string) error {
scomp, err := parseScrypt(scryptPassword)
if err != nil {
return err
}
keylenBytes := len(scryptPassword) - DefaultParams.DKLen
if keylenBytes < 1 {
return errors.New("Invalid scryptPassword length")
}
// scrypt the cleartext password with the same parameters and salt
tmpDK, err := scrypt.Key([]byte(password),
[]byte(scomp.Salt),
scomp.N, // Must be a power of 2 greater than 1
scomp.R,
scomp.P, // r*p must be < 2^30
DefaultParams.DKLen)
if err != nil {
return err
}
// Compare the Derived Key from the SCRYPT password
if subtle.ConstantTimeCompare(scomp.DK, tmpDK) != 1 {
return errors.New("invalid password")
}
return err
}
func parseScrypt(scryptPassword string) (SCRYPTComponents, error) {
var err error
var scomp SCRYPTComponents
if scryptPassword == "" {
return scomp, errors.New("scrypt password is required")
}
sh := strings.Split(scryptPassword, ":")
// Algorithm
scomp.Algorithm = sh[0]
if scomp.Algorithm == "" {
return scomp, errors.New("Algorithm was not defined")
}
if scomp.Algorithm != DefaultParams.Algorithm {
return scomp, fmt.Errorf("Algorithm defined is not %s", DefaultParams.Algorithm)
}
// N
n := sh[1]
if n == "" {
return scomp, errors.New("N was not defined")
}
var nInt int
nInt, err = strconv.Atoi(n)
if err != nil {
return scomp, fmt.Errorf("%v i=%d, type: %T", err, nInt, nInt)
}
scomp.N = nInt
// R
r := sh[2]
if r == "" {
return scomp, errors.New("r was not defined")
}
scomp.R, err = strconv.Atoi(r)
if err != nil {
return scomp, errors.New(fmt.Sprintf("i=%d, type: %T\n", scomp.R, scomp.R))
}
// P
p := sh[3]
if p == "" {
return scomp, errors.New("p was not defined")
}
scomp.P, err = strconv.Atoi(p)
if err != nil {
return scomp, errors.New(fmt.Sprintf("i=%d, type: %T\n", scomp.P, scomp.P))
}
// Salt
saltBase64 := sh[4]
scomp.Salt, err = base64.StdEncoding.DecodeString(saltBase64)
if err != nil {
return scomp, errors.New("salt cannot be decoded")
}
scomp.SaltLen = len(scomp.Salt)
if len(scomp.Salt) == 0 {
return scomp, errors.New("salt length cannot be zero")
}
// Salt
dkBase64 := sh[5]
scomp.DK, err = base64.StdEncoding.DecodeString(dkBase64)
if err != nil {
return scomp, errors.New("key cannot be decoded")
}
return scomp, err
}
// generateSalt returns securely generated random bytes.
// It will return an error if the system's secure random
// number generator fails to function correctly, in which
// case the caller should not continue.
func generateSalt(n int) ([]byte, error) {
b := make([]byte, n)
_, err := rand.Read(b)
// Note that err == nil only if we read len(b) bytes.
if err != nil {
return nil, err
}
return b, nil
}