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