package argon2 import ( "crypto/subtle" "encoding/base64" "errors" "fmt" "strings" "golang.org/x/crypto/argon2" ) var ( ErrInvalidHash = errors.New("argon2id: hash is not in the correct format") ErrIncompatibleVariant = errors.New("argon2id: incompatible variant of argon2") ErrIncompatibleVersion = errors.New("argon2id: incompatible version of argon2") ) func Verify(text, hash string) (bool, error) { params, salt, key, err := DecodeHash(hash) if err != nil { return false, err } otherKey := argon2.IDKey([]byte(text), salt, params.Iterations, params.Memory, params.Parallelism, params.KeyLength) keyLen := int32(len(key)) otherKeyLen := int32(len(otherKey)) if subtle.ConstantTimeEq(keyLen, otherKeyLen) == 0 { return false, nil } if subtle.ConstantTimeCompare(key, otherKey) == 1 { return true, nil } return false, nil } type Params struct { Memory uint32 Iterations uint32 Parallelism uint8 SaltLength uint32 KeyLength uint32 } func DecodeHash(hash string) (params *Params, salt, key []byte, err error) { vals := strings.Split(hash, "$") if len(vals) != 6 { return nil, nil, nil, ErrInvalidHash } if vals[1] != "argon2id" { return nil, nil, nil, ErrIncompatibleVariant } 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 } params = &Params{} _, err = fmt.Sscanf(vals[3], "m=%d,t=%d,p=%d", ¶ms.Memory, ¶ms.Iterations, ¶ms.Parallelism) if err != nil { return nil, nil, nil, ErrInvalidHash } salt, err = base64.RawStdEncoding.Strict().DecodeString(vals[4]) if err != nil { return nil, nil, nil, ErrInvalidHash } params.SaltLength = uint32(len(salt)) key, err = base64.RawStdEncoding.Strict().DecodeString(vals[5]) if err != nil { return nil, nil, nil, ErrInvalidHash } params.KeyLength = uint32(len(key)) return params, salt, key, nil }