Initial commit
This commit is contained in:
commit
79014dd21e
22
.gitignore
vendored
Normal file
22
.gitignore
vendored
Normal 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
62
decoder/decoder.go
Normal 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
51
decoder/decoder_test.go
Normal 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
65
encoder/encoder.go
Normal 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
33
encoder/encoder_test.go
Normal 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
55
encoder/options.go
Normal 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
7
go.mod
Normal 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
4
go.sum
Normal 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=
|
Loading…
Reference in New Issue
Block a user