Files
rmm-openwrt/server/internal/authn/password.go

88 lines
2.2 KiB
Go

package authn
import (
"crypto/rand"
"crypto/subtle"
"encoding/base64"
"errors"
"fmt"
"strconv"
"strings"
"golang.org/x/crypto/argon2"
)
const (
argonTime uint32 = 2
argonMemory uint32 = 32 * 1024
argonThreads uint8 = 1
argonKeyLen uint32 = 32
saltLen = 16
)
func HashPassword(password string) (string, error) {
if len(password) < 12 {
return "", errors.New("password must contain at least 12 characters")
}
if len(password) > 1024 {
return "", errors.New("password must not exceed 1024 bytes")
}
salt := make([]byte, saltLen)
if _, err := rand.Read(salt); err != nil {
return "", err
}
hash := argon2.IDKey([]byte(password), salt, argonTime, argonMemory, argonThreads, argonKeyLen)
return fmt.Sprintf("$argon2id$v=19$m=%d,t=%d,p=%d$%s$%s",
argonMemory,
argonTime,
argonThreads,
base64.RawStdEncoding.EncodeToString(salt),
base64.RawStdEncoding.EncodeToString(hash),
), nil
}
func VerifyPassword(encoded, password string) bool {
if len(password) > 1024 {
return false
}
parts := strings.Split(encoded, "$")
if len(parts) != 6 || parts[1] != "argon2id" || parts[2] != "v=19" {
return false
}
params := strings.Split(parts[3], ",")
if len(params) != 3 {
return false
}
memory, ok := parseParam(params[0], "m=")
if !ok || memory < 8*1024 || memory > 256*1024 {
return false
}
timeCost, ok := parseParam(params[1], "t=")
if !ok || timeCost < 1 || timeCost > 10 {
return false
}
threads, ok := parseParam(params[2], "p=")
if !ok || threads < 1 || threads > 16 {
return false
}
salt, err := base64.RawStdEncoding.DecodeString(parts[4])
if err != nil || len(salt) < 16 || len(salt) > 64 {
return false
}
expected, err := base64.RawStdEncoding.DecodeString(parts[5])
if err != nil || len(expected) < 16 || len(expected) > 64 {
return false
}
actual := argon2.IDKey([]byte(password), salt, uint32(timeCost), uint32(memory), uint8(threads), uint32(len(expected)))
return subtle.ConstantTimeCompare(actual, expected) == 1
}
func parseParam(value, prefix string) (uint64, bool) {
raw, ok := strings.CutPrefix(value, prefix)
if !ok {
return 0, false
}
n, err := strconv.ParseUint(raw, 10, 32)
return n, err == nil
}