aboutsummaryrefslogtreecommitdiff
path: root/src/user/secret.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/user/secret.go')
-rw-r--r--src/user/secret.go122
1 files changed, 102 insertions, 20 deletions
diff --git a/src/user/secret.go b/src/user/secret.go
index b6382fe..a777072 100644
--- a/src/user/secret.go
+++ b/src/user/secret.go
@@ -2,45 +2,127 @@ package user
import (
"crypto/rand"
+ "crypto/subtle"
"encoding/base64"
+ "errors"
"fmt"
+ "strings"
"golang.org/x/crypto/argon2"
)
-func HashSecret(secret string) (string, error) {
- hashconf := &struct {
- memory uint32
- iterations uint32
- parallelism uint8
- keyLength uint32
- saltLength uint32
- }{64 * 1024, 3, 2, 12, 16}
- salt := make([]byte, hashconf.saltLength)
+var (
+ errInvalidHash = errors.New("provided hash is wrong format")
+ errHashesNotEqual = errors.New("secret is not equal to encoded hash")
+ errIncorrectVersion = errors.New("incorrect version of Argon2")
+)
+
+type hashconf struct {
+ memory uint32
+ iterations uint32
+ parallelism uint8
+ keyLength uint32
+ saltLength uint32
+}
+
+func hashSecret(secret string) (string, error) {
+ hc := &hashconf{
+ memory: 64 * 1024,
+ iterations: 3,
+ parallelism: 2,
+ keyLength: 12,
+ saltLength: 16,
+ }
+ salt := make([]byte, hc.saltLength)
_, err := rand.Read(salt)
if err != nil {
return "", err
}
- hash := argon2.IDKey(
- []byte(secret),
- salt,
- hashconf.iterations,
- hashconf.memory,
- hashconf.parallelism,
- hashconf.keyLength,
- )
+ hash := hashArgon2(secret, salt, hc)
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,
- hashconf.memory,
- hashconf.iterations,
- hashconf.parallelism,
+ hc.memory,
+ hc.iterations,
+ hc.parallelism,
b64Salt,
b64Hash,
)
return encodedHash, nil
}
+
+func compareSecretToHash(secret, encoded string) (bool, error) {
+ // decode the encoded hash
+ hc, salt, comparehash, err := decodeHash(encoded)
+ if err != nil {
+ return false, err
+ }
+
+ // encode the secret
+ verifyhash := hashArgon2(secret, salt, hc)
+
+ // compare the hashes using constant time comparison
+ // to prevent timing attacks. if not equal, then return false
+ if subtle.ConstantTimeCompare(comparehash, verifyhash) != 1 {
+ return false, errHashesNotEqual
+ }
+
+ return true, nil
+}
+
+func hashArgon2(secret string, salt []byte, hc *hashconf) []byte {
+ hash := argon2.IDKey(
+ []byte(secret),
+ salt,
+ hc.iterations,
+ hc.memory,
+ hc.parallelism,
+ hc.keyLength,
+ )
+ return hash
+}
+
+func decodeHash(encoded string) (hc *hashconf, salt, decodedhash []byte, err error) {
+ params := strings.Split(encoded, "$")
+ // check we have enough params
+ if len(params) != 6 {
+ return nil, nil, nil, errInvalidHash
+ }
+
+ // check the argon2 version matches
+ var version int
+ _, err = fmt.Sscanf(params[2], "v=%d", &version)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ if version != argon2.Version {
+ return nil, nil, nil, errIncorrectVersion
+ }
+
+ // parse hashconf params to be returned
+ hc = &hashconf{}
+ _, err = fmt.Sscanf(params[3], "m=%d,t=%d,p=%d", &hc.memory, &hc.iterations, &hc.parallelism)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+
+ // decode the salt
+ salt, err = base64.RawStdEncoding.Strict().DecodeString(params[4])
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ hc.saltLength = uint32(len(salt))
+
+ // decode the hash
+ decodedhash, err = base64.RawStdEncoding.Strict().DecodeString(params[5])
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ hc.keyLength = uint32(len(decodedhash))
+
+ return hc, salt, decodedhash, nil
+}