144 lines
3.3 KiB
Go
144 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
|
||
|
}
|
||
|
}
|