Enshi/enshi_back/utils/passwordHashing.go
2024-09-29 19:38:22 +03:00

159 lines
3.6 KiB
Go

package utils
import (
"bytes"
"crypto/rand"
"encoding/base64"
"fmt"
"strings"
"github.com/joho/godotenv"
"golang.org/x/crypto/argon2"
)
type Argon2Hash struct {
time uint32
memory uint32
threads uint8
keyLen uint32
saltLen uint32
}
type HashSalt struct {
hash []byte
salt []byte
stringToStore string
}
// Initializer for algorithm
func NewArgon2Hash(time, keyLen, saltLen, memory uint32, threads uint8) *Argon2Hash {
return &Argon2Hash{
time: time,
keyLen: keyLen,
saltLen: saltLen,
memory: memory,
threads: threads,
}
}
// Generating salt (byte slice with pseudorandom symbols)
func SaltGen(length uint32) ([]byte, error) {
salt := make([]byte, length)
// Read pseudorandom numbers and write them in salt
_, err := rand.Read(salt)
if err != nil {
return nil, err
}
return salt, nil
}
// Generating hash with given password and salt
func (a *Argon2Hash) HashGen(password, salt []byte) (*HashSalt, error) {
var err error
// If salt len is zero => we hashing new password => we need new salt ;)
if len(salt) == 0 {
salt, err = SaltGen(a.saltLen)
}
if err != nil {
return nil, err
}
// Generating password hash with given params
hash := argon2.IDKey(password, salt, a.time, a.memory, a.threads, a.keyLen)
// Salt and hash is byte slice and to properly read them (as string) we need to use this functions
saltDecoded := base64.RawStdEncoding.EncodeToString(salt)
hashDecoded := base64.RawStdEncoding.EncodeToString(hash)
// Creating string with salt and password hash
stringToStore := fmt.Sprintf("$m=%d,t=%d$%s$%s", a.memory, a.time, saltDecoded, hashDecoded)
// This is unnecessary structure i created following the guide 0_0
return &HashSalt{hash: hash, salt: salt, stringToStore: stringToStore}, nil
}
// Returns error if password hashes are not equal
func (a *Argon2Hash) Compare(hash, salt, password []byte) error {
// We hash given password with salt we created for original one
// so we can check wether hashes are equal
hashSalt, err := a.HashGen(password, salt)
if err != nil {
return err
}
// Comparing hashes
if !bytes.Equal(hash, hashSalt.hash) {
return fmt.Errorf("invalid password (hashes does not match)")
}
return nil
}
// Function to extract hash and salt from password string
// that is stored in db
//
// returns hash, salt, error
func DecodeArgon2String(passwordHash string) ([]byte, []byte, error) {
values := strings.Split(passwordHash, "$")
// Transform string hash in byte slice
salt, err := base64.RawStdEncoding.Strict().DecodeString(values[2])
if err != nil {
return nil, nil, err
}
// Transform string hash in byte slice
hash, err := base64.RawStdEncoding.Strict().DecodeString(values[3])
if err != nil {
return nil, nil, err
}
return hash, salt, nil
}
var Argon2Hasher = NewArgon2Hash(2, 100, 32, 64*1024, 1)
func Test() {
pas := []byte("qwerty1")
testDbString := "$m=65536,t=2$u0h2MoT48NXJFIRQXW9/i0tNDu427RJv3vIeZQIm8FU$QtUmjmPhsBgWGloNQVRoFkLkHnQwuCqRVfgKA0Sm2QNMPc86vSLxQ/c8JUXroO37qwXdfC9DNvTnm/OOi7GfTBGnJJotLBlonG/9epAMGY453s9UZVeghmvftCagXzPS9QB3cg"
var salt []byte
cringe, err := Argon2Hasher.HashGen(pas, salt)
if err != nil {
return
}
storedH, storedS, err := DecodeArgon2String(testDbString)
if err != nil {
fmt.Println("CRINGE", err)
return
}
err = Argon2Hasher.Compare(storedH, storedS, []byte("qwerty1"))
if err != nil {
fmt.Println("DONT MATCH")
} else {
fmt.Println("MATCH")
}
if err := godotenv.Load("secrets.env"); err != nil {
fmt.Println("Error load .env")
}
// fmt.Println(testDbString)
fmt.Printf("%s", cringe.stringToStore)
fmt.Print("\n\n\n\n")
}