From 1e21aa945338dae031fcf49495f2f802fcc0ffd6 Mon Sep 17 00:00:00 2001 From: Nicola Murino Date: Wed, 28 Sep 2022 19:15:02 +0200 Subject: [PATCH] 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 --- docs/account.md | 2 +- docs/full-configuration.md | 1 + internal/dataprovider/dataprovider.go | 8 ++++++-- internal/sftpd/sftpd_test.go | 1 + 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/account.md b/docs/account.md index d812fb29..e384e442 100644 --- a/docs/account.md +++ b/docs/account.md @@ -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. -SFTPGo supports checking passwords stored with bcrypt, pbkdf2, md5crypt and sha512crypt too. For pbkdf2 the supported format is `$$$$`, 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 `$$$$`, 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: diff --git a/docs/full-configuration.md b/docs/full-configuration.md index 1f17e6dd..8d0eeef9 100644 --- a/docs/full-configuration.md +++ b/docs/full-configuration.md @@ -502,6 +502,7 @@ Supported hash algorithms: - PBKDF2 sha256 with base64 salt, prefix `$pbkdf2-b64salt-sha256$` - MD5 crypt, prefix `$1$` - MD5 crypt APR1, prefix `$apr1$` +- SHA256 crypt, prefix `$5$` - SHA512 crypt, prefix `$6$` - LDAP MD5, prefix `{MD5}` diff --git a/internal/dataprovider/dataprovider.go b/internal/dataprovider/dataprovider.go index aa4c12f8..5bdcc2ff 100644 --- a/internal/dataprovider/dataprovider.go +++ b/internal/dataprovider/dataprovider.go @@ -51,6 +51,7 @@ import ( "github.com/GehirnInc/crypt" "github.com/GehirnInc/crypt/apr1_crypt" "github.com/GehirnInc/crypt/md5_crypt" + "github.com/GehirnInc/crypt/sha256_crypt" "github.com/GehirnInc/crypt/sha512_crypt" "github.com/alexedwards/argon2id" "github.com/go-chi/render" @@ -96,6 +97,7 @@ const ( pbkdf2SHA256B64SaltPrefix = "$pbkdf2-b64salt-sha256$" md5cryptPwdPrefix = "$1$" md5cryptApr1PwdPrefix = "$apr1$" + sha256cryptPwdPrefix = "$5$" sha512cryptPwdPrefix = "$6$" md5LDAPPwdPrefix = "{MD5}" trackQuotaDisabledError = "please enable track_quota in your configuration to use this method" @@ -163,10 +165,10 @@ var ( internalHashPwdPrefixes = []string{argonPwdPrefix, bcryptPwdPrefix} hashPwdPrefixes = []string{argonPwdPrefix, bcryptPwdPrefix, pbkdf2SHA1Prefix, pbkdf2SHA256Prefix, pbkdf2SHA512Prefix, pbkdf2SHA256B64SaltPrefix, md5cryptPwdPrefix, md5cryptApr1PwdPrefix, md5LDAPPwdPrefix, - sha512cryptPwdPrefix} + sha256cryptPwdPrefix, sha512cryptPwdPrefix} pbkdfPwdPrefixes = []string{pbkdf2SHA1Prefix, pbkdf2SHA256Prefix, pbkdf2SHA512Prefix, pbkdf2SHA256B64SaltPrefix} pbkdfPwdB64SaltPrefixes = []string{pbkdf2SHA256B64SaltPrefix} - unixPwdPrefixes = []string{md5cryptPwdPrefix, md5cryptApr1PwdPrefix, sha512cryptPwdPrefix} + unixPwdPrefixes = []string{md5cryptPwdPrefix, md5cryptApr1PwdPrefix, sha256cryptPwdPrefix, sha512cryptPwdPrefix} sharedProviders = []string{PGSQLDataProviderName, MySQLDataProviderName, CockroachDataProviderName} logSender = "dataprovider" sqlTableUsers string @@ -3067,6 +3069,8 @@ func compareUnixPasswordAndHash(user *User, password string) (bool, error) { var crypter crypt.Crypter if strings.HasPrefix(user.Password, sha512cryptPwdPrefix) { crypter = sha512_crypt.New() + } else if strings.HasPrefix(user.Password, sha256cryptPwdPrefix) { + crypter = sha256_crypt.New() } else if strings.HasPrefix(user.Password, md5cryptPwdPrefix) { crypter = md5_crypt.New() } else if strings.HasPrefix(user.Password, md5cryptApr1PwdPrefix) { diff --git a/internal/sftpd/sftpd_test.go b/internal/sftpd/sftpd_test.go index a12096b2..1b811232 100644 --- a/internal/sftpd/sftpd_test.go +++ b/internal/sftpd/sftpd_test.go @@ -7231,6 +7231,7 @@ func TestHashedPasswords(t *testing.T) { pwdMapping["$1$b5caebda$VODr/nyhGWgZaY8sJ4x05."] = plainPwd pwdMapping["$2a$14$ajq8Q7fbtFRQvXpdCq7Jcuy.Rx1h/L4J60Otx.gyNLbAYctGMJ9tK"] = "secret" pwdMapping["$6$459ead56b72e44bc$uog86fUxscjt28BZxqFBE2pp2QD8P/1e98MNF75Z9xJfQvOckZnQ/1YJqiq1XeytPuDieHZvDAMoP7352ELkO1"] = "secret" + pwdMapping["$5$h4Aalt0fJdGX8sgv$Rd2ew0fvgzUN.DzAVlKa9QL4q/DZWo4SsKpB9.3AyZ/"] = plainPwd pwdMapping["$apr1$OBWLeSme$WoJbB736e7kKxMBIAqilb1"] = plainPwd pwdMapping["{MD5}5f4dcc3b5aa765d61d8327deb882cf99"] = plainPwd