99 "crypto/sha1"
1010 "crypto/sha256"
1111 "crypto/sha512"
12+ "crypto/subtle"
1213 "encoding/binary"
14+ "encoding/hex"
1315 "fmt"
1416 "io"
1517 mrand "math/rand"
@@ -29,7 +31,7 @@ func Pstack() string {
2931 return string (buf [0 :n ])
3032}
3133
32- func CalcPassword (scramble , password []byte ) []byte {
34+ func CalcNativePassword (scramble , password []byte ) []byte {
3335 if len (password ) == 0 {
3436 return nil
3537 }
@@ -39,35 +41,100 @@ func CalcPassword(scramble, password []byte) []byte {
3941 crypt .Write (password )
4042 stage1 := crypt .Sum (nil )
4143
42- // scrambleHash = SHA1(scramble + SHA1(stage1Hash))
43- // inner Hash
44+ // stage2Hash = SHA1(stage1Hash)
4445 crypt .Reset ()
4546 crypt .Write (stage1 )
46- hash := crypt .Sum (nil )
47+ stage2 := crypt .Sum (nil )
4748
48- // outer Hash
49+ // scrambleHash = SHA1(scramble + stage2Hash)
4950 crypt .Reset ()
5051 crypt .Write (scramble )
51- crypt .Write (hash )
52- scramble = crypt .Sum (nil )
52+ crypt .Write (stage2 )
53+ scrambleHash : = crypt .Sum (nil )
5354
5455 // token = scrambleHash XOR stage1Hash
55- for i := range scramble {
56- scramble [i ] ^= stage1 [i ]
56+ return Xor (scrambleHash , stage1 )
57+ }
58+
59+ // Xor modifies hash1 in-place with XOR against hash2
60+ func Xor (hash1 []byte , hash2 []byte ) []byte {
61+ l := min (len (hash1 ), len (hash2 ))
62+ for i := range l {
63+ hash1 [i ] ^= hash2 [i ]
64+ }
65+ return hash1
66+ }
67+
68+ // hash_stage1 = xor(reply, sha1(public_seed, hash_stage2))
69+ func stage1FromReply (scramble []byte , seed []byte , stage2 []byte ) []byte {
70+ crypt := sha1 .New ()
71+ crypt .Write (seed )
72+ crypt .Write (stage2 )
73+ seededHash := crypt .Sum (nil )
74+
75+ return Xor (scramble , seededHash )
76+ }
77+
78+ // DecodePasswordHex decodes the standard format used by MySQL
79+ // Password hashes in the 4.1 format always begin with a * character
80+ // see https://dev.mysql.com/doc/mysql-security-excerpt/5.7/en/password-hashing.html
81+ // ref vitess.io/vitess/go/mysql/auth_server.go
82+ func DecodePasswordHex (hexEncodedPassword string ) ([]byte , error ) {
83+ if hexEncodedPassword [0 ] == '*' {
84+ hexEncodedPassword = hexEncodedPassword [1 :]
85+ }
86+ return hex .DecodeString (hexEncodedPassword )
87+ }
88+
89+ // EncodePasswordHex encodes to the standard format used by MySQL
90+ // adds the optionally leading * to the hashed password
91+ func EncodePasswordHex (passwordHash []byte ) string {
92+ hexstr := strings .ToUpper (hex .EncodeToString (passwordHash ))
93+ return "*" + hexstr
94+ }
95+
96+ // NativePasswordHash = sha1(sha1(password))
97+ func NativePasswordHash (password []byte ) []byte {
98+ if len (password ) == 0 {
99+ return nil
57100 }
58- return scramble
101+
102+ // stage1Hash = SHA1(password)
103+ crypt := sha1 .New ()
104+ crypt .Write (password )
105+ stage1 := crypt .Sum (nil )
106+
107+ // stage2Hash = SHA1(stage1Hash)
108+ crypt .Reset ()
109+ crypt .Write (stage1 )
110+ return crypt .Sum (stage1 [:0 ])
111+ }
112+
113+ func CompareNativePassword (reply []byte , stored []byte , seed []byte ) bool {
114+ if len (stored ) == 0 {
115+ return false
116+ }
117+
118+ // hash_stage1 = xor(reply, sha1(public_seed, hash_stage2))
119+ stage1 := stage1FromReply (reply , seed , stored )
120+ // andidate_hash2 = sha1(hash_stage1)
121+ stage2 := sha1 .Sum (stage1 )
122+
123+ // check(candidate_hash2 == hash_stage2)
124+ // use ConstantTimeCompare to mitigate timing based attacks
125+ return subtle .ConstantTimeCompare (stage2 [:], stored ) == 1
59126}
60127
61128// CalcCachingSha2Password: Hash password using MySQL 8+ method (SHA256)
62- func CalcCachingSha2Password (scramble []byte , password string ) []byte {
129+ func CalcCachingSha2Password (scramble []byte , password [] byte ) []byte {
63130 if len (password ) == 0 {
64131 return nil
65132 }
66133
67134 // XOR(SHA256(password), SHA256(SHA256(SHA256(password)), scramble))
68135
69136 crypt := sha256 .New ()
70- crypt .Write ([] byte ( password ) )
137+ crypt .Write (password )
71138 message1 := crypt .Sum (nil )
72139
73140 crypt .Reset ()
@@ -79,11 +146,7 @@ func CalcCachingSha2Password(scramble []byte, password string) []byte {
79146 crypt .Write (scramble )
80147 message2 := crypt .Sum (nil )
81148
82- for i := range message1 {
83- message1 [i ] ^= message2 [i ]
84- }
85-
86- return message1
149+ return Xor (message1 , message2 )
87150}
88151
89152// Taken from https://github.com/go-sql-driver/mysql/pull/1518
@@ -135,6 +198,89 @@ func EncryptPassword(password string, seed []byte, pub *rsa.PublicKey) ([]byte,
135198 return rsa .EncryptOAEP (sha1v , rand .Reader , pub , plain , nil )
136199}
137200
201+ const (
202+ SALT_LENGTH = 16
203+ ITERATION_MULTIPLIER = 1000
204+ SHA256_PASSWORD_ITERATIONS = 5
205+ )
206+
207+ // generateUserSalt generate salt of given length for sha256_password hash
208+ func generateUserSalt (length int ) ([]byte , error ) {
209+ // Generate a random salt of the given length
210+ // Implement this function for your project
211+ salt := make ([]byte , length )
212+ _ , err := rand .Read (salt )
213+ if err != nil {
214+ return []byte ("" ), err
215+ }
216+
217+ // Restrict to 7-bit to avoid multi-byte UTF-8
218+ for i := range salt {
219+ salt [i ] = salt [i ] &^ 128
220+ for salt [i ] == 36 || salt [i ] == 0 { // '$' or NUL
221+ newval := make ([]byte , 1 )
222+ _ , err := rand .Read (newval )
223+ if err != nil {
224+ return []byte ("" ), err
225+ }
226+ salt [i ] = newval [0 ] &^ 128
227+ }
228+ }
229+ return salt , nil
230+ }
231+
232+ // hashCrypt256 salt and hash a password the given number of iterations
233+ func hashCrypt256 (source , salt string , iterations uint64 ) (string , error ) {
234+ actualIterations := iterations * ITERATION_MULTIPLIER
235+ hashInput := []byte (source + salt )
236+ var hash [32 ]byte
237+ for i := uint64 (0 ); i < actualIterations ; i ++ {
238+ hash = sha256 .Sum256 (hashInput )
239+ hashInput = hash [:]
240+ }
241+
242+ hashHex := hex .EncodeToString (hash [:])
243+ digest := fmt .Sprintf ("$%d$%s$%s" , iterations , salt , hashHex )
244+ return digest , nil
245+ }
246+
247+ // Check256HashingPassword compares a password to a hash for sha256_password
248+ // rather than trying to recreate just the hash we recreate the full hash
249+ // and use that for comparison
250+ func Check256HashingPassword (pwhash []byte , password string ) (bool , error ) {
251+ pwHashParts := bytes .Split (pwhash , []byte ("$" ))
252+ if len (pwHashParts ) != 4 {
253+ return false , errors .New ("failed to decode hash parts" )
254+ }
255+
256+ iterationsPart := pwHashParts [1 ]
257+ if len (iterationsPart ) == 0 {
258+ return false , errors .New ("iterations part is empty" )
259+ }
260+
261+ iterations , err := strconv .ParseUint (string (iterationsPart ), 10 , 64 )
262+ if err != nil {
263+ return false , errors .New ("failed to decode iterations" )
264+ }
265+ salt := pwHashParts [2 ][:SALT_LENGTH ]
266+
267+ newHash , err := hashCrypt256 (password , string (salt ), iterations )
268+ if err != nil {
269+ return false , err
270+ }
271+
272+ return subtle .ConstantTimeCompare (pwhash , []byte (newHash )) == 1 , nil
273+ }
274+
275+ // NewSha256PasswordHash creates a new password hash for sha256_password
276+ func NewSha256PasswordHash (pwd string ) (string , error ) {
277+ salt , err := generateUserSalt (SALT_LENGTH )
278+ if err != nil {
279+ return "" , err
280+ }
281+ return hashCrypt256 (pwd , string (salt ), SHA256_PASSWORD_ITERATIONS )
282+ }
283+
138284func DecompressMariadbData (data []byte ) ([]byte , error ) {
139285 // algorithm always 0=zlib
140286 // algorithm := (data[pos] & 0x07) >> 4
0 commit comments