github.com/goravel/framework@v1.13.9/hash/argon2id.go (about)

     1  package hash
     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  	"github.com/goravel/framework/contracts/config"
    13  )
    14  
    15  type Argon2id struct {
    16  	// The format of the hash.
    17  	format string
    18  	// The version of argon2id to use.
    19  	version int
    20  	// The time cost parameter.
    21  	time uint32
    22  	// The memory cost parameter.
    23  	memory uint32
    24  	// The threads cost parameter.
    25  	threads uint8
    26  	// The length of the key to generate.
    27  	keyLen uint32
    28  	// The length of the random salt to generate.
    29  	saltLen uint32
    30  }
    31  
    32  // NewArgon2id returns a new Argon2id hasher.
    33  func NewArgon2id(config config.Config) *Argon2id {
    34  	return &Argon2id{
    35  		format:  "$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s",
    36  		version: argon2.Version,
    37  		time:    uint32(config.GetInt("hashing.argon2id.time", 4)),
    38  		memory:  uint32(config.GetInt("hashing.argon2id.memory", 65536)),
    39  		threads: uint8(config.GetInt("hashing.argon2id.threads", 1)),
    40  		keyLen:  32,
    41  		saltLen: 16,
    42  	}
    43  }
    44  
    45  func (a *Argon2id) Make(value string) (string, error) {
    46  	salt := make([]byte, a.saltLen)
    47  	if _, err := rand.Read(salt); err != nil {
    48  		return "", err
    49  	}
    50  
    51  	hash := argon2.IDKey([]byte(value), salt, a.time, a.memory, a.threads, a.keyLen)
    52  
    53  	return fmt.Sprintf(a.format, a.version, a.memory, a.time, a.threads, base64.RawStdEncoding.EncodeToString(salt), base64.RawStdEncoding.EncodeToString(hash)), nil
    54  }
    55  
    56  func (a *Argon2id) Check(value, hash string) bool {
    57  	hashParts := strings.Split(hash, "$")
    58  	if len(hashParts) != 6 {
    59  		return false
    60  	}
    61  
    62  	var version int
    63  	_, err := fmt.Sscanf(hashParts[2], "v=%d", &version)
    64  	if err != nil {
    65  		return false
    66  	}
    67  	if version != a.version {
    68  		return false
    69  	}
    70  
    71  	memory := a.memory
    72  	time := a.time
    73  	threads := a.threads
    74  
    75  	_, err = fmt.Sscanf(hashParts[3], "m=%d,t=%d,p=%d", &memory, &time, &threads)
    76  	if err != nil {
    77  		return false
    78  	}
    79  
    80  	salt, err := base64.RawStdEncoding.DecodeString(hashParts[4])
    81  	if err != nil {
    82  		return false
    83  	}
    84  
    85  	decodedHash, err := base64.RawStdEncoding.DecodeString(hashParts[5])
    86  	if err != nil {
    87  		return false
    88  	}
    89  
    90  	hashToCompare := argon2.IDKey([]byte(value), salt, time, memory, threads, uint32(len(decodedHash)))
    91  
    92  	return subtle.ConstantTimeCompare(decodedHash, hashToCompare) == 1
    93  }
    94  
    95  func (a *Argon2id) NeedsRehash(hash string) bool {
    96  	hashParts := strings.Split(hash, "$")
    97  	if len(hashParts) != 6 {
    98  		return true
    99  	}
   100  
   101  	var version int
   102  	_, err := fmt.Sscanf(hashParts[2], "v=%d", &version)
   103  	if err != nil {
   104  		return true
   105  	}
   106  	if version != a.version {
   107  		return true
   108  	}
   109  
   110  	var memory, time uint32
   111  	var threads uint8
   112  	_, err = fmt.Sscanf(hashParts[3], "m=%d,t=%d,p=%d", &memory, &time, &threads)
   113  	if err != nil {
   114  		return true
   115  	}
   116  
   117  	return memory != a.memory || time != a.time || threads != a.threads
   118  }