Initial commit

This commit is contained in:
Euphoria Laxis 2024-07-24 19:42:25 +02:00
commit 79014dd21e
Signed by: euphoria-laxis
GPG Key ID: 6892E47DD43F59EF
8 changed files with 299 additions and 0 deletions

22
.gitignore vendored Normal file
View File

@ -0,0 +1,22 @@
### Go
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
vendor/
# Go workspace file
go.work
### GoLand
.idea/

62
decoder/decoder.go Normal file
View File

@ -0,0 +1,62 @@
package decoder
import (
"crypto/subtle"
"encoding/base64"
"fmt"
"strings"
"euphoria-laxis.fr/go-packages/argon2/encoder"
"golang.org/x/crypto/argon2"
)
type Decoder struct{}
func NewDecoder() *Decoder {
return new(Decoder)
}
func (decoder *Decoder) decodeHash(encodedHash string) (o *encoder.Options, salt, hash []byte, err error) {
values := strings.Split(encodedHash, "$")
if len(values) != 6 {
return nil, nil, nil, encoder.ErrInvalidHash
}
var version int
_, err = fmt.Sscanf(values[2], "v=%d", &version)
if err != nil {
return nil, nil, nil, err
}
if version != argon2.Version {
return nil, nil, nil, encoder.ErrIncompatibleVersion
}
o = new(encoder.Options)
_, err = fmt.Sscanf(values[3], "m=%d,t=%d,p=%d", &o.Memory, &o.Iterations, &o.Parallelism)
if err != nil {
return nil, nil, nil, err
}
salt, err = base64.RawStdEncoding.DecodeString(values[4])
if err != nil {
return nil, nil, nil, err
}
o.SaltLength = uint32(len(salt))
hash, err = base64.RawStdEncoding.DecodeString(values[5])
if err != nil {
return nil, nil, nil, err
}
o.KeyLength = uint32(len(hash))
return o, salt, hash, nil
}
func (decoder *Decoder) CompareStringToHash(password string, hashedPassword string) (match bool, err error) {
p, salt, hash, err := decoder.decodeHash(hashedPassword)
if err != nil {
return false, err
}
otherHash := argon2.IDKey([]byte(password), salt, p.Iterations, p.Memory, p.Parallelism, p.KeyLength)
if subtle.ConstantTimeCompare(hash, otherHash) == 1 {
return true, nil
}
return false, nil
}

51
decoder/decoder_test.go Normal file
View File

@ -0,0 +1,51 @@
package decoder
import (
"testing"
"euphoria-laxis.fr/go-packages/argon2/encoder"
)
func TestDecoder(t *testing.T) {
opts := []encoder.OptFunc{
encoder.SetMemory(32 * 1024), // 32 bits
encoder.SetParallelism(4), // 4 concurrent actions
encoder.SetKeyLength(32), // key length
encoder.SetSaltLength(32), // salt length
encoder.SetIterations(4), // 4 iterations, should be fast since there's 4 concurrent actions
}
e, _ := encoder.NewEncoder(opts...)
randomString, err := e.RandomString(32)
if err != nil {
t.Fatal(err)
}
var hashedString string
hashedString, err = e.HashString(randomString)
if err != nil {
t.Fatal(err)
}
d := NewDecoder()
var match bool
match, err = d.CompareStringToHash(randomString, hashedString)
if err != nil {
t.Fatal(err)
}
if !match {
t.Log("passwords comparison failed")
t.Log("passwords should match")
t.Fail()
}
randomString, err = e.RandomString(32)
if err != nil {
t.Fatal(err)
}
match, err = d.CompareStringToHash(randomString, hashedString)
if err != nil {
t.Fatal(err)
}
if match {
t.Log("passwords comparison failed")
t.Log("passwords shouldn't match")
t.Fail()
}
}

65
encoder/encoder.go Normal file
View File

@ -0,0 +1,65 @@
package encoder
import (
"crypto/rand"
"encoding/base64"
"fmt"
"golang.org/x/crypto/argon2"
)
type Encoder struct {
Options
}
func NewEncoder(opts ...OptFunc) (*Encoder, *Options) {
o := defaultOptions
for _, fn := range opts {
fn(&o)
}
return &Encoder{o}, &o
}
func (encoder *Encoder) generateRandomBytes(n uint32) ([]byte, error) {
b := make([]byte, n)
_, err := rand.Read(b)
if err != nil {
return nil, err
}
return b, nil
}
func (encoder *Encoder) HashString(password string) (encodedHash string, err error) {
salt, err := encoder.generateRandomBytes(encoder.SaltLength)
if err != nil {
return "", err
}
hash := argon2.IDKey(
[]byte(password),
salt,
encoder.Iterations,
encoder.Memory,
encoder.Parallelism,
encoder.KeyLength,
)
b64Salt := base64.RawStdEncoding.EncodeToString(salt)
b64Hash := base64.RawStdEncoding.EncodeToString(hash)
encodedHash = fmt.Sprintf(
"$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s",
argon2.Version,
encoder.Memory,
encoder.Iterations,
encoder.Parallelism,
b64Salt,
b64Hash,
)
return encodedHash, nil
}
func (encoder *Encoder) RandomString(s int) (string, error) {
b, err := encoder.generateRandomBytes(uint32(s))
return base64.URLEncoding.EncodeToString(b), err
}

33
encoder/encoder_test.go Normal file
View File

@ -0,0 +1,33 @@
package encoder
import (
"testing"
)
var (
randomString, hashedString string
opts []OptFunc
)
func TestOptions(t *testing.T) {
opts = []OptFunc{
SetMemory(32 * 1024), // 32 bits
SetParallelism(4), // 4 concurrent actions
SetKeyLength(32), // key length
SetSaltLength(32), // salt length
SetIterations(4), // 4 iterations, should be fast since there's 4 concurrent actions
}
}
func TestEncoder(t *testing.T) {
var err error
e, _ := NewEncoder(opts...)
randomString, err = e.RandomString(32)
if err != nil {
t.Fatal(err)
}
hashedString, err = e.HashString(randomString)
if err != nil {
t.Fatal(err)
}
}

55
encoder/options.go Normal file
View File

@ -0,0 +1,55 @@
package encoder
import "errors"
type Options struct {
Memory uint32
Iterations uint32
Parallelism uint8
SaltLength uint32
KeyLength uint32
}
var (
ErrInvalidHash = errors.New("the encoded hash is not in the correct format")
ErrIncompatibleVersion = errors.New("incompatible version of argon2")
defaultOptions = Options{
Memory: 64 * 1024,
Iterations: 3,
Parallelism: 2,
SaltLength: 16,
KeyLength: 32,
}
)
type OptFunc func(*Options)
func SetMemory(memory uint32) OptFunc {
return func(options *Options) {
options.Memory = memory
}
}
func SetIterations(iterations uint32) OptFunc {
return func(options *Options) {
options.Iterations = iterations
}
}
func SetParallelism(parallelism uint8) OptFunc {
return func(options *Options) {
options.Parallelism = parallelism
}
}
func SetSaltLength(saltLength uint32) OptFunc {
return func(options *Options) {
options.SaltLength = saltLength
}
}
func SetKeyLength(keyLength uint32) OptFunc {
return func(options *Options) {
options.KeyLength = keyLength
}
}

7
go.mod Normal file
View File

@ -0,0 +1,7 @@
module euphoria-laxis.fr/go-packages/argon2
go 1.22
require golang.org/x/crypto v0.25.0
require golang.org/x/sys v0.22.0 // indirect

4
go.sum Normal file
View File

@ -0,0 +1,4 @@
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=