searcherside/core/services/argon2id_hasher.go
Peter Kurfer 9ea9a8f658
Some checks failed
Go build / build (push) Failing after 1m58s
feat: continue basic setup
- setup ent scheme
- add command to create users
- document API
- add helpers to create migrations
- add command to run migrations
- add basic compose file
2024-06-19 21:19:37 +02:00

143 lines
3.3 KiB
Go

package services
import (
"bytes"
"crypto/rand"
"errors"
"fmt"
"strconv"
"golang.org/x/crypto/argon2"
"code.icb4dc0.de/prskr/searcherside/core/ports"
)
const (
argon2idSaltLength = 16
argon2idKeyLength uint32 = 32
)
var _ ports.PasswordHashAlgorithm = (*Argon2IDHashAlgorithm)(nil)
var ErrPasswordHashMismatch = errors.New("password hash mismatch")
type Argon2IDHashAlgorithm struct {
Argon2Params
}
func (a *Argon2IDHashAlgorithm) Hash(password []byte) ([]byte, error) {
params := a.Argon2Params
if params == (Argon2Params{}) {
params = DefaultArgon2Params
}
salt := make([]byte, argon2idSaltLength)
_, _ = rand.Read(salt)
pwh := &PasswordHash{
Hash: argon2.IDKey(password, salt, params.Iterations, params.Memory, params.Threads, argon2idKeyLength),
Salt: salt,
Params: &a.Argon2Params,
}
return pwh.MarshalText()
}
func (a *Argon2IDHashAlgorithm) Validate(password []byte, hash []byte) error {
var pwh PasswordHash
if err := pwh.UnmarshalText(hash); err != nil {
return fmt.Errorf("failed parse hash: %w", err)
}
params, ok := pwh.Params.(*Argon2Params)
if !ok {
return fmt.Errorf("hash params type mismatch: %T", pwh.Params)
}
actual := argon2.IDKey(password, pwh.Salt, params.Iterations, params.Memory, params.Threads, argon2idKeyLength)
if !bytes.Equal(actual, pwh.Hash) {
return ErrPasswordHashMismatch
}
return nil
}
var (
_ hashParams = (*Argon2Params)(nil)
)
var DefaultArgon2Params = Argon2Params{
Iterations: 3,
Memory: 64 * 1024,
Threads: 2,
}
type Argon2Params struct {
Iterations uint32
Memory uint32
Threads uint8
}
func (*Argon2Params) Algorithm() string {
return "argon2id"
}
// MarshalText implements encoding.TextMarshaler.
func (p *Argon2Params) MarshalText() (text []byte, err error) {
if p == nil || *p == (Argon2Params{}) {
return DefaultArgon2Params.MarshalText()
}
return []byte(fmt.Sprintf("v=19$m=%d,t=%d,p=%d", p.Memory, p.Iterations, p.Threads)), nil
}
// UnmarshalText implements encoding.TextUnmarshaler.
func (p *Argon2Params) UnmarshalText(text []byte) error {
parts := bytes.Split(text, []byte(","))
if len(parts) < 3 {
return fmt.Errorf("invalid argon2 parameter: %s", text)
}
for i := range parts {
keyValue := bytes.Split(parts[i], []byte("="))
if len(keyValue) != 2 {
return fmt.Errorf("invalid argon2 parameter: %s", parts[i])
}
switch string(keyValue[0]) {
case "v":
continue
case "m":
parsed, err := strconv.ParseUint(string(keyValue[1]), 10, 32)
if err != nil {
return fmt.Errorf("failed to parse argon2 memory value %s: %w", keyValue[1], err)
}
p.Memory = uint32(parsed)
case "t":
parsed, err := strconv.ParseUint(string(keyValue[1]), 10, 32)
if err != nil {
return fmt.Errorf("failed to parse argon2 iteration value %s: %w", keyValue[1], err)
}
p.Iterations = uint32(parsed)
case "p":
parsed, err := strconv.ParseUint(string(keyValue[1]), 10, 8)
if err != nil {
return fmt.Errorf("failed to parse argon2 threads value %s: %w", keyValue[1], err)
}
p.Threads = uint8(parsed)
default:
return fmt.Errorf("unexpected parameter key: %s", keyValue[0])
}
}
return nil
}
func paramsForAlgorithm(alg string) hashParams {
switch alg {
case "argon2id":
return new(Argon2Params)
default:
return nil
}
}