diff --git a/config/config.go b/config/config.go index 7acf528d..f45d8570 100644 --- a/config/config.go +++ b/config/config.go @@ -134,6 +134,13 @@ func init() { PostLoginScope: 0, CheckPasswordHook: "", CheckPasswordScope: 0, + PasswordHashing: dataprovider.PasswordHashing{ + Argon2Options: dataprovider.Argon2Options{ + Memory: 65536, + Iterations: 1, + Parallelism: 2, + }, + }, }, HTTPDConfig: httpd.Conf{ BindPort: 8080, diff --git a/dataprovider/dataprovider.go b/dataprovider/dataprovider.go index edd50fe0..2635f269 100644 --- a/dataprovider/dataprovider.go +++ b/dataprovider/dataprovider.go @@ -118,12 +118,25 @@ var ( sqlTableFolders = "folders" sqlTableFoldersMapping = "folders_mapping" sqlTableSchemaVersion = "schema_version" + argon2Params *argon2id.Params ) type schemaVersion struct { Version int } +// Argon2Options defines the options for argon2 password hashing +type Argon2Options struct { + Memory uint32 `json:"memory" mapstructure:"memory"` + Iterations uint32 `json:"iterations" mapstructure:"iterations"` + Parallelism uint8 `json:"parallelism" mapstructure:"parallelism"` +} + +// PasswordHashing defines the configuration for password hashing +type PasswordHashing struct { + Argon2Options Argon2Options `json:"argon2_options" mapstructure:"argon2_options"` +} + // UserActions defines the action to execute on user create, update, delete. type UserActions struct { // Valid values are add, update, delete. Empty slice to disable @@ -234,6 +247,8 @@ type Config struct { // - 4 means WebDAV // you can combine the scopes, for example 6 means FTP and WebDAV CheckPasswordScope int `json:"check_password_scope" mapstructure:"check_password_scope"` + // PasswordHashing defines the configuration for password hashing + PasswordHashing PasswordHashing `json:"password_hashing" mapstructure:"password_hashing"` } // BackupData defines the structure for the backup/restore files @@ -371,6 +386,13 @@ func Initialize(cnf Config, basePath string) error { providerLog(logger.LevelWarn, "database migration error: %v", err) return err } + argon2Params = &argon2id.Params{ + Memory: cnf.PasswordHashing.Argon2Options.Memory, + Iterations: cnf.PasswordHashing.Argon2Options.Iterations, + Parallelism: cnf.PasswordHashing.Argon2Options.Parallelism, + SaltLength: 16, + KeyLength: 32, + } startAvailabilityTimer() return nil } @@ -989,7 +1011,7 @@ func validateBaseParams(user *User) error { func createUserPasswordHash(user *User) error { if len(user.Password) > 0 && !utils.IsStringPrefixInSlice(user.Password, hashPwdPrefixes) { - pwd, err := argon2id.CreateHash(user.Password, argon2id.DefaultParams) + pwd, err := argon2id.CreateHash(user.Password, argon2Params) if err != nil { return err } diff --git a/docs/full-configuration.md b/docs/full-configuration.md index ddf7458d..67fc2fb9 100644 --- a/docs/full-configuration.md +++ b/docs/full-configuration.md @@ -138,6 +138,11 @@ The configuration file contains the following sections: - `post_login_scope`, defines the scope for the post-login hook. 0 means notify both failed and successful logins. 1 means notify failed logins. 2 means notify successful logins. - `check_password_hook`, string. Absolute path to an external program or an HTTP URL to invoke to check the user provided password. See [Check password hook](./check-password-hook.md) for more details. Leave empty to disable. - `check_password_scope`, defines the scope for the check password hook. 0 means all protocols, 1 means SSH, 2 means FTP, 4 means WebDAV. You can combine the scopes, for example 6 means FTP and WebDAV. + - `password_hashing`, struct. It contains the configuration parameters to be used to generate the password hash. SFTPGo can verify passwords in several formats and uses the `argon2id` algorithm to hash passwords in plain-text before storing them inside the data provider. These options allow you to customize how the hash is generated. + - `argon2_options` struct containing the options for argon2id hashing algorithm. The `memory` and `iterations` parameters control the computational cost of hashing the password. The higher these figures are, the greater the cost of generating the hash and the longer the runtime. It also follows that the greater the cost will be for any attacker trying to guess the password. If the code is running on a machine with multiple cores, then you can decrease the runtime without reducing the cost by increasing the `parallelism` parameter. This controls the number of threads that the work is spread across. + - `memory`, unsigned integer. The amount of memory used by the algorithm (in kibibytes). Default: 65536. + - `iterations`, unsigned integer. The number of iterations over the memory. Default: 1. + - `parallelism`. unsigned 8 bit integer. The number of threads (or lanes) used by the algorithm. Default: 2. - **"httpd"**, the configuration for the HTTP server used to serve REST API and to expose the built-in web interface - `bind_port`, integer. The port used for serving HTTP requests. Set to 0 to disable HTTP server. Default: 8080 - `bind_address`, string. Leave blank to listen on all available network interfaces. Default: "127.0.0.1" diff --git a/sftpgo.json b/sftpgo.json index d4b22c0f..c9906a8e 100644 --- a/sftpgo.json +++ b/sftpgo.json @@ -92,7 +92,14 @@ "post_login_hook": "", "post_login_scope": 0, "check_password_hook": "", - "check_password_scope": 0 + "check_password_scope": 0, + "password_hashing": { + "argon2_options": { + "memory": 65536, + "iterations": 1, + "parallelism": 2 + } + } }, "httpd": { "bind_port": 8080,