80 lines
1.4 KiB
Go
80 lines
1.4 KiB
Go
|
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
|
||
|
|
||
|
}
|