2019-04-24 21:52:08 +00:00
|
|
|
package authentication
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2019-11-01 18:31:51 +00:00
|
|
|
"log"
|
2019-04-24 21:52:08 +00:00
|
|
|
"math/rand"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
2019-11-01 18:31:51 +00:00
|
|
|
"github.com/simia-tech/crypt"
|
|
|
|
)
|
2019-04-24 21:52:08 +00:00
|
|
|
|
|
|
|
// PasswordHash represents all characteristics of a password hash.
|
|
|
|
// Authelia only supports salted SHA512 method, i.e., $6$ mode.
|
|
|
|
type PasswordHash struct {
|
|
|
|
// The number of rounds.
|
|
|
|
Rounds int
|
|
|
|
// The salt with a max size of 16 characters for SHA512.
|
|
|
|
Salt string
|
|
|
|
// The password hash.
|
|
|
|
Hash string
|
|
|
|
}
|
|
|
|
|
2019-12-27 16:55:00 +00:00
|
|
|
// ParseHash extracts all characteristics of a hash given its string representation.
|
|
|
|
func ParseHash(hash string) (*PasswordHash, error) {
|
2019-04-24 21:52:08 +00:00
|
|
|
parts := strings.Split(hash, "$")
|
|
|
|
|
|
|
|
if len(parts) != 5 {
|
2019-12-27 16:55:00 +00:00
|
|
|
return nil, fmt.Errorf("Cannot parse the hash %s", hash)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Only supports salted sha 512.
|
|
|
|
if parts[1] != "6" {
|
|
|
|
return nil, fmt.Errorf("Authelia only supports salted SHA512 hashing ($6$), not $%s$", parts[1])
|
2019-04-24 21:52:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
roundsKV := strings.Split(parts[2], "=")
|
|
|
|
if len(roundsKV) != 2 {
|
2019-12-27 16:55:00 +00:00
|
|
|
return nil, errors.New("Cannot match pattern 'rounds=<int>' to find the number of rounds")
|
2019-04-24 21:52:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
rounds, err := strconv.ParseInt(roundsKV[1], 10, 0)
|
|
|
|
if err != nil {
|
2019-12-27 16:55:00 +00:00
|
|
|
return nil, fmt.Errorf("Cannot find the number of rounds from %s using pattern 'rounds=<int>'. Cause: %s", roundsKV[1], err.Error())
|
2019-04-24 21:52:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return &PasswordHash{
|
|
|
|
Rounds: int(rounds),
|
|
|
|
Salt: parts[3],
|
|
|
|
Hash: parts[4],
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// The set of letters RandomString can pick in.
|
|
|
|
var possibleLetters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
|
|
|
|
|
|
|
// RandomString generate a random string of n characters.
|
|
|
|
func RandomString(n int) string {
|
|
|
|
b := make([]rune, n)
|
|
|
|
for i := range b {
|
|
|
|
b[i] = possibleLetters[rand.Intn(len(possibleLetters))]
|
|
|
|
}
|
|
|
|
return string(b)
|
|
|
|
}
|
|
|
|
|
|
|
|
// HashPassword generate a salt and hash the password with the salt and a constant
|
|
|
|
// number of rounds.
|
2019-11-01 18:31:51 +00:00
|
|
|
func HashPassword(password string, salt string) string {
|
|
|
|
if salt == "" {
|
|
|
|
salt = fmt.Sprintf("$6$rounds=50000$%s", RandomString(16))
|
|
|
|
}
|
|
|
|
hash, err := crypt.Crypt(password, salt)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
2019-04-24 21:52:08 +00:00
|
|
|
}
|
2019-11-01 18:31:51 +00:00
|
|
|
return hash
|
2019-04-24 21:52:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// CheckPassword check a password against a hash.
|
|
|
|
func CheckPassword(password string, hash string) (bool, error) {
|
2019-12-27 16:55:00 +00:00
|
|
|
passwordHash, err := ParseHash(hash)
|
2019-04-24 21:52:08 +00:00
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
salt := fmt.Sprintf("$6$rounds=%d$%s$", passwordHash.Rounds, passwordHash.Salt)
|
2019-11-01 18:31:51 +00:00
|
|
|
pHash := HashPassword(password, salt)
|
2019-04-24 21:52:08 +00:00
|
|
|
return pHash == hash, nil
|
|
|
|
}
|