-
Notifications
You must be signed in to change notification settings - Fork 1k
Don't store plaintext passwords #1040
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 3 commits
5f26fd6
43cc679
4e6aea2
88a5a80
80c125d
ea8b036
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -9,7 +9,9 @@ import ( | |||||||||||||||||||||||||
"crypto/sha1" | ||||||||||||||||||||||||||
"crypto/sha256" | ||||||||||||||||||||||||||
"crypto/sha512" | ||||||||||||||||||||||||||
"crypto/subtle" | ||||||||||||||||||||||||||
"encoding/binary" | ||||||||||||||||||||||||||
"encoding/hex" | ||||||||||||||||||||||||||
"fmt" | ||||||||||||||||||||||||||
"io" | ||||||||||||||||||||||||||
mrand "math/rand" | ||||||||||||||||||||||||||
|
@@ -29,7 +31,7 @@ func Pstack() string { | |||||||||||||||||||||||||
return string(buf[0:n]) | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
func CalcPassword(scramble, password []byte) []byte { | ||||||||||||||||||||||||||
func CalcNativePassword(scramble, password []byte) []byte { | ||||||||||||||||||||||||||
if len(password) == 0 { | ||||||||||||||||||||||||||
return nil | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
@@ -39,35 +41,100 @@ func CalcPassword(scramble, password []byte) []byte { | |||||||||||||||||||||||||
crypt.Write(password) | ||||||||||||||||||||||||||
stage1 := crypt.Sum(nil) | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
// scrambleHash = SHA1(scramble + SHA1(stage1Hash)) | ||||||||||||||||||||||||||
// inner Hash | ||||||||||||||||||||||||||
// stage2Hash = SHA1(stage1Hash) | ||||||||||||||||||||||||||
crypt.Reset() | ||||||||||||||||||||||||||
crypt.Write(stage1) | ||||||||||||||||||||||||||
hash := crypt.Sum(nil) | ||||||||||||||||||||||||||
stage2 := crypt.Sum(nil) | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
// outer Hash | ||||||||||||||||||||||||||
// scrambleHash = SHA1(scramble + stage2Hash) | ||||||||||||||||||||||||||
crypt.Reset() | ||||||||||||||||||||||||||
crypt.Write(scramble) | ||||||||||||||||||||||||||
crypt.Write(hash) | ||||||||||||||||||||||||||
scramble = crypt.Sum(nil) | ||||||||||||||||||||||||||
crypt.Write(stage2) | ||||||||||||||||||||||||||
scrambleHash := crypt.Sum(nil) | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
// token = scrambleHash XOR stage1Hash | ||||||||||||||||||||||||||
for i := range scramble { | ||||||||||||||||||||||||||
scramble[i] ^= stage1[i] | ||||||||||||||||||||||||||
return Xor(scrambleHash, stage1) | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
func Xor(hash1 []byte, hash2 []byte) []byte { | ||||||||||||||||||||||||||
lance6716 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||
for i := range hash1 { | ||||||||||||||||||||||||||
hash1[i] ^= hash2[i] | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
return scramble | ||||||||||||||||||||||||||
return hash1 | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
// hash_stage1 = xor(reply, sha1(public_seed, hash_stage2)) | ||||||||||||||||||||||||||
func Stage1FromReply(scramble []byte, seed []byte, stage2 []byte) []byte { | ||||||||||||||||||||||||||
lance6716 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||
crypt := sha1.New() | ||||||||||||||||||||||||||
crypt.Write(seed) | ||||||||||||||||||||||||||
crypt.Write(stage2) | ||||||||||||||||||||||||||
seededHash := crypt.Sum(nil) | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
return Xor(scramble, seededHash) | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
// FROM vitess.io/vitess/go/mysql/auth_server.go | ||||||||||||||||||||||||||
lance6716 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||
// DecodePasswordHex decodes the standard format used by MySQL | ||||||||||||||||||||||||||
// for 4.1 style password hashes. It drops the optionally leading * before | ||||||||||||||||||||||||||
// decoding the rest as a hex encoded string. | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Golang's coding style suggests the function name |
||||||||||||||||||||||||||
func DecodePasswordHex(hexEncodedPassword string) ([]byte, error) { | ||||||||||||||||||||||||||
if hexEncodedPassword[0] == '*' { | ||||||||||||||||||||||||||
hexEncodedPassword = hexEncodedPassword[1:] | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
return hex.DecodeString(hexEncodedPassword) | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
// EncodePasswordHex encodes to the standard format used by MySQL | ||||||||||||||||||||||||||
// adds the optionally leading * to the hashed password | ||||||||||||||||||||||||||
func EncodePasswordHex(passwordHash []byte) string { | ||||||||||||||||||||||||||
hexstr := strings.ToUpper(hex.EncodeToString(passwordHash)) | ||||||||||||||||||||||||||
return "*" + hexstr | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
// NativePasswordHash = sha1(sha1(password)) | ||||||||||||||||||||||||||
func NativePasswordHash(password []byte) []byte { | ||||||||||||||||||||||||||
if len(password) == 0 { | ||||||||||||||||||||||||||
return nil | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
// stage1Hash = SHA1(password) | ||||||||||||||||||||||||||
crypt := sha1.New() | ||||||||||||||||||||||||||
crypt.Write(password) | ||||||||||||||||||||||||||
stage1 := crypt.Sum(nil) | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
// stage2Hash = SHA1(stage1Hash) | ||||||||||||||||||||||||||
crypt.Reset() | ||||||||||||||||||||||||||
crypt.Write(stage1) | ||||||||||||||||||||||||||
return crypt.Sum(nil) | ||||||||||||||||||||||||||
lance6716 marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
The argument |
||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
func CompareNativePassword(reply []byte, stored []byte, seed []byte) bool { | ||||||||||||||||||||||||||
if len(stored) == 0 { | ||||||||||||||||||||||||||
return false | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
// hash_stage1 = xor(reply, sha1(public_seed, hash_stage2)) | ||||||||||||||||||||||||||
stage1 := Stage1FromReply(reply, seed, stored) | ||||||||||||||||||||||||||
// andidate_hash2 = sha1(hash_stage1) | ||||||||||||||||||||||||||
crypt := sha1.New() | ||||||||||||||||||||||||||
crypt.Write(stage1) | ||||||||||||||||||||||||||
stage2 := crypt.Sum(nil) | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
// check(candidate_hash2 == hash_stage2) | ||||||||||||||||||||||||||
// use ConstantTimeCompare to mitigate timing based attacks | ||||||||||||||||||||||||||
return subtle.ConstantTimeCompare(stage2, stored) == 1 | ||||||||||||||||||||||||||
Comment on lines
+120
to
+126
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
we can reuse the builtin functions. |
||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
// CalcCachingSha2Password: Hash password using MySQL 8+ method (SHA256) | ||||||||||||||||||||||||||
func CalcCachingSha2Password(scramble []byte, password string) []byte { | ||||||||||||||||||||||||||
func CalcCachingSha2Password(scramble []byte, password []byte) []byte { | ||||||||||||||||||||||||||
if len(password) == 0 { | ||||||||||||||||||||||||||
return nil | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
// XOR(SHA256(password), SHA256(SHA256(SHA256(password)), scramble)) | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
crypt := sha256.New() | ||||||||||||||||||||||||||
crypt.Write([]byte(password)) | ||||||||||||||||||||||||||
crypt.Write(password) | ||||||||||||||||||||||||||
message1 := crypt.Sum(nil) | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, I see that you just mimic the old code, so my comments like |
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
crypt.Reset() | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. line 149 can also use your new |
||||||||||||||||||||||||||
|
@@ -135,6 +202,91 @@ func EncryptPassword(password string, seed []byte, pub *rsa.PublicKey) ([]byte, | |||||||||||||||||||||||||
return rsa.EncryptOAEP(sha1v, rand.Reader, pub, plain, nil) | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
const ( | ||||||||||||||||||||||||||
SALT_LENGTH = 16 | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: My IDE says "Use camel case instead of snake case". However this project has both style so you can ignore it. |
||||||||||||||||||||||||||
ITERATION_MULTIPLIER = 1000 | ||||||||||||||||||||||||||
SHA256_PASSWORD_ITERATIONS = 5 | ||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
// generateUserSalt generate salt of given length for sha256_password hash | ||||||||||||||||||||||||||
func generateUserSalt(length int) ([]byte, error) { | ||||||||||||||||||||||||||
// Generate a random salt of the given length | ||||||||||||||||||||||||||
// Implement this function for your project | ||||||||||||||||||||||||||
salt := make([]byte, length) | ||||||||||||||||||||||||||
_, err := rand.Read(salt) | ||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||
return []byte(""), err | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
// Restrict to 7-bit to avoid multi-byte UTF-8 | ||||||||||||||||||||||||||
for i := range salt { | ||||||||||||||||||||||||||
salt[i] = salt[i] &^ 128 | ||||||||||||||||||||||||||
for salt[i] == 36 || salt[i] == 0 { // '$' or NUL | ||||||||||||||||||||||||||
newval := make([]byte, 1) | ||||||||||||||||||||||||||
_, err := rand.Read(newval) | ||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||
return []byte(""), err | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
salt[i] = newval[0] &^ 128 | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
return salt, nil | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
// hashCrypt256 salt and hash a password the given number of iterations | ||||||||||||||||||||||||||
func hashCrypt256(source, salt string, iterations uint64) (string, error) { | ||||||||||||||||||||||||||
actualIterations := iterations * ITERATION_MULTIPLIER | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. for this function, the const There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tried to match mysql server salt generation, but I'm wondering if it's used there to generate both the salt for the stored password hashes and for scramble generation. I see the scramble generation here is using something similar but not quite the same, perhaps the two can/should be combined? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't get your point clearly.
Do you mean the "scramble generation" related functions are similar to "salt generation" functions, so you want to merge them? Can you give a simple explanation about which functions are affected? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I was looking at |
||||||||||||||||||||||||||
hashInput := []byte(source + salt) | ||||||||||||||||||||||||||
var hash [32]byte | ||||||||||||||||||||||||||
for i := uint64(0); i < actualIterations; i++ { | ||||||||||||||||||||||||||
h := sha256.New() | ||||||||||||||||||||||||||
h.Write(hashInput) | ||||||||||||||||||||||||||
hash = sha256.Sum256(h.Sum(nil)) | ||||||||||||||||||||||||||
lance6716 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||
hashInput = hash[:] | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
hashHex := hex.EncodeToString(hash[:]) | ||||||||||||||||||||||||||
digest := fmt.Sprintf("$%d$%s$%s", iterations, salt, hashHex) | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You might be right, I was trying to recreate what was being done in mysql-server, but it may not be necessary here |
||||||||||||||||||||||||||
return digest, nil | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
// Check256HashingPassword compares a password to a hash for sha256_password | ||||||||||||||||||||||||||
// rather than trying to recreate just the hash we recreate the full hash | ||||||||||||||||||||||||||
// and use that for comparison | ||||||||||||||||||||||||||
func Check256HashingPassword(pwhash []byte, password string) (bool, error) { | ||||||||||||||||||||||||||
pwHashParts := bytes.Split(pwhash, []byte("$")) | ||||||||||||||||||||||||||
if len(pwHashParts) != 4 { | ||||||||||||||||||||||||||
return false, errors.New("failed to decode hash parts") | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
iterationsPart := pwHashParts[1] | ||||||||||||||||||||||||||
if len(iterationsPart) == 0 { | ||||||||||||||||||||||||||
return false, errors.New("iterations part is empty") | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
iterations, err := strconv.ParseUint(string(iterationsPart), 10, 64) | ||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||
return false, errors.New("failed to decode iterations") | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
salt := pwHashParts[2][:SALT_LENGTH] | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
newHash, err := hashCrypt256(password, string(salt), iterations) | ||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||
return false, err | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
return bytes.Equal(pwhash, []byte(newHash)), nil | ||||||||||||||||||||||||||
lance6716 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
// NewSha256PasswordHash creates a new password hash for sha256_password | ||||||||||||||||||||||||||
func NewSha256PasswordHash(pwd string) (string, error) { | ||||||||||||||||||||||||||
salt, err := generateUserSalt(SALT_LENGTH) | ||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||
return "", err | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
return hashCrypt256(pwd, string(salt), SHA256_PASSWORD_ITERATIONS) | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
func DecompressMariadbData(data []byte) ([]byte, error) { | ||||||||||||||||||||||||||
// algorithm always 0=zlib | ||||||||||||||||||||||||||
// algorithm := (data[pos] & 0x07) >> 4 | ||||||||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: seems
stage2
is not used anymore, so we can pass-instage2[:0]
to reuse the array under the slice.