add support for checking sha256crypt passwords

they will be converted to the configured password hashing algorithm after
the first user login

Fixes #1000

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
Nicola Murino 2022-09-28 19:15:02 +02:00
parent f9eadd7f04
commit 1e21aa9453
No known key found for this signature in database
GPG key ID: 935D2952DEC4EECF
4 changed files with 9 additions and 3 deletions

View file

@ -14,7 +14,7 @@ the dump is a JSON with all SFTPGo data including users, folders, admins.
These properties are stored inside the configured data provider. These properties are stored inside the configured data provider.
SFTPGo supports checking passwords stored with bcrypt, pbkdf2, md5crypt and sha512crypt too. For pbkdf2 the supported format is `$<algo>$<iterations>$<salt>$<hashed pwd base64 encoded>`, where algo is `pbkdf2-sha1` or `pbkdf2-sha256` or `pbkdf2-sha512` or `$pbkdf2-b64salt-sha256$`. For example the pbkdf2-sha256 of the word password using 150000 iterations and E86a9YMX3zC7 as salt must be stored as `$pbkdf2-sha256$150000$E86a9YMX3zC7$R5J62hsSq+pYw00hLLPKBbcGXmq7fj5+/M0IFoYtZbo=`. In pbkdf2 variant with b64salt the salt is base64 encoded. For bcrypt the format must be the one supported by golang's crypto/bcrypt package, for example the password secret with cost 14 must be stored as `$2a$14$ajq8Q7fbtFRQvXpdCq7Jcuy.Rx1h/L4J60Otx.gyNLbAYctGMJ9tK`. For md5crypt and sha512crypt we support the format used in `/etc/shadow` with the `$1$` and `$6$` prefix, this is useful if you are migrating from Unix system user accounts. We support Apache md5crypt (`$apr1$` prefix) too. Using the REST API you can send a password hashed as bcrypt, pbkdf2, md5crypt or sha512crypt and it will be stored as is. SFTPGo supports checking passwords stored with argon2id, bcrypt, pbkdf2, md5cryptm sha256crypt and sha512crypt too. For pbkdf2 the supported format is `$<algo>$<iterations>$<salt>$<hashed pwd base64 encoded>`, where algo is `pbkdf2-sha1` or `pbkdf2-sha256` or `pbkdf2-sha512` or `$pbkdf2-b64salt-sha256$`. For example the pbkdf2-sha256 of the word password using 150000 iterations and E86a9YMX3zC7 as salt must be stored as `$pbkdf2-sha256$150000$E86a9YMX3zC7$R5J62hsSq+pYw00hLLPKBbcGXmq7fj5+/M0IFoYtZbo=`. In pbkdf2 variant with b64salt the salt is base64 encoded. For bcrypt the format must be the one supported by golang's crypto/bcrypt package, for example the password secret with cost 14 must be stored as `$2a$14$ajq8Q7fbtFRQvXpdCq7Jcuy.Rx1h/L4J60Otx.gyNLbAYctGMJ9tK`. For md5crypt, sha256crypt and sha512crypt we support the format used in `/etc/shadow` with the `$1$`, `$5$` and `$6$` prefix, this is useful if you are migrating from Unix system user accounts. We support Apache md5crypt (`$apr1$` prefix) too. Using the REST API you can send a password hashed as argon2id, bcrypt, pbkdf2, md5crypt, sha256crypt or sha512crypt and it will be stored as is.
If you want to use your existing accounts, you have these options: If you want to use your existing accounts, you have these options:

View file

@ -502,6 +502,7 @@ Supported hash algorithms:
- PBKDF2 sha256 with base64 salt, prefix `$pbkdf2-b64salt-sha256$` - PBKDF2 sha256 with base64 salt, prefix `$pbkdf2-b64salt-sha256$`
- MD5 crypt, prefix `$1$` - MD5 crypt, prefix `$1$`
- MD5 crypt APR1, prefix `$apr1$` - MD5 crypt APR1, prefix `$apr1$`
- SHA256 crypt, prefix `$5$`
- SHA512 crypt, prefix `$6$` - SHA512 crypt, prefix `$6$`
- LDAP MD5, prefix `{MD5}` - LDAP MD5, prefix `{MD5}`

View file

@ -51,6 +51,7 @@ import (
"github.com/GehirnInc/crypt" "github.com/GehirnInc/crypt"
"github.com/GehirnInc/crypt/apr1_crypt" "github.com/GehirnInc/crypt/apr1_crypt"
"github.com/GehirnInc/crypt/md5_crypt" "github.com/GehirnInc/crypt/md5_crypt"
"github.com/GehirnInc/crypt/sha256_crypt"
"github.com/GehirnInc/crypt/sha512_crypt" "github.com/GehirnInc/crypt/sha512_crypt"
"github.com/alexedwards/argon2id" "github.com/alexedwards/argon2id"
"github.com/go-chi/render" "github.com/go-chi/render"
@ -96,6 +97,7 @@ const (
pbkdf2SHA256B64SaltPrefix = "$pbkdf2-b64salt-sha256$" pbkdf2SHA256B64SaltPrefix = "$pbkdf2-b64salt-sha256$"
md5cryptPwdPrefix = "$1$" md5cryptPwdPrefix = "$1$"
md5cryptApr1PwdPrefix = "$apr1$" md5cryptApr1PwdPrefix = "$apr1$"
sha256cryptPwdPrefix = "$5$"
sha512cryptPwdPrefix = "$6$" sha512cryptPwdPrefix = "$6$"
md5LDAPPwdPrefix = "{MD5}" md5LDAPPwdPrefix = "{MD5}"
trackQuotaDisabledError = "please enable track_quota in your configuration to use this method" trackQuotaDisabledError = "please enable track_quota in your configuration to use this method"
@ -163,10 +165,10 @@ var (
internalHashPwdPrefixes = []string{argonPwdPrefix, bcryptPwdPrefix} internalHashPwdPrefixes = []string{argonPwdPrefix, bcryptPwdPrefix}
hashPwdPrefixes = []string{argonPwdPrefix, bcryptPwdPrefix, pbkdf2SHA1Prefix, pbkdf2SHA256Prefix, hashPwdPrefixes = []string{argonPwdPrefix, bcryptPwdPrefix, pbkdf2SHA1Prefix, pbkdf2SHA256Prefix,
pbkdf2SHA512Prefix, pbkdf2SHA256B64SaltPrefix, md5cryptPwdPrefix, md5cryptApr1PwdPrefix, md5LDAPPwdPrefix, pbkdf2SHA512Prefix, pbkdf2SHA256B64SaltPrefix, md5cryptPwdPrefix, md5cryptApr1PwdPrefix, md5LDAPPwdPrefix,
sha512cryptPwdPrefix} sha256cryptPwdPrefix, sha512cryptPwdPrefix}
pbkdfPwdPrefixes = []string{pbkdf2SHA1Prefix, pbkdf2SHA256Prefix, pbkdf2SHA512Prefix, pbkdf2SHA256B64SaltPrefix} pbkdfPwdPrefixes = []string{pbkdf2SHA1Prefix, pbkdf2SHA256Prefix, pbkdf2SHA512Prefix, pbkdf2SHA256B64SaltPrefix}
pbkdfPwdB64SaltPrefixes = []string{pbkdf2SHA256B64SaltPrefix} pbkdfPwdB64SaltPrefixes = []string{pbkdf2SHA256B64SaltPrefix}
unixPwdPrefixes = []string{md5cryptPwdPrefix, md5cryptApr1PwdPrefix, sha512cryptPwdPrefix} unixPwdPrefixes = []string{md5cryptPwdPrefix, md5cryptApr1PwdPrefix, sha256cryptPwdPrefix, sha512cryptPwdPrefix}
sharedProviders = []string{PGSQLDataProviderName, MySQLDataProviderName, CockroachDataProviderName} sharedProviders = []string{PGSQLDataProviderName, MySQLDataProviderName, CockroachDataProviderName}
logSender = "dataprovider" logSender = "dataprovider"
sqlTableUsers string sqlTableUsers string
@ -3067,6 +3069,8 @@ func compareUnixPasswordAndHash(user *User, password string) (bool, error) {
var crypter crypt.Crypter var crypter crypt.Crypter
if strings.HasPrefix(user.Password, sha512cryptPwdPrefix) { if strings.HasPrefix(user.Password, sha512cryptPwdPrefix) {
crypter = sha512_crypt.New() crypter = sha512_crypt.New()
} else if strings.HasPrefix(user.Password, sha256cryptPwdPrefix) {
crypter = sha256_crypt.New()
} else if strings.HasPrefix(user.Password, md5cryptPwdPrefix) { } else if strings.HasPrefix(user.Password, md5cryptPwdPrefix) {
crypter = md5_crypt.New() crypter = md5_crypt.New()
} else if strings.HasPrefix(user.Password, md5cryptApr1PwdPrefix) { } else if strings.HasPrefix(user.Password, md5cryptApr1PwdPrefix) {

View file

@ -7231,6 +7231,7 @@ func TestHashedPasswords(t *testing.T) {
pwdMapping["$1$b5caebda$VODr/nyhGWgZaY8sJ4x05."] = plainPwd pwdMapping["$1$b5caebda$VODr/nyhGWgZaY8sJ4x05."] = plainPwd
pwdMapping["$2a$14$ajq8Q7fbtFRQvXpdCq7Jcuy.Rx1h/L4J60Otx.gyNLbAYctGMJ9tK"] = "secret" pwdMapping["$2a$14$ajq8Q7fbtFRQvXpdCq7Jcuy.Rx1h/L4J60Otx.gyNLbAYctGMJ9tK"] = "secret"
pwdMapping["$6$459ead56b72e44bc$uog86fUxscjt28BZxqFBE2pp2QD8P/1e98MNF75Z9xJfQvOckZnQ/1YJqiq1XeytPuDieHZvDAMoP7352ELkO1"] = "secret" pwdMapping["$6$459ead56b72e44bc$uog86fUxscjt28BZxqFBE2pp2QD8P/1e98MNF75Z9xJfQvOckZnQ/1YJqiq1XeytPuDieHZvDAMoP7352ELkO1"] = "secret"
pwdMapping["$5$h4Aalt0fJdGX8sgv$Rd2ew0fvgzUN.DzAVlKa9QL4q/DZWo4SsKpB9.3AyZ/"] = plainPwd
pwdMapping["$apr1$OBWLeSme$WoJbB736e7kKxMBIAqilb1"] = plainPwd pwdMapping["$apr1$OBWLeSme$WoJbB736e7kKxMBIAqilb1"] = plainPwd
pwdMapping["{MD5}5f4dcc3b5aa765d61d8327deb882cf99"] = plainPwd pwdMapping["{MD5}5f4dcc3b5aa765d61d8327deb882cf99"] = plainPwd