added support for verifying sha256/sha512 passwords hash

this simplifies the migration of users from some proprietary products

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
Nicola Murino 2023-06-03 16:58:45 +02:00
parent 48939b2b4f
commit 74e5999c63
No known key found for this signature in database
GPG key ID: 935D2952DEC4EECF
3 changed files with 32 additions and 9 deletions

View file

@ -585,7 +585,9 @@ Supported hash algorithms:
- MD5 crypt APR1, prefix `$apr1$` - MD5 crypt APR1, prefix `$apr1$`
- SHA256 crypt, prefix `$5$` - SHA256 crypt, prefix `$5$`
- SHA512 crypt, prefix `$6$` - SHA512 crypt, prefix `$6$`
- LDAP MD5, prefix `{MD5}` - MD5 digest, prefix `{MD5}`
- SHA256 digest, prefix `{SHA256}`
- SHA512 digest, prefix `{SHA512}`
If you set a password with one of these prefixes it will not be hashed. If you set a password with one of these prefixes it will not be hashed.
When users log in, if their passwords are stored with anything other than the preferred algorithm, SFTPGo will automatically upgrade the algorithm to the preferred one. When users log in, if their passwords are stored with anything other than the preferred algorithm, SFTPGo will automatically upgrade the algorithm to the preferred one.

View file

@ -100,7 +100,9 @@ const (
sha256cryptPwdPrefix = "$5$" sha256cryptPwdPrefix = "$5$"
sha512cryptPwdPrefix = "$6$" sha512cryptPwdPrefix = "$6$"
yescryptPwdPrefix = "$y$" yescryptPwdPrefix = "$y$"
md5LDAPPwdPrefix = "{MD5}" md5DigestPwdPrefix = "{MD5}"
sha256DigestPwdPrefix = "{SHA256}"
sha512DigestPwdPrefix = "{SHA512}"
trackQuotaDisabledError = "please enable track_quota in your configuration to use this method" trackQuotaDisabledError = "please enable track_quota in your configuration to use this method"
operationAdd = "add" operationAdd = "add"
operationUpdate = "update" operationUpdate = "update"
@ -180,12 +182,13 @@ var (
sqlPlaceholders []string sqlPlaceholders []string
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, md5DigestPwdPrefix,
sha256cryptPwdPrefix, sha512cryptPwdPrefix, yescryptPwdPrefix} sha256DigestPwdPrefix, sha512DigestPwdPrefix, sha256cryptPwdPrefix, sha512cryptPwdPrefix, yescryptPwdPrefix}
pbkdfPwdPrefixes = []string{pbkdf2SHA1Prefix, pbkdf2SHA256Prefix, pbkdf2SHA512Prefix, pbkdf2SHA256B64SaltPrefix} pbkdfPwdPrefixes = []string{pbkdf2SHA1Prefix, pbkdf2SHA256Prefix, pbkdf2SHA512Prefix, pbkdf2SHA256B64SaltPrefix}
pbkdfPwdB64SaltPrefixes = []string{pbkdf2SHA256B64SaltPrefix} pbkdfPwdB64SaltPrefixes = []string{pbkdf2SHA256B64SaltPrefix}
unixPwdPrefixes = []string{md5cryptPwdPrefix, md5cryptApr1PwdPrefix, sha256cryptPwdPrefix, sha512cryptPwdPrefix, unixPwdPrefixes = []string{md5cryptPwdPrefix, md5cryptApr1PwdPrefix, sha256cryptPwdPrefix, sha512cryptPwdPrefix,
yescryptPwdPrefix} yescryptPwdPrefix}
digestPwdPrefixes = []string{md5DigestPwdPrefix, sha256DigestPwdPrefix, sha512DigestPwdPrefix}
sharedProviders = []string{PGSQLDataProviderName, MySQLDataProviderName, CockroachDataProviderName} sharedProviders = []string{PGSQLDataProviderName, MySQLDataProviderName, CockroachDataProviderName}
logSender = "dataprovider" logSender = "dataprovider"
sqlTableUsers string sqlTableUsers string
@ -3211,10 +3214,8 @@ func isPasswordOK(user *User, password string) (bool, error) {
if err != nil { if err != nil {
return match, err return match, err
} }
} else if strings.HasPrefix(user.Password, md5LDAPPwdPrefix) { } else if util.IsStringPrefixInSlice(user.Password, digestPwdPrefixes) {
h := md5.New() match = compareDigestPasswordAndHash(user, password)
h.Write([]byte(password))
match = fmt.Sprintf("%s%x", md5LDAPPwdPrefix, h.Sum(nil)) == user.Password
} }
if err == nil && match { if err == nil && match {
cachedUserPasswords.Add(user.Username, password, user.Password) cachedUserPasswords.Add(user.Username, password, user.Password)
@ -3377,6 +3378,25 @@ func checkUserAndPubKey(user *User, pubKey []byte, isSSHCert bool) (User, string
return *user, "", ErrInvalidCredentials return *user, "", ErrInvalidCredentials
} }
func compareDigestPasswordAndHash(user *User, password string) bool {
if strings.HasPrefix(user.Password, md5DigestPwdPrefix) {
h := md5.New()
h.Write([]byte(password))
return fmt.Sprintf("%s%x", md5DigestPwdPrefix, h.Sum(nil)) == user.Password
}
if strings.HasPrefix(user.Password, sha256DigestPwdPrefix) {
h := sha256.New()
h.Write([]byte(password))
return fmt.Sprintf("%s%x", sha256DigestPwdPrefix, h.Sum(nil)) == user.Password
}
if strings.HasPrefix(user.Password, sha512DigestPwdPrefix) {
h := sha512.New()
h.Write([]byte(password))
return fmt.Sprintf("%s%x", sha512DigestPwdPrefix, h.Sum(nil)) == user.Password
}
return false
}
func compareUnixPasswordAndHash(user *User, password string) (bool, error) { func compareUnixPasswordAndHash(user *User, password string) (bool, error) {
if strings.HasPrefix(user.Password, yescryptPwdPrefix) { if strings.HasPrefix(user.Password, yescryptPwdPrefix) {
return compareYescryptPassword(user.Password, password) return compareYescryptPassword(user.Password, password)

View file

@ -7416,7 +7416,8 @@ func TestHashedPasswords(t *testing.T) {
pwdMapping["$5$h4Aalt0fJdGX8sgv$Rd2ew0fvgzUN.DzAVlKa9QL4q/DZWo4SsKpB9.3AyZ/"] = plainPwd 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
pwdMapping["{SHA256}5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"] = plainPwd
pwdMapping["{SHA512}b109f3bbbc244eb82441917ed06d618b9008dd09b3befd1b5e07394c706a8bb980b1d7785e5976ec049b46df5f1326af5a2ea6d103fd07c95385ffab0cacbc86"] = plainPwd
for pwd, clearPwd := range pwdMapping { for pwd, clearPwd := range pwdMapping {
u := getTestUser(usePubKey) u := getTestUser(usePubKey)
u.Password = pwd u.Password = pwd