Skip to content
Snippets Groups Projects
Commit e014cdf8 authored by ale's avatar ale
Browse files

Upgrade ai3/go-common

parent a8e1c017
No related branches found
No related tags found
No related merge requests found
Pipeline #87645 passed
......@@ -3,7 +3,7 @@ module git.autistici.org/ula/nginx-authenticator
go 1.22.9
require (
git.autistici.org/ai3/go-common v0.0.0-20241017171051-880a2c5ae7f4
git.autistici.org/ai3/go-common v0.0.0-20250125130542-62b40adde91d
github.com/coreos/go-systemd/v22 v22.5.0
)
......
......@@ -6,15 +6,15 @@ import (
"encoding/hex"
"errors"
"fmt"
"log"
"strconv"
"strings"
"golang.org/x/crypto/argon2"
)
var (
argonKeyLen uint32 = 32
const (
argonLegacyKeySize = 32
argonDefaultKeySize = 16
argonSaltLen = 16
)
......@@ -29,9 +29,11 @@ type argon2PasswordHash struct {
// newArgon2PasswordHash returns an Argon2i-based PasswordHash using the
// specified parameters for time, memory, and number of threads.
func newArgon2PasswordHash(time, mem uint32, threads uint8, codec argon2Codec) PasswordHash {
func newArgon2PasswordHash(kind string, keySize int, time, mem uint32, threads uint8, codec argon2Codec) PasswordHash {
return &argon2PasswordHash{
params: argon2Params{
KeySize: keySize,
Kind: kind,
Time: time,
Memory: mem,
Threads: threads,
......@@ -41,8 +43,8 @@ func newArgon2PasswordHash(time, mem uint32, threads uint8, codec argon2Codec) P
}
// NewArgon2 returns an Argon2i-based PasswordHash using the default parameters.
func NewArgon2() PasswordHash {
return NewArgon2WithParams(
func NewArgon2Legacy() PasswordHash {
return NewArgon2LegacyWithParams(
defaultArgon2Params.Time,
defaultArgon2Params.Memory,
defaultArgon2Params.Threads,
......@@ -51,8 +53,8 @@ func NewArgon2() PasswordHash {
// NewArgon2WithParams returns an Argon2i-based PasswordHash using the
// specified parameters for time, memory, and number of threads.
func NewArgon2WithParams(time, mem uint32, threads uint8) PasswordHash {
return newArgon2PasswordHash(time, mem, threads, &a2Codec{})
func NewArgon2LegacyWithParams(time, mem uint32, threads uint8) PasswordHash {
return newArgon2PasswordHash(kindArgon2I, argonLegacyKeySize, time, mem, threads, &a2LegacyCodec{})
}
// NewArgon2Std returns an Argon2i-based PasswordHash that conforms
......@@ -65,12 +67,12 @@ func NewArgon2Std() PasswordHash {
)
}
// NewArgon2StdWithParams returns an Argon2i-based PasswordHash using
// NewArgon2StdWithParams returns an Argon2id-based PasswordHash using
// the specified parameters for time, memory, and number of
// threads. This will use the string encoding ("$argon2$") documented
// threads. This will use the string encoding ("$argon2id$") documented
// in the argon2 reference implementation.
func NewArgon2StdWithParams(time, mem uint32, threads uint8) PasswordHash {
return newArgon2PasswordHash(time, mem, threads, &argon2StdCodec{})
return newArgon2PasswordHash(kindArgon2ID, argonDefaultKeySize, time, mem, threads, &argon2StdCodec{})
}
// ComparePassword returns true if the given password matches the
......@@ -80,28 +82,53 @@ func (s *argon2PasswordHash) ComparePassword(encrypted, password string) bool {
if err != nil {
return false
}
dk2 := argon2.Key([]byte(password), salt, params.Time, params.Memory, params.Threads, argonKeyLen)
dk2 := params.hash(password, salt)
return subtle.ConstantTimeCompare(dk, dk2) == 1
}
// Encrypt the given password with the Argon2 algorithm.
func (s *argon2PasswordHash) Encrypt(password string) string {
salt := getRandomBytes(argonSaltLen)
dk := argon2.Key([]byte(password), salt, s.params.Time, s.params.Memory, s.params.Threads, argonKeyLen)
dk := s.params.hash(password, salt)
return s.codec.encodeArgon2Hash(s.params, salt, dk)
}
const (
kindArgon2I = "argon2i"
kindArgon2ID = "argon2id"
)
type argon2Params struct {
Kind string
KeySize int
Time uint32
Memory uint32
Threads uint8
}
func (p argon2Params) hash(password string, salt []byte) []byte {
if p.KeySize == 0 {
panic("key size is 0")
}
switch p.Kind {
case kindArgon2I:
return argon2.Key([]byte(password), salt, p.Time, p.Memory, p.Threads, uint32(p.KeySize))
case kindArgon2ID:
return argon2.IDKey([]byte(password), salt, p.Time, p.Memory, p.Threads, uint32(p.KeySize))
default:
panic("unknown argon2 hash kind")
}
}
// Default Argon2 parameters are tuned for a high-traffic
// authentication service (<1ms per operation).
var defaultArgon2Params = argon2Params{
Kind: kindArgon2ID,
KeySize: 16,
Time: 1,
Memory: 4 * 1024,
Memory: 64 * 1024,
Threads: 4,
}
......@@ -110,13 +137,14 @@ type argon2Codec interface {
decodeArgon2Hash(string) (argon2Params, []byte, []byte, error)
}
type a2Codec struct{}
// Argon2i legacy encoding, do not use.
type a2LegacyCodec struct{}
func (*a2Codec) encodeArgon2Hash(params argon2Params, salt, dk []byte) string {
func (*a2LegacyCodec) encodeArgon2Hash(params argon2Params, salt, dk []byte) string {
return fmt.Sprintf("$a2$%d$%d$%d$%x$%x", params.Time, params.Memory, params.Threads, salt, dk)
}
func (*a2Codec) decodeArgon2Hash(s string) (params argon2Params, salt []byte, dk []byte, err error) {
func (*a2LegacyCodec) decodeArgon2Hash(s string) (params argon2Params, salt []byte, dk []byte, err error) {
if !strings.HasPrefix(s, "$a2$") {
err = errors.New("not an Argon2 password hash")
return
......@@ -128,6 +156,8 @@ func (*a2Codec) decodeArgon2Hash(s string) (params argon2Params, salt []byte, dk
return
}
params.Kind = kindArgon2I
var i uint64
if i, err = strconv.ParseUint(parts[0], 10, 32); err != nil {
......@@ -149,16 +179,36 @@ func (*a2Codec) decodeArgon2Hash(s string) (params argon2Params, salt []byte, dk
if err != nil {
return
}
dk, err = hex.DecodeString(parts[4])
if err != nil {
return
}
params.KeySize = len(dk)
switch len(dk) {
case 16, 24, 32:
default:
err = errors.New("bad key size")
}
return
}
// Standard Argon2 encoding as per the reference implementation in
// https://github.com/P-H-C/phc-winner-argon2/blob/4ac8640c2adc1257677d27d3f833c8d1ee68c7d2/src/encoding.c#L242-L252
type argon2StdCodec struct{}
const argon2HashVersionStr = "v=19"
func (*argon2StdCodec) encodeArgon2Hash(params argon2Params, salt, dk []byte) string {
encSalt := base64.RawStdEncoding.EncodeToString(salt)
encDK := base64.RawStdEncoding.EncodeToString(dk)
return fmt.Sprintf("$argon2i$v=19$m=%d,t=%d,p=%d$%s$%s", params.Memory, params.Time, params.Threads, encSalt, encDK)
return fmt.Sprintf(
"$%s$%s$m=%d,t=%d,p=%d$%s$%s",
params.Kind, argon2HashVersionStr,
params.Memory, params.Time, params.Threads,
encSalt, encDK)
}
func parseArgon2HashParams(s string) (params argon2Params, err error) {
......@@ -182,7 +232,7 @@ func parseArgon2HashParams(s string) (params argon2Params, err error) {
i, err = strconv.ParseUint(kv[1], 10, 8)
params.Threads = uint8(i)
default:
err = errors.New("unknown parameter in hash")
err = fmt.Errorf("unknown parameter '%s' in hash", kv[0])
}
if err != nil {
return
......@@ -192,30 +242,46 @@ func parseArgon2HashParams(s string) (params argon2Params, err error) {
}
func (*argon2StdCodec) decodeArgon2Hash(s string) (params argon2Params, salt []byte, dk []byte, err error) {
if !strings.HasPrefix(s, "$argon2i$") {
var kind string
switch {
case strings.HasPrefix(s, "$argon2i$"):
kind = kindArgon2I
case strings.HasPrefix(s, "$argon2id$"):
kind = kindArgon2ID
default:
err = errors.New("not an Argon2 password hash")
return
}
parts := strings.SplitN(s[9:], "$", 4)
if len(parts) != 4 {
parts := strings.SplitN(s, "$", 6)
if len(parts) != 6 {
err = errors.New("bad encoding")
return
}
if parts[0] != "v=19" {
if parts[2] != argon2HashVersionStr {
err = errors.New("bad argon2 hash version")
return
}
params, err = parseArgon2HashParams(parts[1])
params, err = parseArgon2HashParams(parts[3])
if err != nil {
return
}
if salt, err = base64.RawStdEncoding.DecodeString(parts[2]); err != nil {
params.Kind = kind
if salt, err = base64.RawStdEncoding.DecodeString(parts[4]); err != nil {
return
}
if dk, err = base64.RawStdEncoding.DecodeString(parts[5]); err != nil {
return
}
dk, err = base64.RawStdEncoding.DecodeString(parts[3])
log.Printf("params: %+v", params)
params.KeySize = len(dk)
switch len(dk) {
case 16, 24, 32:
default:
err = errors.New("bad key size")
}
return
}
......@@ -4,7 +4,7 @@
// The format is the well-known dollar-separated field string,
// extended with optional algorithm-specific parameters:
//
// $id[$params...]$salt$encrypted
// $id[$params...]$salt$encrypted
//
// We extend 'id' beyond the values supported by the libc crypt(3)
// function with the following hashing algorithms:
......@@ -16,9 +16,8 @@
// the parameterized benchmarks are named with
// time/memory(MB)/threads. For nicer results:
//
// go test -bench=Argon2 -run=none . 2>&1 | \
// awk '/^Bench/ {ops=1000000000 / $3; print $1 " " ops " ops/sec"}'
//
// go test -bench=Argon2 -run=none . 2>&1 | \
// awk '/^Bench/ {ops=1000000000 / $3; print $1 " " ops " ops/sec"}'
package pwhash
import (
......@@ -49,12 +48,13 @@ func getRandomBytes(n int) []byte {
// A registry of default handlers for decoding passwords.
var prefixRegistry = map[string]PasswordHash{
"$1$": NewSystemCrypt(),
"$5$": NewSystemCrypt(),
"$6$": NewSystemCrypt(),
"$s$": NewScrypt(),
"$a2$": NewArgon2(),
"$argon2i$": NewArgon2Std(),
"$1$": NewSystemCrypt(),
"$5$": NewSystemCrypt(),
"$6$": NewSystemCrypt(),
"$s$": NewScrypt(),
"$a2$": NewArgon2Legacy(),
"$argon2i$": NewArgon2Std(),
"$argon2id$": NewArgon2Std(),
}
// ComparePassword returns true if the given password matches the
......@@ -65,6 +65,7 @@ func ComparePassword(encrypted, password string) bool {
return h.ComparePassword(encrypted, password)
}
}
return false
}
......@@ -73,7 +74,7 @@ func ComparePassword(encrypted, password string) bool {
var DefaultEncryptAlgorithm PasswordHash
func init() {
DefaultEncryptAlgorithm = NewArgon2()
DefaultEncryptAlgorithm = NewArgon2Std()
}
// Encrypt will encrypt a password with the default algorithm.
......
# git.autistici.org/ai3/go-common v0.0.0-20241017171051-880a2c5ae7f4
# git.autistici.org/ai3/go-common v0.0.0-20250125130542-62b40adde91d
## explicit; go 1.21.0
git.autistici.org/ai3/go-common/pwhash
# github.com/amoghe/go-crypt v0.0.0-20220222110647-20eada5f5964
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment