forked from 0x2E/fusion

fusion's current password mechanism is vulnerable to a timing attack: https://en.wikipedia.org/wiki/Timing_attack Because fusion checks passwords using simple character-by-character string comparison, a password attempt that begins with the correct characters will take longer to evaluate than one that starts with incorrect characters. For example, if the correct password is 'platypus123' then a password attempt of 'plates' will take longer to evaluate than 'spoons' because 'plates' and 'platypus' share a common prefix. An attacker who attempts the password 'plates' will know that they likely have the correct prefix. To prevent the timing attack, this change hashes the user's password using PBKDF2 and compares hashes using subtle.ConstantTimeCompare, which is specifically designed to prevent timing attacks.
43 lines
1.1 KiB
Go
43 lines
1.1 KiB
Go
package auth
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"crypto/subtle"
|
|
"errors"
|
|
|
|
"golang.org/x/crypto/pbkdf2"
|
|
)
|
|
|
|
var ErrPasswordTooShort = errors.New("password must be non-empty")
|
|
|
|
type HashedPassword struct {
|
|
hash []byte
|
|
}
|
|
|
|
func (hp HashedPassword) Bytes() []byte {
|
|
return hp.hash
|
|
}
|
|
|
|
func (hp HashedPassword) Equals(other HashedPassword) bool {
|
|
return subtle.ConstantTimeCompare(hp.hash, other.hash) != 0
|
|
}
|
|
|
|
func HashPassword(password string) (HashedPassword, error) {
|
|
if len(password) == 0 {
|
|
return HashedPassword{}, ErrPasswordTooShort
|
|
}
|
|
|
|
// These bytes are chosen at random. It's insecure to use a static salt to
|
|
// hash a set of passwords, but since we're only ever hashing a single
|
|
// password, using a static salt is fine. The salt prevents an attacker from
|
|
// using a rainbow table to retrieve the plaintext password from the hashed
|
|
// version, and that's all that's necessary for fusion's needs.
|
|
staticSalt := []byte{36, 129, 1, 54}
|
|
iter := 100
|
|
keyLen := 32
|
|
hash := pbkdf2.Key([]byte(password), staticSalt, iter, keyLen, sha256.New)
|
|
|
|
return HashedPassword{
|
|
hash: hash,
|
|
}, nil
|
|
}
|