Designed to be extensible to other algorithms which use salting. Signed-off-by: Ethan Wellenreiter <ewellenreiter@gmail.com>
126 lines
3.2 KiB
Go
126 lines
3.2 KiB
Go
package lcrypto
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"golang.org/x/crypto/argon2"
|
|
)
|
|
|
|
var (
|
|
ErrInvalidHash = errors.New("the encoded hash is not in the correct format")
|
|
ErrIncompatibleVersion = errors.New("incompatible argon version")
|
|
ErrWrongHashAlgo = errors.New("wrong hash algorithm")
|
|
algorithmName = "argon2id"
|
|
)
|
|
|
|
type Argon2id struct {
|
|
Memory uint32
|
|
Iterations uint32
|
|
Parallelism uint8
|
|
}
|
|
|
|
func (a *Argon2id) getAlgoString() string {
|
|
return fmt.Sprintf("$%s$v=%d$m=%d,t=%d,p=%d", algorithmName, argon2.Version, a.Memory, a.Iterations, a.Parallelism)
|
|
}
|
|
|
|
func (a *Argon2id) decodeAlgoParams(encodedString string) (algo Argon2id, leftovers []string, err error) {
|
|
algo = Argon2id{}
|
|
|
|
vals := strings.Split(encodedString, "$")
|
|
if len(vals) > 4 {
|
|
return algo, nil, ErrInvalidHash
|
|
}
|
|
|
|
var name string
|
|
if _, err = fmt.Sscanf(vals[1], "%s", &name); err != nil {
|
|
return algo, nil, err
|
|
}
|
|
if name != algorithmName {
|
|
return algo, nil, ErrWrongHashAlgo
|
|
}
|
|
|
|
var version int
|
|
_, err = fmt.Sscanf(vals[2], "v=%d", &version)
|
|
if err != nil {
|
|
return algo, nil, err
|
|
}
|
|
if version != argon2.Version {
|
|
return algo, nil, ErrIncompatibleVersion
|
|
}
|
|
|
|
_, err = fmt.Sscanf(vals[3], "m=%d,t=%d,p=%d", &algo.Memory, &algo.Iterations, &algo.Parallelism)
|
|
if err != nil {
|
|
return algo, nil, err
|
|
}
|
|
|
|
return algo, vals[4:], nil
|
|
}
|
|
|
|
func (a *Argon2id) EncodeHashAndSalt(hash []byte, salt []byte) (string, error) {
|
|
// Base64 encode the salt and hashed password.
|
|
b64Hash := base64.RawStdEncoding.EncodeToString(hash)
|
|
b64Salt := base64.RawStdEncoding.EncodeToString(salt)
|
|
|
|
// Return a string using the standard encoded hash representation.
|
|
encoding := fmt.Sprintf("%s$%s$%s", a.getAlgoString(), b64Hash, b64Salt)
|
|
return encoding, nil
|
|
}
|
|
|
|
func (a *Argon2id) EncodeHash(hash []byte) (string, error) {
|
|
// Base64 encode the salt and hashed password.
|
|
b64Hash := base64.RawStdEncoding.EncodeToString(hash)
|
|
|
|
// Return a string using the standard encoded hash representation.
|
|
encoding := fmt.Sprintf("%s$%s", a.getAlgoString(), b64Hash)
|
|
return encoding, nil
|
|
}
|
|
|
|
func (a *Argon2id) DecodeHashAndSalt(encodedString string) (algo Argon2id, hash []byte, salt []byte, err error) {
|
|
|
|
algo, values, err := a.decodeAlgoParams(encodedString)
|
|
if err != nil {
|
|
return algo, nil, nil, err
|
|
}
|
|
|
|
if len(values) != 2 {
|
|
return algo, nil, nil, ErrInvalidHash
|
|
}
|
|
|
|
hash, err = base64.RawStdEncoding.Strict().DecodeString(values[0])
|
|
if err != nil {
|
|
return algo, nil, nil, err
|
|
}
|
|
|
|
salt, err = base64.RawStdEncoding.Strict().DecodeString(values[1])
|
|
if err != nil {
|
|
return algo, nil, nil, err
|
|
}
|
|
|
|
return algo, hash, salt, nil
|
|
}
|
|
|
|
func (a *Argon2id) DecodeHash(encodedString string) (algo Argon2id, hash []byte, err error) {
|
|
algo, values, err := a.decodeAlgoParams(encodedString)
|
|
if err != nil {
|
|
return algo, nil, err
|
|
}
|
|
|
|
if len(values) != 1 {
|
|
return algo, nil, ErrInvalidHash
|
|
}
|
|
|
|
hash, err = base64.RawStdEncoding.Strict().DecodeString(values[0])
|
|
if err != nil {
|
|
return algo, nil, err
|
|
}
|
|
return algo, hash, nil
|
|
}
|
|
|
|
func (a *Argon2id) HashString(in string, salt []byte, keyLen uint) ([]byte, error) {
|
|
hash := argon2.IDKey([]byte(in), salt, a.Iterations, a.Memory, a.Parallelism, uint32(keyLen))
|
|
return hash, nil
|
|
}
|