package lib import ( "crypto/rand" "crypto/subtle" "encoding/base64" "errors" "fmt" "strings" "golang.org/x/crypto/argon2" ) var ( ErrInvalidHash = errors.New("Hash has an invalid format") ErrIncompatibleVersion = errors.New("Incompatible version of argon2") ) type Argon2idConfig struct { raw_hash []byte salt []byte hash_len uint32 // bytes salt_len uint32 // bytes mem_cost uint32 // KiB iterations uint32 threads uint8 } func GenerateSalt(length int) ([]byte, error) { secret := make([]byte, length) _, err := rand.Read(secret) if err != nil { return nil, err } return secret, nil } func GenerateHash(password string) (string, error) { var err error config := &Argon2idConfig{ iterations: 4, mem_cost: 64 * 1024, threads: 4, hash_len: 32, } config.salt, err = GenerateSalt(16) if err != nil { return "", err } config.raw_hash = argon2.IDKey([]byte(password), config.salt, config.iterations, config.mem_cost, config.threads, config.hash_len) encoded_hash := fmt.Sprintf( "$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s", argon2.Version, config.mem_cost, config.iterations, config.threads, base64.RawStdEncoding.EncodeToString(config.salt), base64.RawStdEncoding.EncodeToString(config.raw_hash), ) return encoded_hash, nil } func ValidateHash(password string, encoded_hash string) (match bool, err error) { p, salt, hash, err := DecodeHash(encoded_hash) if err != nil { return false, err } otherHash := argon2.IDKey([]byte(password), salt, p.iterations, p.mem_cost, p.threads, p.hash_len) // subtle.ConstantTimeCompare prevents timing attacks. if subtle.ConstantTimeCompare(hash, otherHash) == 1 { return true, nil } return false, nil } func DecodeHash(encodedHash string) (p *Argon2idConfig, salt, hash []byte, err error) { vals := strings.Split(encodedHash, "$") if len(vals) != 6 { return nil, nil, nil, ErrInvalidHash } var version int _, err = fmt.Sscanf(vals[2], "v=%d", &version) if err != nil { return nil, nil, nil, err } if version != argon2.Version { return nil, nil, nil, ErrIncompatibleVersion } p = &Argon2idConfig{} _, err = fmt.Sscanf(vals[3], "m=%d,t=%d,p=%d", &p.mem_cost, &p.iterations, &p.threads) if err != nil { return nil, nil, nil, err } salt, err = base64.RawStdEncoding.Strict().DecodeString(vals[4]) if err != nil { return nil, nil, nil, err } hash, err = base64.RawStdEncoding.Strict().DecodeString(vals[5]) if err != nil { return nil, nil, nil, err } p.hash_len = uint32(len(hash)) p.salt_len = uint32(len(salt)) return p, salt, hash, nil }