diff --git a/modules/caddyhttp/caddyauth/caddyauth.go b/modules/caddyhttp/caddyauth/caddyauth.go index 69db62a5c..792c198ee 100644 --- a/modules/caddyhttp/caddyauth/caddyauth.go +++ b/modules/caddyhttp/caddyauth/caddyauth.go @@ -60,7 +60,8 @@ func (Authentication) CaddyModule() caddy.ModuleInfo { } } -// Provision sets up a. +// Provision sets up an Authentication module by initializing its logger, +// loading and registering all configured authentication providers. func (a *Authentication) Provision(ctx caddy.Context) error { a.logger = ctx.Logger() a.Providers = make(map[string]Authenticator) diff --git a/modules/caddyhttp/caddyauth/command.go b/modules/caddyhttp/caddyauth/command.go index c9f440060..07489397a 100644 --- a/modules/caddyhttp/caddyauth/command.go +++ b/modules/caddyhttp/caddyauth/command.go @@ -32,7 +32,7 @@ import ( func init() { caddycmd.RegisterCommand(caddycmd.Command{ Name: "hash-password", - Usage: "[--plaintext ] [--algorithm ]", + Usage: "[--plaintext ] [--algorithm ] [--cost ]", Short: "Hashes a password and writes base64", Long: ` Convenient way to hash a plaintext password. The resulting @@ -43,10 +43,17 @@ Caddy is attached to a controlling tty, the plaintext will not be echoed. --algorithm currently only supports 'bcrypt', and is the default. + +--cost sets the bcrypt hashing difficulty. +Higher values increase security by making the hash computation slower and more CPU-intensive. +If the provided cost is not within the valid range [bcrypt.MinCost, bcrypt.MaxCost], +the default value (defaultBcryptCost) will be used instead. +Note: Higher cost values can significantly degrade performance on slower systems. `, CobraFunc: func(cmd *cobra.Command) { cmd.Flags().StringP("plaintext", "p", "", "The plaintext password") cmd.Flags().StringP("algorithm", "a", "bcrypt", "Name of the hash algorithm") + cmd.Flags().Int("bcrypt-cost", defaultBcryptCost, "Bcrypt hashing cost (only used with 'bcrypt' algorithm)") cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdHashPassword) }, }) @@ -57,6 +64,7 @@ func cmdHashPassword(fs caddycmd.Flags) (int, error) { algorithm := fs.String("algorithm") plaintext := []byte(fs.String("plaintext")) + bcryptCost := fs.Int("bcrypt-cost") if len(plaintext) == 0 { fd := int(os.Stdin.Fd()) @@ -108,7 +116,7 @@ func cmdHashPassword(fs caddycmd.Flags) (int, error) { var hashString string switch algorithm { case "bcrypt": - hash, err = BcryptHash{}.Hash(plaintext) + hash, err = BcryptHash{cost: bcryptCost}.Hash(plaintext) hashString = string(hash) default: return caddy.ExitCodeFailedStartup, fmt.Errorf("unrecognized hash algorithm: %s", algorithm) diff --git a/modules/caddyhttp/caddyauth/hashes.go b/modules/caddyhttp/caddyauth/hashes.go index ce3df901e..ea91f24e2 100644 --- a/modules/caddyhttp/caddyauth/hashes.go +++ b/modules/caddyhttp/caddyauth/hashes.go @@ -15,6 +15,8 @@ package caddyauth import ( + "errors" + "golang.org/x/crypto/bcrypt" "github.com/caddyserver/caddy/v2" @@ -24,8 +26,15 @@ func init() { caddy.RegisterModule(BcryptHash{}) } +// defaultBcryptCost cost 14 strikes a solid balance between security, usability, and hardware performance +const defaultBcryptCost = 14 + // BcryptHash implements the bcrypt hash. -type BcryptHash struct{} +type BcryptHash struct { + // cost is the bcrypt hashing difficulty factor (work factor). + // Higher values increase computation time and security. + cost int +} // CaddyModule returns the Caddy module information. func (BcryptHash) CaddyModule() caddy.ModuleInfo { @@ -38,7 +47,7 @@ func (BcryptHash) CaddyModule() caddy.ModuleInfo { // Compare compares passwords. func (BcryptHash) Compare(hashed, plaintext []byte) (bool, error) { err := bcrypt.CompareHashAndPassword(hashed, plaintext) - if err == bcrypt.ErrMismatchedHashAndPassword { + if errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) { return false, nil } if err != nil { @@ -48,8 +57,13 @@ func (BcryptHash) Compare(hashed, plaintext []byte) (bool, error) { } // Hash hashes plaintext using a random salt. -func (BcryptHash) Hash(plaintext []byte) ([]byte, error) { - return bcrypt.GenerateFromPassword(plaintext, 14) +func (b BcryptHash) Hash(plaintext []byte) ([]byte, error) { + cost := b.cost + if cost < bcrypt.MinCost || cost > bcrypt.MaxCost { + cost = defaultBcryptCost + } + + return bcrypt.GenerateFromPassword(plaintext, cost) } // FakeHash returns a fake hash. diff --git a/modules/caddyhttp/encode/encode_test.go b/modules/caddyhttp/encode/encode_test.go index d84c76d14..90be1e932 100644 --- a/modules/caddyhttp/encode/encode_test.go +++ b/modules/caddyhttp/encode/encode_test.go @@ -122,7 +122,6 @@ func TestPreferOrder(t *testing.T) { } } - func TestValidate(t *testing.T) { type testCase struct { name string