go: add handling of argon2 to the password module
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
surtur 2023-08-19 04:28:00 +02:00
parent 2e49dd58d7
commit f8364605e4
Signed by: wanderer
SSH Key Fingerprint: SHA256:MdCZyJ2sHLltrLBp0xQO0O1qTW9BT/xl5nXkDvhlMCI
4 changed files with 157 additions and 0 deletions

1
go.mod
View File

@ -11,6 +11,7 @@ require (
github.com/labstack/echo/v4 v4.11.1
github.com/labstack/gommon v0.4.0
github.com/lib/pq v1.10.9
github.com/matthewhartstonge/argon2 v0.3.3
github.com/microcosm-cc/bluemonday v1.0.25
github.com/philandstuff/dhall-golang/v6 v6.0.2
github.com/xiaoqidun/entps v0.0.0-20230712154733-ff3582a22e82

2
go.sum
View File

@ -73,6 +73,8 @@ github.com/leanovate/gopter v0.2.5-0.20190402064358-634a59d12406 h1:+OUpk+IVvmKU
github.com/leanovate/gopter v0.2.5-0.20190402064358-634a59d12406/go.mod h1:gNcbPWNEWRe4lm+bycKqxUYoH5uoVje5SkOJ3uoLer8=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/matthewhartstonge/argon2 v0.3.3 h1:38/hupgfzqO2UGxqXqmSqErE8KJvQnIxWWg7IXUqWgQ=
github.com/matthewhartstonge/argon2 v0.3.3/go.mod h1:W2fhVs3+4FGxqDiap9SxxwNF/0SOVYcITpqDZe8RrhY=
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=

78
modules/password/argon.go Normal file
View File

@ -0,0 +1,78 @@
// Copyright 2023 wanderer <a_mirre at utb dot cz>
// SPDX-License-Identifier: AGPL-3.0-only
package password
import (
"time"
"github.com/matthewhartstonge/argon2"
"golang.org/x/exp/rand"
)
var (
// argon provides the package with current config. the one currently set on
// init is the memory-hard argon2.RecommendedConfig().
argon argon2.Config
// r stores a seeded *rand.Rand so that the generated salts are more close
// to non-deterministic.
r = rand.New(rand.NewSource(uint64(time.Now().UnixNano())))
)
// Argon accepts a password string and returns hash and salt byte slices, or an
// error.
func Argon(password string) (*argon2.Raw, error) {
salt := make([]byte, argon.SaltLength)
err := genSalt(salt)
if err != nil {
return nil, err
}
hash, err := argon.Hash([]byte(password), salt)
if err != nil {
return nil, err
}
return &hash, nil
}
// ArgonDecode attempts to decode the given byte slice into an argon raw
// struct.
func ArgonDecode(digest []byte) (*argon2.Raw, error) {
hash, err := argon2.Decode(digest)
if err != nil {
return nil, err
}
return &hash, nil
}
// ArgonVerify checks that the provided password matches the one used to create
// the provided digest, returns a bool and an error.
func ArgonVerify(password string, digest []byte) (bool, error) {
return argon2.VerifyEncoded([]byte(password), digest)
}
// genSalt fills slice b with len(b) random bytes.
func genSalt(b []byte) error {
_, err := r.Read(b)
if err != nil {
return err
}
return nil
}
func init() {
// TODO: hide this behind a config flag someday.
noop := false
if noop {
// 64MiB peak memory usage.
argon = argon2.DefaultConfig()
} else {
// 2GiB peak memory usage.
argon = argon2.RecommendedDefaults()
}
}

View File

@ -0,0 +1,76 @@
// Copyright 2023 wanderer <a_mirre at utb dot cz>
// SPDX-License-Identifier: AGPL-3.0-only
package password
import (
"testing"
"github.com/matthewhartstonge/argon2"
)
func TestArgon(t *testing.T) {
// set lighter defaults for the tests.
argon = argon2.MemoryConstrainedDefaults()
passwd := "password0123#@:"
raw, err := Argon(passwd)
if err != nil {
t.Error(err)
}
// save the salt generated when hashing the password using our own func,
// it'll be reused when reproducing manual hash creation.
salt := raw.Salt
manualRaw, err := argon.Hash([]byte(passwd), salt)
if err != nil {
t.Errorf("failed to create a hash: %q", err)
}
want := raw.Encode()
got := manualRaw.Encode()
t.Logf("want: %q, got: %q", want, got)
if len(want) != len(got) {
t.Errorf("password hashes differ, want: %q, got: %q", want, got)
}
for i := range got {
if got[i] != want[i] {
t.Logf("password hashes differ, want: %q, got: %q", want, got)
break
}
}
if _, err := ArgonVerify(passwd, want); err != nil {
t.Errorf("passwords don't equal: %q", err)
} else {
t.Log("passwords equal")
}
if _, err := ArgonVerify(passwd, got); err != nil {
t.Errorf("passwords don't equal for the manually created hash: %q", err)
} else {
t.Log("passwords equal for the manually created hash")
}
dw, err := ArgonDecode(want)
if err != nil {
t.Errorf("could not decode the digest: %q", err)
}
dg, err := ArgonDecode(got)
if err != nil {
t.Errorf("could not decode the digest: %q", err)
}
for i := range dw.Hash {
if dw.Hash[i] != dg.Hash[i] {
t.Errorf("hash values don't equal: want: %q, got: %q", dw.Hash, dg.Hash)
break
}
}
}