package auth import ( "crypto/sha256" "encoding/base64" "golang.org/x/crypto/bcrypt" ) // saltedInput mixes the login into the password material before bcrypt hashing, using // the login as a per-user salt: a leaked hash can't be replayed against a different // login, and pre-hashing keeps the input short regardless of login/password length // (bcrypt only considers the first 72 bytes of its input). func saltedInput(login, password string) []byte { sum := sha256.Sum256([]byte(login + "\x00" + password)) return []byte(base64.StdEncoding.EncodeToString(sum[:])) } // dummyHash is compared against on unknown logins so that a failed lookup takes // roughly the same time as a real password comparison, avoiding user enumeration. var dummyHash, _ = bcrypt.GenerateFromPassword(saltedInput("dummy-login", "dummy-password"), bcrypt.DefaultCost) func Hash(login, password string) (string, error) { hash, err := bcrypt.GenerateFromPassword(saltedInput(login, password), bcrypt.DefaultCost) if err != nil { return "", err } return string(hash), nil } func Compare(login, hash, password string) bool { return bcrypt.CompareHashAndPassword([]byte(hash), saltedInput(login, password)) == nil } func CompareDummy() { _ = bcrypt.CompareHashAndPassword(dummyHash, saltedInput("dummy-login", "dummy-password")) }