diff options
Diffstat (limited to 'src/user/secret.go')
| -rw-r--r-- | src/user/secret.go | 122 |
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 +} |
