package shared import ( "bytes" "crypto/rand" "encoding/base64" "errors" "golang.org/x/crypto/argon2" "runtime" "strings" ) type Hash struct { Hash []byte Salt []byte } func (h *Hash) String() string { return base64.StdEncoding.EncodeToString(h.Salt) + ";" + base64.StdEncoding.EncodeToString(h.Hash) } func NewHash(pass string, salt []byte) (hash *Hash, err error) { if salt == nil { salt = make([]byte, 16) _, err = rand.Read(salt) if err != nil { return nil, err } } var parallelism uint8 if runtime.NumCPU() > 255 { parallelism = 255 } else { parallelism = uint8(runtime.NumCPU()) // #nosec G115 -- False positive, if this does happen, blame radiation. } argonHash := argon2.IDKey([]byte(pass), salt, 6, 64*1024, parallelism, 45) return &Hash{ Salt: salt, Hash: argonHash, }, nil } var ErrInvalidHashStr = errors.New("invalid hash string") func CompareHash(hash string, pass string) (matches bool, err error) { hashSplit := strings.Split(hash, ";") if len(hashSplit) != 2 { return false, ErrInvalidHashStr } salt, err := base64.StdEncoding.DecodeString(hashSplit[0]) if err != nil { return false, err } decodedHash, err := base64.StdEncoding.DecodeString(hashSplit[1]) if err != nil { return false, err } hashInfo, err := NewHash(pass, salt) if err != nil { return false, err } if !bytes.Equal(decodedHash, hashInfo.Hash) { return false, nil } return true, nil }