9
9
"crypto/sha1"
10
10
"crypto/sha256"
11
11
"crypto/sha512"
12
+ "crypto/subtle"
12
13
"encoding/binary"
14
+ "encoding/hex"
13
15
"fmt"
14
16
"io"
15
17
mrand "math/rand"
@@ -29,7 +31,7 @@ func Pstack() string {
29
31
return string (buf [0 :n ])
30
32
}
31
33
32
- func CalcPassword (scramble , password []byte ) []byte {
34
+ func CalcNativePassword (scramble , password []byte ) []byte {
33
35
if len (password ) == 0 {
34
36
return nil
35
37
}
@@ -39,35 +41,100 @@ func CalcPassword(scramble, password []byte) []byte {
39
41
crypt .Write (password )
40
42
stage1 := crypt .Sum (nil )
41
43
42
- // scrambleHash = SHA1(scramble + SHA1(stage1Hash))
43
- // inner Hash
44
+ // stage2Hash = SHA1(stage1Hash)
44
45
crypt .Reset ()
45
46
crypt .Write (stage1 )
46
- hash := crypt .Sum (nil )
47
+ stage2 := crypt .Sum (nil )
47
48
48
- // outer Hash
49
+ // scrambleHash = SHA1(scramble + stage2Hash)
49
50
crypt .Reset ()
50
51
crypt .Write (scramble )
51
- crypt .Write (hash )
52
- scramble = crypt .Sum (nil )
52
+ crypt .Write (stage2 )
53
+ scrambleHash : = crypt .Sum (nil )
53
54
54
55
// token = scrambleHash XOR stage1Hash
55
- for i := range scramble {
56
- scramble [i ] ^= stage1 [i ]
56
+ return Xor (scrambleHash , stage1 )
57
+ }
58
+
59
+ func Xor (hash1 []byte , hash2 []byte ) []byte {
60
+ for i := range hash1 {
61
+ hash1 [i ] ^= hash2 [i ]
57
62
}
58
- return scramble
63
+ return hash1
64
+ }
65
+
66
+ // hash_stage1 = xor(reply, sha1(public_seed, hash_stage2))
67
+ func Stage1FromReply (scramble []byte , seed []byte , stage2 []byte ) []byte {
68
+ crypt := sha1 .New ()
69
+ crypt .Write (seed )
70
+ crypt .Write (stage2 )
71
+ seededHash := crypt .Sum (nil )
72
+
73
+ return Xor (scramble , seededHash )
74
+ }
75
+
76
+ // FROM vitess.io/vitess/go/mysql/auth_server.go
77
+ // DecodePasswordHex decodes the standard format used by MySQL
78
+ // for 4.1 style password hashes. It drops the optionally leading * before
79
+ // decoding the rest as a hex encoded string.
80
+ func DecodePasswordHex (hexEncodedPassword string ) ([]byte , error ) {
81
+ if hexEncodedPassword [0 ] == '*' {
82
+ hexEncodedPassword = hexEncodedPassword [1 :]
83
+ }
84
+ return hex .DecodeString (hexEncodedPassword )
85
+ }
86
+
87
+ // EncodePasswordHex encodes to the standard format used by MySQL
88
+ // adds the optionally leading * to the hashed password
89
+ func EncodePasswordHex (passwordHash []byte ) string {
90
+ hexstr := strings .ToUpper (hex .EncodeToString (passwordHash ))
91
+ return "*" + hexstr
92
+ }
93
+
94
+ // NativePasswordHash = sha1(sha1(password))
95
+ func NativePasswordHash (password []byte ) []byte {
96
+ if len (password ) == 0 {
97
+ return nil
98
+ }
99
+
100
+ // stage1Hash = SHA1(password)
101
+ crypt := sha1 .New ()
102
+ crypt .Write (password )
103
+ stage1 := crypt .Sum (nil )
104
+
105
+ // stage2Hash = SHA1(stage1Hash)
106
+ crypt .Reset ()
107
+ crypt .Write (stage1 )
108
+ return crypt .Sum (nil )
109
+ }
110
+
111
+ func CompareNativePassword (reply []byte , stored []byte , seed []byte ) bool {
112
+ if len (stored ) == 0 {
113
+ return false
114
+ }
115
+
116
+ // hash_stage1 = xor(reply, sha1(public_seed, hash_stage2))
117
+ stage1 := Stage1FromReply (reply , seed , stored )
118
+ // andidate_hash2 = sha1(hash_stage1)
119
+ crypt := sha1 .New ()
120
+ crypt .Write (stage1 )
121
+ stage2 := crypt .Sum (nil )
122
+
123
+ // check(candidate_hash2 == hash_stage2)
124
+ // use ConstantTimeCompare to mitigate timing based attacks
125
+ return subtle .ConstantTimeCompare (stage2 , stored ) == 1
59
126
}
60
127
61
128
// CalcCachingSha2Password: Hash password using MySQL 8+ method (SHA256)
62
- func CalcCachingSha2Password (scramble []byte , password string ) []byte {
129
+ func CalcCachingSha2Password (scramble []byte , password [] byte ) []byte {
63
130
if len (password ) == 0 {
64
131
return nil
65
132
}
66
133
67
134
// XOR(SHA256(password), SHA256(SHA256(SHA256(password)), scramble))
68
135
69
136
crypt := sha256 .New ()
70
- crypt .Write ([] byte ( password ) )
137
+ crypt .Write (password )
71
138
message1 := crypt .Sum (nil )
72
139
73
140
crypt .Reset ()
@@ -135,6 +202,91 @@ func EncryptPassword(password string, seed []byte, pub *rsa.PublicKey) ([]byte,
135
202
return rsa .EncryptOAEP (sha1v , rand .Reader , pub , plain , nil )
136
203
}
137
204
205
+ const (
206
+ SALT_LENGTH = 16
207
+ ITERATION_MULTIPLIER = 1000
208
+ SHA256_PASSWORD_ITERATIONS = 5
209
+ )
210
+
211
+ // generateUserSalt generate salt of given length for sha256_password hash
212
+ func generateUserSalt (length int ) ([]byte , error ) {
213
+ // Generate a random salt of the given length
214
+ // Implement this function for your project
215
+ salt := make ([]byte , length )
216
+ _ , err := rand .Read (salt )
217
+ if err != nil {
218
+ return []byte ("" ), err
219
+ }
220
+
221
+ // Restrict to 7-bit to avoid multi-byte UTF-8
222
+ for i := range salt {
223
+ salt [i ] = salt [i ] &^ 128
224
+ for salt [i ] == 36 || salt [i ] == 0 { // '$' or NUL
225
+ newval := make ([]byte , 1 )
226
+ _ , err := rand .Read (newval )
227
+ if err != nil {
228
+ return []byte ("" ), err
229
+ }
230
+ salt [i ] = newval [0 ] &^ 128
231
+ }
232
+ }
233
+ return salt , nil
234
+ }
235
+
236
+ // hashCrypt256 salt and hash a password the given number of iterations
237
+ func hashCrypt256 (source , salt string , iterations uint64 ) (string , error ) {
238
+ actualIterations := iterations * ITERATION_MULTIPLIER
239
+ hashInput := []byte (source + salt )
240
+ var hash [32 ]byte
241
+ for i := uint64 (0 ); i < actualIterations ; i ++ {
242
+ h := sha256 .New ()
243
+ h .Write (hashInput )
244
+ hash = sha256 .Sum256 (h .Sum (nil ))
245
+ hashInput = hash [:]
246
+ }
247
+
248
+ hashHex := hex .EncodeToString (hash [:])
249
+ digest := fmt .Sprintf ("$%d$%s$%s" , iterations , salt , hashHex )
250
+ return digest , nil
251
+ }
252
+
253
+ // Check256HashingPassword compares a password to a hash for sha256_password
254
+ // rather than trying to recreate just the hash we recreate the full hash
255
+ // and use that for comparison
256
+ func Check256HashingPassword (pwhash []byte , password string ) (bool , error ) {
257
+ pwHashParts := bytes .Split (pwhash , []byte ("$" ))
258
+ if len (pwHashParts ) != 4 {
259
+ return false , errors .New ("failed to decode hash parts" )
260
+ }
261
+
262
+ iterationsPart := pwHashParts [1 ]
263
+ if len (iterationsPart ) == 0 {
264
+ return false , errors .New ("iterations part is empty" )
265
+ }
266
+
267
+ iterations , err := strconv .ParseUint (string (iterationsPart ), 10 , 64 )
268
+ if err != nil {
269
+ return false , errors .New ("failed to decode iterations" )
270
+ }
271
+ salt := pwHashParts [2 ][:SALT_LENGTH ]
272
+
273
+ newHash , err := hashCrypt256 (password , string (salt ), iterations )
274
+ if err != nil {
275
+ return false , err
276
+ }
277
+
278
+ return bytes .Equal (pwhash , []byte (newHash )), nil
279
+ }
280
+
281
+ // NewSha256PasswordHash creates a new password hash for sha256_password
282
+ func NewSha256PasswordHash (pwd string ) (string , error ) {
283
+ salt , err := generateUserSalt (SALT_LENGTH )
284
+ if err != nil {
285
+ return "" , err
286
+ }
287
+ return hashCrypt256 (pwd , string (salt ), SHA256_PASSWORD_ITERATIONS )
288
+ }
289
+
138
290
func DecompressMariadbData (data []byte ) ([]byte , error ) {
139
291
// algorithm always 0=zlib
140
292
// algorithm := (data[pos] & 0x07) >> 4
0 commit comments