package utils import ( "crypto/md5" "crypto/rand" "crypto/sha256" "crypto/sha512" "crypto/subtle" "encoding/base64" "fmt" "strings" "golang.org/x/crypto/argon2" "golang.org/x/crypto/bcrypt" ) const ( argon2Time = 3 argon2Memory = 64 * 1024 argon2Threads = 4 argon2KeyLen = 32 saltLength = 16 ) func Hash(password string, algorithm string) (string, error) { switch algorithm { case "argon2": return Argon2Hash(password) case "bcrypt": return BcryptHash(password) case "md5": return MD5Hash(password) case "sha256": return SHA256Hash(password) case "sha512": return SHA512Hash(password) default: return "", fmt.Errorf("unsupported hash algorithm: %s", algorithm) } } func Verify(password, encodedHash string, algorithm string) (bool, error) { switch algorithm { case "argon2": return Argon2Verify(password, encodedHash) case "bcrypt": return BcryptVerify(password, encodedHash) case "md5": return MD5Verify(password, encodedHash) case "sha256": return SHA256Verify(password, encodedHash) case "sha512": return SHA512Verify(password, encodedHash) default: return false, fmt.Errorf("unsupported hash algorithm: %s", algorithm) } } func Argon2Hash(password string) (string, error) { salt := make([]byte, saltLength) if _, err := rand.Read(salt); err != nil { return "", err } hash := argon2.IDKey([]byte(password), salt, argon2Time, argon2Memory, argon2Threads, argon2KeyLen) b64Salt := base64.RawStdEncoding.EncodeToString(salt) b64Hash := base64.RawStdEncoding.EncodeToString(hash) encodedHash := fmt.Sprintf("$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s", argon2.Version, argon2Memory, argon2Time, argon2Threads, b64Salt, b64Hash) return encodedHash, nil } // Verify compares a password with an Argon2id hash func Argon2Verify(password, encodedHash string) (bool, error) { parts := strings.Split(encodedHash, "$") if len(parts) != 6 { return false, fmt.Errorf("invalid hash format") } if parts[1] != "argon2id" { return false, fmt.Errorf("unsupported hash algorithm") } var version int _, err := fmt.Sscanf(parts[2], "v=%d", &version) if err != nil { return false, err } if version != argon2.Version { return false, fmt.Errorf("incompatible version") } var memory, time uint32 var threads uint8 _, err = fmt.Sscanf(parts[3], "m=%d,t=%d,p=%d", &memory, &time, &threads) if err != nil { return false, err } salt, err := base64.RawStdEncoding.DecodeString(parts[4]) if err != nil { return false, err } hash, err := base64.RawStdEncoding.DecodeString(parts[5]) if err != nil { return false, err } otherHash := argon2.IDKey([]byte(password), salt, time, memory, threads, uint32(len(hash))) if subtle.ConstantTimeCompare(hash, otherHash) == 1 { return true, nil } return false, nil } // BcryptHash generates a bcrypt hash of the password func BcryptHash(password string) (string, error) { hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { return "", err } return string(hash), nil } // BcryptVerify compares a password with a bcrypt hash func BcryptVerify(password, encodedHash string) (bool, error) { err := bcrypt.CompareHashAndPassword([]byte(encodedHash), []byte(password)) if err != nil { return false, nil } return true, nil } // MD5Hash generates an MD5 hash with salt func MD5Hash(password string) (string, error) { salt := make([]byte, saltLength) if _, err := rand.Read(salt); err != nil { return "", err } hasher := md5.New() hasher.Write(salt) hasher.Write([]byte(password)) hash := hasher.Sum(nil) b64Salt := base64.RawStdEncoding.EncodeToString(salt) b64Hash := base64.RawStdEncoding.EncodeToString(hash) encodedHash := fmt.Sprintf("$md5$%s$%s", b64Salt, b64Hash) return encodedHash, nil } // MD5Verify compares a password with an MD5 hash func MD5Verify(password, encodedHash string) (bool, error) { parts := strings.Split(encodedHash, "$") if len(parts) != 4 || parts[1] != "md5" { return false, fmt.Errorf("invalid hash format") } salt, err := base64.RawStdEncoding.DecodeString(parts[2]) if err != nil { return false, err } hash, err := base64.RawStdEncoding.DecodeString(parts[3]) if err != nil { return false, err } hasher := md5.New() hasher.Write(salt) hasher.Write([]byte(password)) otherHash := hasher.Sum(nil) if subtle.ConstantTimeCompare(hash, otherHash) == 1 { return true, nil } return false, nil } // SHA256Hash generates a SHA256 hash with salt func SHA256Hash(password string) (string, error) { salt := make([]byte, saltLength) if _, err := rand.Read(salt); err != nil { return "", err } hasher := sha256.New() hasher.Write(salt) hasher.Write([]byte(password)) hash := hasher.Sum(nil) b64Salt := base64.RawStdEncoding.EncodeToString(salt) b64Hash := base64.RawStdEncoding.EncodeToString(hash) encodedHash := fmt.Sprintf("$sha256$%s$%s", b64Salt, b64Hash) return encodedHash, nil } // SHA256Verify compares a password with a SHA256 hash func SHA256Verify(password, encodedHash string) (bool, error) { parts := strings.Split(encodedHash, "$") if len(parts) != 4 || parts[1] != "sha256" { return false, fmt.Errorf("invalid hash format") } salt, err := base64.RawStdEncoding.DecodeString(parts[2]) if err != nil { return false, err } hash, err := base64.RawStdEncoding.DecodeString(parts[3]) if err != nil { return false, err } hasher := sha256.New() hasher.Write(salt) hasher.Write([]byte(password)) otherHash := hasher.Sum(nil) if subtle.ConstantTimeCompare(hash, otherHash) == 1 { return true, nil } return false, nil } // SHA512Hash generates a SHA512 hash with salt func SHA512Hash(password string) (string, error) { salt := make([]byte, saltLength) if _, err := rand.Read(salt); err != nil { return "", err } hasher := sha512.New() hasher.Write(salt) hasher.Write([]byte(password)) hash := hasher.Sum(nil) b64Salt := base64.RawStdEncoding.EncodeToString(salt) b64Hash := base64.RawStdEncoding.EncodeToString(hash) encodedHash := fmt.Sprintf("$sha512$%s$%s", b64Salt, b64Hash) return encodedHash, nil } // SHA512Verify compares a password with a SHA512 hash func SHA512Verify(password, encodedHash string) (bool, error) { parts := strings.Split(encodedHash, "$") if len(parts) != 4 || parts[1] != "sha512" { return false, fmt.Errorf("invalid hash format") } salt, err := base64.RawStdEncoding.DecodeString(parts[2]) if err != nil { return false, err } hash, err := base64.RawStdEncoding.DecodeString(parts[3]) if err != nil { return false, err } hasher := sha512.New() hasher.Write(salt) hasher.Write([]byte(password)) otherHash := hasher.Sum(nil) if subtle.ConstantTimeCompare(hash, otherHash) == 1 { return true, nil } return false, nil }