@@ -6,25 +6,67 @@ import (
6
6
"crypto/subtle"
7
7
"encoding/base64"
8
8
"fmt"
9
+ "os"
9
10
"strconv"
10
11
"strings"
11
12
12
13
"golang.org/x/crypto/pbkdf2"
14
+ "golang.org/x/exp/slices"
13
15
"golang.org/x/xerrors"
14
16
)
15
17
16
- const (
17
- // This is the length of our output hash.
18
- // bcrypt has a hash size of 59, so we rounded up to a power of 8.
18
+ var (
19
+ // The base64 encoder used when producing the string representation of
20
+ // hashes.
21
+ base64Encoding = base64 .RawStdEncoding
22
+
23
+ // The number of iterations to use when generating the hash. This was chosen
24
+ // to make it about as fast as bcrypt hashes. Increasing this causes hashes
25
+ // to take longer to compute.
26
+ defaultHashIter = 65535
27
+
28
+ // This is the length of our output hash. bcrypt has a hash size of up to
29
+ // 60, so we rounded up to a power of 8.
19
30
hashLength = 64
31
+
20
32
// The scheme to include in our hashed password.
21
33
hashScheme = "pbkdf2-sha256"
34
+
35
+ // A salt size of 16 is the default in passlib. A minimum of 8 can be safely
36
+ // used.
37
+ defaultSaltSize = 16
38
+
39
+ // The simulated hash is used when trying to simulate password checks for
40
+ // users that don't exist.
41
+ simulatedHash , _ = Hash ("hunter2" )
22
42
)
23
43
24
- // Compare checks the equality of passwords from a hashed pbkdf2 string.
25
- // This uses pbkdf2 to ensure FIPS 140-2 compliance. See:
44
+ // Make password hashing much faster in tests.
45
+ func init () {
46
+ args := os .Args [1 :]
47
+
48
+ // Ensure this can never be enabled if running in server mode.
49
+ if slices .Contains (args , "server" ) {
50
+ return
51
+ }
52
+
53
+ for _ , flag := range args {
54
+ if strings .HasPrefix (flag , "-test." ) {
55
+ defaultHashIter = 1
56
+ return
57
+ }
58
+ }
59
+ }
60
+
61
+ // Compare checks the equality of passwords from a hashed pbkdf2 string. This
62
+ // uses pbkdf2 to ensure FIPS 140-2 compliance. See:
26
63
// https://csrc.nist.gov/csrc/media/templates/cryptographic-module-validation-program/documents/security-policies/140sp2261.pdf
27
64
func Compare (hashed string , password string ) (bool , error ) {
65
+ // If the hased password provided is empty, simulate comparing a real hash.
66
+ if hashed == "" {
67
+ hashed = simulatedHash
68
+ }
69
+
28
70
if len (hashed ) < hashLength {
29
71
return false , xerrors .Errorf ("hash too short: %d" , len (hashed ))
30
72
}
@@ -42,37 +84,40 @@ func Compare(hashed string, password string) (bool, error) {
42
84
if err != nil {
43
85
return false , xerrors .Errorf ("parse iter from hash: %w" , err )
44
86
}
45
- salt , err := base64 . RawStdEncoding .DecodeString (parts [3 ])
87
+ salt , err := base64Encoding .DecodeString (parts [3 ])
46
88
if err != nil {
47
89
return false , xerrors .Errorf ("decode salt: %w" , err )
48
90
}
49
91
50
92
if subtle .ConstantTimeCompare ([]byte (hashWithSaltAndIter (password , salt , iter )), []byte (hashed )) != 1 {
51
93
return false , nil
52
94
}
95
+
53
96
return true , nil
54
97
}
55
98
56
99
// Hash generates a hash using pbkdf2.
57
100
// See the Compare() comment for rationale.
58
101
func Hash (password string ) (string , error ) {
59
- // bcrypt uses a salt size of 16 bytes.
60
- salt := make ([]byte , 16 )
102
+ salt := make ([]byte , defaultSaltSize )
61
103
_ , err := rand .Read (salt )
62
104
if err != nil {
63
105
return "" , xerrors .Errorf ("read random bytes for salt: %w" , err )
64
106
}
65
- // The default hash iteration is 1024 for speed.
66
- // As this is increased, the password is hashed more.
67
- return hashWithSaltAndIter (password , salt , 1024 ), nil
107
+
108
+ return hashWithSaltAndIter (password , salt , defaultHashIter ), nil
68
109
}
69
110
70
111
// Produces a string representation of the hash.
71
112
func hashWithSaltAndIter (password string , salt []byte , iter int ) string {
72
- hash := pbkdf2 .Key ([]byte (password ), salt , iter , hashLength , sha256 .New )
73
- hash = []byte (base64 .RawStdEncoding .EncodeToString (hash ))
74
- salt = []byte (base64 .RawStdEncoding .EncodeToString (salt ))
75
- // This format is similar to bcrypt. See:
76
- // https://en.wikipedia.org/wiki/Bcrypt#Description
77
- return fmt .Sprintf ("$%s$%d$%s$%s" , hashScheme , iter , salt , hash )
113
+ var (
114
+ hash = pbkdf2 .Key ([]byte (password ), salt , iter , hashLength , sha256 .New )
115
+ encHash = make ([]byte , base64Encoding .EncodedLen (len (hash )))
116
+ encSalt = make ([]byte , base64Encoding .EncodedLen (len (salt )))
117
+ )
118
+
119
+ base64Encoding .Encode (encHash , hash )
120
+ base64Encoding .Encode (encSalt , salt )
121
+
122
+ return fmt .Sprintf ("$%s$%d$%s$%s" , hashScheme , iter , encSalt , encHash )
78
123
}
0 commit comments