81 lines
2.3 KiB
Go
81 lines
2.3 KiB
Go
package argon2pw
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"crypto/subtle"
|
|
"encoding/base64"
|
|
"errors"
|
|
"fmt"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"golang.org/x/crypto/argon2"
|
|
)
|
|
|
|
const (
|
|
passwordType = "argon2id"
|
|
saltLen = 32
|
|
argon2KeyLen = 32
|
|
argon2Time = 1
|
|
argon2Memory = 64 * 1024
|
|
)
|
|
|
|
var argon2Threads = uint8(runtime.NumCPU())
|
|
|
|
// GenerateSaltedHash takes a plaintext password and generates an argon2 hash
|
|
func GenerateSaltedHash(password string) (string, error) {
|
|
if len(password) == 0 {
|
|
return "", errors.New("Password length cannot be 0")
|
|
}
|
|
salt, _ := generateSalt(saltLen)
|
|
unencodedPassword := argon2.IDKey([]byte(password), []byte(salt), argon2Time, argon2Memory, argon2Threads, argon2KeyLen)
|
|
encodedPassword := base64.StdEncoding.EncodeToString(unencodedPassword)
|
|
hash := fmt.Sprintf("%s$%d$%d$%d$%d$%s$%s",
|
|
passwordType, argon2Time, argon2Memory, argon2Threads, argon2KeyLen, salt, encodedPassword)
|
|
|
|
return hash, nil
|
|
}
|
|
|
|
// CompareHashWithPassword compares an argon2 hash against plaintext password
|
|
func CompareHashWithPassword(hash, password string) (bool, error) {
|
|
if len(hash) == 0 || len(password) == 0 {
|
|
return false, errors.New("Arguments cannot be zero length")
|
|
}
|
|
hashParts := strings.Split(hash, "$")
|
|
if len(hashParts) != 7 {
|
|
return false, errors.New("Invalid Password Hash")
|
|
}
|
|
|
|
passwordType := hashParts[0]
|
|
time, _ := strconv.Atoi((hashParts[1]))
|
|
memory, _ := strconv.Atoi(hashParts[2])
|
|
threads, _ := strconv.Atoi(hashParts[3])
|
|
keyLen, _ := strconv.Atoi(hashParts[4])
|
|
salt := []byte(hashParts[5])
|
|
key, _ := base64.StdEncoding.DecodeString(hashParts[6])
|
|
|
|
var calculatedKey []byte
|
|
switch passwordType {
|
|
case "argon2id":
|
|
calculatedKey = argon2.IDKey([]byte(password), salt, uint32(time), uint32(memory), uint8(threads), uint32(keyLen))
|
|
case "argon2i", "argon2":
|
|
calculatedKey = argon2.Key([]byte(password), salt, uint32(time), uint32(memory), uint8(threads), uint32(keyLen))
|
|
default:
|
|
return false, errors.New("Invalid Password Hash")
|
|
}
|
|
|
|
if subtle.ConstantTimeCompare(key, calculatedKey) != 1 {
|
|
return false, errors.New("Password did not match")
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
func generateSalt(len int) (string, error) {
|
|
unencodedSalt := make([]byte, len)
|
|
if _, err := rand.Read(unencodedSalt); err != nil {
|
|
return "", err
|
|
}
|
|
return base64.StdEncoding.EncodeToString(unencodedSalt), nil
|
|
}
|