github.com/lovung/GoCleanArchitecture@v0.0.0-20210302152432-50d91fd29f9f/pkg/hasher/password.go (about)

     1  package hasher
     2  
     3  import (
     4  	"crypto/rand"
     5  	"crypto/subtle"
     6  	"encoding/base64"
     7  	"fmt"
     8  	"strings"
     9  
    10  	"golang.org/x/crypto/argon2"
    11  )
    12  
    13  type passwordConfig struct {
    14  	time    uint32
    15  	memory  uint32
    16  	threads uint8
    17  	keyLen  uint32
    18  }
    19  
    20  var _config = passwordConfig{
    21  	time:    8,
    22  	memory:  64 * 1024,
    23  	threads: 8,
    24  	keyLen:  64,
    25  }
    26  
    27  // HashPassword hash password before saving
    28  func HashPassword(password string) (string, error) {
    29  	return generatePassword(&_config, password)
    30  }
    31  
    32  // CheckPasswordHash verify the password
    33  func CheckPasswordHash(password, hash string) (bool, error) {
    34  	return comparePassword(password, hash)
    35  }
    36  
    37  // generatePassword is used to generate a new password hash for storing and
    38  // comparing at a later date.
    39  func generatePassword(c *passwordConfig, password string) (string, error) {
    40  	// Generate a Salt
    41  	salt := make([]byte, 16)
    42  	if _, err := rand.Read(salt); err != nil {
    43  		return "", err
    44  	}
    45  
    46  	hash := argon2.IDKey([]byte(password), salt, c.time, c.memory, c.threads, c.keyLen)
    47  
    48  	// Base64 encode the salt and hashed password.
    49  	b64Salt := base64.RawStdEncoding.EncodeToString(salt)
    50  	b64Hash := base64.RawStdEncoding.EncodeToString(hash)
    51  
    52  	format := "$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s"
    53  	full := fmt.Sprintf(format, argon2.Version, c.memory, c.time, c.threads, b64Salt, b64Hash)
    54  	return full, nil
    55  }
    56  
    57  // comparePassword is used to compare a user-inputted password to a hash to see
    58  // if the password matches or not.
    59  func comparePassword(password, hash string) (bool, error) {
    60  	parts := strings.Split(hash, "$")
    61  
    62  	c := &passwordConfig{}
    63  	_, err := fmt.Sscanf(parts[3], "m=%d,t=%d,p=%d", &c.memory, &c.time, &c.threads)
    64  	if err != nil {
    65  		return false, err
    66  	}
    67  
    68  	salt, err := base64.RawStdEncoding.DecodeString(parts[4])
    69  	if err != nil {
    70  		return false, err
    71  	}
    72  
    73  	decodedHash, err := base64.RawStdEncoding.DecodeString(parts[5])
    74  	if err != nil {
    75  		return false, err
    76  	}
    77  	c.keyLen = uint32(len(decodedHash))
    78  
    79  	comparisonHash := argon2.IDKey([]byte(password), salt, c.time, c.memory, c.threads, c.keyLen)
    80  
    81  	return (subtle.ConstantTimeCompare(decodedHash, comparisonHash) == 1), nil
    82  }