go: add handling of argon2 to the password module
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
2e49dd58d7
commit
f8364605e4
1
go.mod
1
go.mod
@ -11,6 +11,7 @@ require (
|
|||||||
github.com/labstack/echo/v4 v4.11.1
|
github.com/labstack/echo/v4 v4.11.1
|
||||||
github.com/labstack/gommon v0.4.0
|
github.com/labstack/gommon v0.4.0
|
||||||
github.com/lib/pq v1.10.9
|
github.com/lib/pq v1.10.9
|
||||||
|
github.com/matthewhartstonge/argon2 v0.3.3
|
||||||
github.com/microcosm-cc/bluemonday v1.0.25
|
github.com/microcosm-cc/bluemonday v1.0.25
|
||||||
github.com/philandstuff/dhall-golang/v6 v6.0.2
|
github.com/philandstuff/dhall-golang/v6 v6.0.2
|
||||||
github.com/xiaoqidun/entps v0.0.0-20230712154733-ff3582a22e82
|
github.com/xiaoqidun/entps v0.0.0-20230712154733-ff3582a22e82
|
||||||
|
2
go.sum
2
go.sum
@ -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/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 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
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.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
|
78
modules/password/argon.go
Normal file
78
modules/password/argon.go
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
76
modules/password/argon_test.go
Normal file
76
modules/password/argon_test.go
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user