initial commit
This commit is contained in:
187
pkg/argon2/argon2.go
Normal file
187
pkg/argon2/argon2.go
Normal file
@@ -0,0 +1,187 @@
|
||||
package argon2
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/subtle"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/argon2"
|
||||
|
||||
"github.com/sour-is/go-passwd"
|
||||
)
|
||||
|
||||
type argon struct {
|
||||
version uint8
|
||||
time uint32
|
||||
memory uint32
|
||||
threads uint8
|
||||
keyLen uint32
|
||||
saltLen uint32
|
||||
|
||||
name string
|
||||
keyFn func(password, salt []byte, time, memory uint32, threads uint8, keyLen uint32) []byte
|
||||
}
|
||||
|
||||
var All = []passwd.Passwder{Argon2i, Argon2id}
|
||||
|
||||
// Argon2i sets default recommended values.
|
||||
var Argon2i = NewArgon2i(3, 32*1024, 4, 32, 16)
|
||||
|
||||
// Argon2id sets default recommended values.
|
||||
var Argon2id = NewArgon2id(1, 64*1024, 4, 32, 16)
|
||||
|
||||
// NewArgon2i creates a new
|
||||
func NewArgon2i(
|
||||
time uint32,
|
||||
memory uint32,
|
||||
threads uint8,
|
||||
keyLen uint32,
|
||||
saltLen uint32,
|
||||
) *argon {
|
||||
return &argon{
|
||||
version: argon2.Version,
|
||||
time: time,
|
||||
memory: memory,
|
||||
threads: threads,
|
||||
keyLen: keyLen,
|
||||
saltLen: saltLen,
|
||||
name: "argon2i",
|
||||
keyFn: argon2.Key,
|
||||
}
|
||||
}
|
||||
|
||||
// NewArgon2i creates a new
|
||||
func NewArgon2id(
|
||||
time uint32,
|
||||
memory uint32,
|
||||
threads uint8,
|
||||
keyLen uint32,
|
||||
saltLen uint32,
|
||||
) *argon {
|
||||
return &argon{
|
||||
version: argon2.Version,
|
||||
time: time,
|
||||
memory: memory,
|
||||
threads: threads,
|
||||
keyLen: keyLen,
|
||||
saltLen: saltLen,
|
||||
name: "argon2id",
|
||||
keyFn: argon2.IDKey,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *argon) Passwd(pass string, check string) (string, error) {
|
||||
var args *pwArgs
|
||||
var err error
|
||||
|
||||
if check == "" {
|
||||
args = p.defaultArgs()
|
||||
_, err := rand.Read(args.salt)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
args.hash = p.keyFn([]byte(pass), args.salt, args.time, args.memory, args.threads, args.keyLen)
|
||||
} else {
|
||||
args, err = p.parseArgs(check)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
hash := p.keyFn([]byte(pass), args.salt, args.time, args.memory, args.threads, args.keyLen)
|
||||
|
||||
if subtle.ConstantTimeCompare(hash, args.hash) == 0 {
|
||||
return "", passwd.ErrNoMatch
|
||||
}
|
||||
}
|
||||
|
||||
return args.String(), nil
|
||||
}
|
||||
func (p *argon) ApplyPasswd(passwd *passwd.Passwd) {
|
||||
passwd.Register(p.name, p)
|
||||
}
|
||||
func (p *argon) defaultArgs() *pwArgs {
|
||||
return &pwArgs{
|
||||
name: p.name,
|
||||
version: p.version,
|
||||
time: p.time,
|
||||
memory: p.memory,
|
||||
threads: p.threads,
|
||||
keyLen: p.keyLen,
|
||||
salt: make([]byte, p.saltLen),
|
||||
}
|
||||
}
|
||||
func (p *argon) parseArgs(hash string) (*pwArgs, error) {
|
||||
pfx := "$" + p.name + "$"
|
||||
|
||||
if !strings.HasPrefix(hash, pfx) {
|
||||
return nil, fmt.Errorf("%w: missing prefix", passwd.ErrBadHash)
|
||||
}
|
||||
hash = strings.TrimPrefix(hash, pfx)
|
||||
args, hash, ok := strings.Cut(hash, "$")
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%w: missing args", passwd.ErrBadHash)
|
||||
}
|
||||
salt, hash, ok := strings.Cut(hash, "$")
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%w: missing salt", passwd.ErrBadHash)
|
||||
}
|
||||
|
||||
var err error
|
||||
pass := p.defaultArgs()
|
||||
pass.salt, err = base64.RawStdEncoding.DecodeString(salt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: corrupt salt part", passwd.ErrBadHash)
|
||||
}
|
||||
pass.hash, err = base64.RawStdEncoding.DecodeString(hash)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: corrupt hash part", passwd.ErrBadHash)
|
||||
}
|
||||
|
||||
pass.name = p.name
|
||||
pass.keyLen = uint32(len(pass.hash))
|
||||
|
||||
for _, part := range strings.Split(args, ",") {
|
||||
if k, v, ok := strings.Cut(part, "="); ok {
|
||||
switch k {
|
||||
case "v":
|
||||
if i, err := strconv.ParseUint(v, 10, 8); err == nil {
|
||||
pass.version = uint8(i)
|
||||
}
|
||||
case "m":
|
||||
if i, err := strconv.ParseUint(v, 10, 32); err == nil {
|
||||
pass.memory = uint32(i)
|
||||
}
|
||||
case "t":
|
||||
if i, err := strconv.ParseUint(v, 10, 32); err == nil {
|
||||
pass.time = uint32(i)
|
||||
}
|
||||
case "p":
|
||||
if i, err := strconv.ParseUint(v, 10, 8); err == nil {
|
||||
pass.threads = uint8(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pass, nil
|
||||
}
|
||||
|
||||
type pwArgs struct {
|
||||
name string
|
||||
version uint8
|
||||
time uint32
|
||||
memory uint32
|
||||
threads uint8
|
||||
keyLen uint32
|
||||
salt []byte
|
||||
hash []byte
|
||||
}
|
||||
|
||||
func (p *pwArgs) String() string {
|
||||
salt := base64.RawStdEncoding.EncodeToString(p.salt)
|
||||
hash := base64.RawStdEncoding.EncodeToString(p.hash)
|
||||
|
||||
return fmt.Sprintf("$%s$v=%d,m=%d,t=%d,p=%d$%s$%s", p.name, p.version, p.memory, p.time, p.threads, salt, hash)
|
||||
}
|
||||
40
pkg/argon2/argon2_test.go
Normal file
40
pkg/argon2/argon2_test.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package argon2_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/matryer/is"
|
||||
|
||||
"github.com/sour-is/go-passwd"
|
||||
"github.com/sour-is/go-passwd/pkg/argon2"
|
||||
)
|
||||
|
||||
func TestPasswdHash(t *testing.T) {
|
||||
type testCase struct {
|
||||
pass, hash string
|
||||
}
|
||||
|
||||
tests := []testCase{}
|
||||
algos := argon2.All
|
||||
|
||||
is := is.New(t)
|
||||
// Generate additional test cases for each algo.
|
||||
for _, algo := range algos {
|
||||
hash, err := algo.Passwd("passwd", "")
|
||||
is.NoErr(err)
|
||||
tests = append(tests, testCase{"passwd", hash})
|
||||
}
|
||||
|
||||
pass := passwd.New(algos...)
|
||||
|
||||
for i, tt := range tests {
|
||||
t.Run(fmt.Sprint("Test-", i), func(t *testing.T) {
|
||||
is := is.New(t)
|
||||
|
||||
hash, err := pass.Passwd(tt.pass, tt.hash)
|
||||
is.NoErr(err)
|
||||
is.Equal(hash, tt.hash)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user