161 lines
4 KiB
Go
161 lines
4 KiB
Go
// Package secret implement all crypto utils like password hashing and secret generation
|
|
package secret
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/rand"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"git.gnous.eu/gnouseu/plakken/internal/constant"
|
|
"golang.org/x/crypto/argon2"
|
|
)
|
|
|
|
type argon2idHash struct {
|
|
salt []byte
|
|
hash []byte
|
|
}
|
|
|
|
// Argon2id config.
|
|
type config struct {
|
|
saltLength uint8
|
|
memory uint32
|
|
threads uint8
|
|
keyLength uint32
|
|
iterations uint32
|
|
}
|
|
|
|
// generateSecret for password hashing or token generation.
|
|
func generateSecret(length uint8) ([]byte, error) {
|
|
secret := make([]byte, length)
|
|
|
|
_, err := rand.Read(secret)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return secret, err
|
|
}
|
|
|
|
// GenerateToken generate hexadecimal token.
|
|
func GenerateToken() (string, error) {
|
|
secret, err := generateSecret(constant.TokenLength)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
token := hex.EncodeToString(secret)
|
|
|
|
return token, nil
|
|
}
|
|
|
|
// generateArgon2ID Generate an argon2id hash from source string and specified salt.
|
|
func (config config) generateArgon2ID(source string, salt []byte) []byte {
|
|
hash := argon2.IDKey([]byte(source), salt, config.iterations, config.memory, config.threads, config.keyLength)
|
|
|
|
return hash
|
|
}
|
|
|
|
// Password hash a source string with argon2id, return properly encoded hash.
|
|
func Password(password string) (string, error) {
|
|
config := config{
|
|
saltLength: constant.ArgonSaltSize,
|
|
memory: constant.ArgonMemory,
|
|
threads: constant.ArgonThreads,
|
|
keyLength: constant.ArgonKeyLength,
|
|
iterations: constant.ArgonIterations,
|
|
}
|
|
|
|
salt, err := generateSecret(config.saltLength)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
hash := config.generateArgon2ID(password, salt)
|
|
|
|
base64Hash := base64.RawStdEncoding.EncodeToString(hash)
|
|
base64Salt := base64.RawStdEncoding.EncodeToString(salt)
|
|
|
|
formatted := fmt.Sprintf("$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s", argon2.Version, config.memory, config.iterations, config.threads, base64Salt, base64Hash)
|
|
|
|
return formatted, nil
|
|
}
|
|
|
|
// VerifyPassword check is source password and stored password is similar, take password and a properly encoded hash.
|
|
func VerifyPassword(password string, hash string) (bool, error) {
|
|
argon2Hash, config, err := parseHash(hash)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
result := config.generateArgon2ID(password, argon2Hash.salt)
|
|
|
|
return bytes.Equal(result, argon2Hash.hash), nil
|
|
}
|
|
|
|
// parseHash parse existing encoded argon2id string.
|
|
func parseHash(source string) (argon2idHash, config, error) {
|
|
separateItem := strings.Split(source, "$")
|
|
if len(separateItem) != 6 { //nolint:gomnd
|
|
return argon2idHash{}, config{}, &parseError{message: "Hash format is not valid"}
|
|
}
|
|
|
|
if separateItem[1] != "argon2id" {
|
|
return argon2idHash{}, config{}, &parseError{message: "Algorithm is not valid"}
|
|
}
|
|
|
|
separateParam := strings.Split(separateItem[3], ",")
|
|
if len(separateParam) != 3 { //nolint:gomnd
|
|
return argon2idHash{}, config{}, &parseError{message: "Hash config is not valid"}
|
|
}
|
|
|
|
salt, err := base64.RawStdEncoding.Strict().DecodeString(separateItem[4])
|
|
if err != nil {
|
|
return argon2idHash{}, config{}, err
|
|
}
|
|
|
|
var hash []byte
|
|
hash, err = base64.RawStdEncoding.Strict().DecodeString(separateItem[5])
|
|
if err != nil {
|
|
return argon2idHash{}, config{}, err
|
|
}
|
|
|
|
saltLength := uint8(len(salt))
|
|
keyLength := uint32(len(hash))
|
|
|
|
var memory int
|
|
memory, err = strconv.Atoi(strings.ReplaceAll(separateParam[0], "m=", ""))
|
|
if err != nil {
|
|
return argon2idHash{}, config{}, err
|
|
}
|
|
|
|
var iterations int
|
|
iterations, err = strconv.Atoi(strings.ReplaceAll(separateParam[1], "t=", ""))
|
|
if err != nil {
|
|
return argon2idHash{}, config{}, err
|
|
}
|
|
|
|
var threads int
|
|
threads, err = strconv.Atoi(strings.ReplaceAll(separateParam[2], "p=", ""))
|
|
if err != nil {
|
|
return argon2idHash{}, config{}, err
|
|
}
|
|
|
|
argon2idStruct := argon2idHash{
|
|
salt: salt,
|
|
hash: hash,
|
|
}
|
|
|
|
hashConfig := config{
|
|
saltLength: saltLength,
|
|
memory: uint32(memory),
|
|
threads: uint8(threads),
|
|
iterations: uint32(iterations),
|
|
keyLength: keyLength,
|
|
}
|
|
|
|
return argon2idStruct, hashConfig, nil
|
|
}
|