add bcrypt and configs to password
This commit is contained in:
parent
c962f05c81
commit
828c581899
39
README.md
39
README.md
@ -169,14 +169,49 @@ admin.Use(auth.RequireAuth("/login"))
|
|||||||
|
|
||||||
### 1. Password Hashing
|
### 1. Password Hashing
|
||||||
|
|
||||||
|
The password package supports both Argon2 (default) and Bcrypt algorithms with configurable settings:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import "git.sharkk.net/Sharkk/Sushi/password"
|
import "git.sharkk.net/Sharkk/Sushi/password"
|
||||||
|
|
||||||
// Hash password for storage
|
// Using default Argon2 configuration
|
||||||
hashedPassword := password.HashPassword("userpassword123")
|
hashedPassword := password.HashPassword("userpassword123")
|
||||||
|
|
||||||
// Verify password during login
|
// Verify password (auto-detects algorithm)
|
||||||
isValid, err := password.VerifyPassword("userpassword123", hashedPassword)
|
isValid, err := password.VerifyPassword("userpassword123", hashedPassword)
|
||||||
|
|
||||||
|
// Configure for Bcrypt
|
||||||
|
password.SetConfig(password.Config{
|
||||||
|
Algorithm: password.Bcrypt,
|
||||||
|
Bcrypt: password.BcryptConfig{
|
||||||
|
Cost: 12, // Higher = more secure but slower
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Configure for Argon2 with custom settings
|
||||||
|
password.SetConfig(password.Config{
|
||||||
|
Algorithm: password.Argon2,
|
||||||
|
Argon2: password.Argon2Config{
|
||||||
|
Time: 3, // Iterations
|
||||||
|
Memory: 128 * 1024, // 128 MB
|
||||||
|
Threads: 4,
|
||||||
|
KeyLen: 32,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Use preset configurations
|
||||||
|
password.SetConfig(password.Config{
|
||||||
|
Algorithm: password.Argon2,
|
||||||
|
Argon2: password.SecureArgon2Config(), // More secure, slower
|
||||||
|
// Or: password.FastArgon2Config() for development
|
||||||
|
})
|
||||||
|
|
||||||
|
// Check if password needs rehashing (algorithm or params changed)
|
||||||
|
if password.NeedsRehash(userHashedPassword) {
|
||||||
|
// Rehash password after successful verification
|
||||||
|
newHash := password.HashPassword(plainPassword)
|
||||||
|
// Update stored hash in database
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. User Structure
|
### 2. User Structure
|
||||||
|
|||||||
@ -8,33 +8,158 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/crypto/argon2"
|
"golang.org/x/crypto/argon2"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Algorithm represents the hashing algorithm to use
|
||||||
|
type Algorithm int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
argonTime = 1
|
Argon2 Algorithm = iota
|
||||||
argonMemory = 64 * 1024
|
Bcrypt
|
||||||
argonThreads = 4
|
|
||||||
argonKeyLen = 32
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// HashPassword creates an argon2id hash of the password
|
// Argon2Config holds configuration for Argon2 hashing
|
||||||
|
type Argon2Config struct {
|
||||||
|
Time uint32 // Number of iterations
|
||||||
|
Memory uint32 // Memory usage in KB
|
||||||
|
Threads uint8 // Number of threads
|
||||||
|
KeyLen uint32 // Length of generated hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// BcryptConfig holds configuration for Bcrypt hashing
|
||||||
|
type BcryptConfig struct {
|
||||||
|
Cost int // Bcrypt cost factor (4-31)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config holds the password hashing configuration
|
||||||
|
type Config struct {
|
||||||
|
Algorithm Algorithm
|
||||||
|
Argon2 Argon2Config
|
||||||
|
Bcrypt BcryptConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultArgon2Config returns recommended Argon2 settings
|
||||||
|
func DefaultArgon2Config() Argon2Config {
|
||||||
|
return Argon2Config{
|
||||||
|
Time: 1,
|
||||||
|
Memory: 64 * 1024, // 64 MB
|
||||||
|
Threads: 4,
|
||||||
|
KeyLen: 32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SecureArgon2Config returns more secure Argon2 settings (slower but more resistant)
|
||||||
|
func SecureArgon2Config() Argon2Config {
|
||||||
|
return Argon2Config{
|
||||||
|
Time: 3,
|
||||||
|
Memory: 128 * 1024, // 128 MB
|
||||||
|
Threads: 4,
|
||||||
|
KeyLen: 32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FastArgon2Config returns faster Argon2 settings (for testing/development)
|
||||||
|
func FastArgon2Config() Argon2Config {
|
||||||
|
return Argon2Config{
|
||||||
|
Time: 1,
|
||||||
|
Memory: 32 * 1024, // 32 MB
|
||||||
|
Threads: 2,
|
||||||
|
KeyLen: 32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultBcryptConfig returns recommended Bcrypt settings
|
||||||
|
func DefaultBcryptConfig() BcryptConfig {
|
||||||
|
return BcryptConfig{
|
||||||
|
Cost: bcrypt.DefaultCost, // 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SecureBcryptConfig returns more secure Bcrypt settings (slower but more resistant)
|
||||||
|
func SecureBcryptConfig() BcryptConfig {
|
||||||
|
return BcryptConfig{
|
||||||
|
Cost: 12,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultConfig returns the default configuration (Argon2 with default settings)
|
||||||
|
func DefaultConfig() Config {
|
||||||
|
return Config{
|
||||||
|
Algorithm: Argon2,
|
||||||
|
Argon2: DefaultArgon2Config(),
|
||||||
|
Bcrypt: DefaultBcryptConfig(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var globalConfig = DefaultConfig()
|
||||||
|
|
||||||
|
// SetConfig sets the global password configuration
|
||||||
|
func SetConfig(config Config) {
|
||||||
|
globalConfig = config
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConfig returns the current global password configuration
|
||||||
|
func GetConfig() Config {
|
||||||
|
return globalConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// HashPassword creates a hash of the password using the global configuration
|
||||||
func HashPassword(password string) string {
|
func HashPassword(password string) string {
|
||||||
|
return HashPasswordWithConfig(password, globalConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HashPasswordWithConfig creates a hash of the password using the specified configuration
|
||||||
|
func HashPasswordWithConfig(password string, config Config) string {
|
||||||
|
switch config.Algorithm {
|
||||||
|
case Bcrypt:
|
||||||
|
return hashBcrypt(password, config.Bcrypt)
|
||||||
|
case Argon2:
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
return hashArgon2(password, config.Argon2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// hashArgon2 creates an argon2id hash of the password
|
||||||
|
func hashArgon2(password string, config Argon2Config) string {
|
||||||
salt := make([]byte, 16)
|
salt := make([]byte, 16)
|
||||||
rand.Read(salt)
|
rand.Read(salt)
|
||||||
|
|
||||||
hash := argon2.IDKey([]byte(password), salt, argonTime, argonMemory, argonThreads, argonKeyLen)
|
hash := argon2.IDKey([]byte(password), salt, config.Time, config.Memory, config.Threads, config.KeyLen)
|
||||||
|
|
||||||
b64Salt := base64.RawStdEncoding.EncodeToString(salt)
|
b64Salt := base64.RawStdEncoding.EncodeToString(salt)
|
||||||
b64Hash := base64.RawStdEncoding.EncodeToString(hash)
|
b64Hash := base64.RawStdEncoding.EncodeToString(hash)
|
||||||
|
|
||||||
encoded := fmt.Sprintf("$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s",
|
encoded := fmt.Sprintf("$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s",
|
||||||
argon2.Version, argonMemory, argonTime, argonThreads, b64Salt, b64Hash)
|
argon2.Version, config.Memory, config.Time, config.Threads, b64Salt, b64Hash)
|
||||||
|
|
||||||
return encoded
|
return encoded
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hashBcrypt creates a bcrypt hash of the password
|
||||||
|
func hashBcrypt(password string, config BcryptConfig) string {
|
||||||
|
hash, err := bcrypt.GenerateFromPassword([]byte(password), config.Cost)
|
||||||
|
if err != nil {
|
||||||
|
// Fallback to default cost if provided cost is invalid
|
||||||
|
hash, _ = bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||||
|
}
|
||||||
|
return string(hash)
|
||||||
|
}
|
||||||
|
|
||||||
// VerifyPassword checks if a password matches the hash
|
// VerifyPassword checks if a password matches the hash
|
||||||
func VerifyPassword(password, encodedHash string) (bool, error) {
|
func VerifyPassword(password, encodedHash string) (bool, error) {
|
||||||
|
// Detect hash type by prefix
|
||||||
|
if strings.HasPrefix(encodedHash, "$2a$") || strings.HasPrefix(encodedHash, "$2b$") || strings.HasPrefix(encodedHash, "$2y$") {
|
||||||
|
return verifyBcrypt(password, encodedHash)
|
||||||
|
} else if strings.HasPrefix(encodedHash, "$argon2id$") {
|
||||||
|
return verifyArgon2(password, encodedHash)
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("unsupported hash format")
|
||||||
|
}
|
||||||
|
|
||||||
|
// verifyArgon2 checks if a password matches an argon2 hash
|
||||||
|
func verifyArgon2(password, encodedHash string) (bool, error) {
|
||||||
parts := strings.Split(encodedHash, "$")
|
parts := strings.Split(encodedHash, "$")
|
||||||
if len(parts) != 6 {
|
if len(parts) != 6 {
|
||||||
return false, fmt.Errorf("invalid hash format")
|
return false, fmt.Errorf("invalid hash format")
|
||||||
@ -77,3 +202,52 @@ func VerifyPassword(password, encodedHash string) (bool, error) {
|
|||||||
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// verifyBcrypt checks if a password matches a bcrypt hash
|
||||||
|
func verifyBcrypt(password, encodedHash string) (bool, error) {
|
||||||
|
err := bcrypt.CompareHashAndPassword([]byte(encodedHash), []byte(password))
|
||||||
|
if err == bcrypt.ErrMismatchedHashAndPassword {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NeedsRehash checks if a hash needs to be updated to current configuration
|
||||||
|
func NeedsRehash(encodedHash string) bool {
|
||||||
|
// Check if using bcrypt when we want argon2
|
||||||
|
if globalConfig.Algorithm == Argon2 && (strings.HasPrefix(encodedHash, "$2a$") || strings.HasPrefix(encodedHash, "$2b$") || strings.HasPrefix(encodedHash, "$2y$")) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if using argon2 when we want bcrypt
|
||||||
|
if globalConfig.Algorithm == Bcrypt && strings.HasPrefix(encodedHash, "$argon2id$") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// For bcrypt, check if cost has changed
|
||||||
|
if strings.HasPrefix(encodedHash, "$2") {
|
||||||
|
cost, err := bcrypt.Cost([]byte(encodedHash))
|
||||||
|
if err == nil && cost != globalConfig.Bcrypt.Cost {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For argon2, check if parameters have changed
|
||||||
|
if strings.HasPrefix(encodedHash, "$argon2id$") {
|
||||||
|
parts := strings.Split(encodedHash, "$")
|
||||||
|
if len(parts) == 6 {
|
||||||
|
var m, t, p uint32
|
||||||
|
_, err := fmt.Sscanf(parts[3], "m=%d,t=%d,p=%d", &m, &t, &p)
|
||||||
|
if err == nil {
|
||||||
|
if m != globalConfig.Argon2.Memory || t != globalConfig.Argon2.Time || p != uint32(globalConfig.Argon2.Threads) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user