70 lines
1.6 KiB
Go
70 lines
1.6 KiB
Go
package auth
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"git.handmade.network/hmn/hmn/src/oops"
|
|
"golang.org/x/crypto/pbkdf2"
|
|
)
|
|
|
|
type HashAlgorithm string
|
|
|
|
const (
|
|
PBKDF2_SHA256 = "pbkdf2_sha256"
|
|
)
|
|
|
|
const PKBDF2KeyLength = 64
|
|
|
|
type HashedPassword struct {
|
|
Algorithm HashAlgorithm
|
|
Iterations int
|
|
Salt string
|
|
Hash string
|
|
}
|
|
|
|
func ParseDjangoPasswordString(s string) (HashedPassword, error) {
|
|
pieces := strings.SplitN(s, "$", 4)
|
|
if len(pieces) < 4 {
|
|
return HashedPassword{}, oops.New(nil, "unrecognized password string format")
|
|
}
|
|
|
|
iterations, err := strconv.Atoi(pieces[1])
|
|
if err != nil {
|
|
return HashedPassword{}, oops.New(err, "could not parse password iterations")
|
|
}
|
|
|
|
return HashedPassword{
|
|
Algorithm: HashAlgorithm(pieces[0]),
|
|
Iterations: iterations,
|
|
Salt: pieces[2],
|
|
Hash: pieces[3],
|
|
}, nil
|
|
}
|
|
|
|
func CheckPassword(password string, hashedPassword HashedPassword) (bool, error) {
|
|
switch hashedPassword.Algorithm {
|
|
case PBKDF2_SHA256:
|
|
decoded, err := base64.StdEncoding.DecodeString(hashedPassword.Hash)
|
|
if err != nil {
|
|
return false, oops.New(nil, "failed to get key length of hashed password")
|
|
}
|
|
|
|
newHash := pbkdf2.Key(
|
|
[]byte(password),
|
|
[]byte(hashedPassword.Salt),
|
|
hashedPassword.Iterations,
|
|
len(decoded),
|
|
sha256.New,
|
|
)
|
|
newHashEncoded := base64.StdEncoding.EncodeToString(newHash)
|
|
|
|
return bytes.Equal([]byte(newHashEncoded), []byte(hashedPassword.Hash)), nil
|
|
default:
|
|
return false, oops.New(nil, "unrecognized password hash algorithm: %s", hashedPassword.Algorithm)
|
|
}
|
|
}
|