Skip to content

Commit

Permalink
added support for verifying sha256/sha512 passwords hash
Browse files Browse the repository at this point in the history
this simplifies the migration of users from some proprietary products

Signed-off-by: Nicola Murino <[email protected]>
  • Loading branch information
drakkan committed Jun 3, 2023
1 parent 48939b2 commit 74e5999
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 9 deletions.
4 changes: 3 additions & 1 deletion docs/full-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,9 @@ Supported hash algorithms:
- MD5 crypt APR1, prefix `$apr1$`
- SHA256 crypt, prefix `$5$`
- 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.
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.
Expand Down
34 changes: 27 additions & 7 deletions internal/dataprovider/dataprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,9 @@ const (
sha256cryptPwdPrefix = "$5$"
sha512cryptPwdPrefix = "$6$"
yescryptPwdPrefix = "$y$"
md5LDAPPwdPrefix = "{MD5}"
md5DigestPwdPrefix = "{MD5}"
sha256DigestPwdPrefix = "{SHA256}"
sha512DigestPwdPrefix = "{SHA512}"
trackQuotaDisabledError = "please enable track_quota in your configuration to use this method"
operationAdd = "add"
operationUpdate = "update"
Expand Down Expand Up @@ -180,12 +182,13 @@ var (
sqlPlaceholders []string
internalHashPwdPrefixes = []string{argonPwdPrefix, bcryptPwdPrefix}
hashPwdPrefixes = []string{argonPwdPrefix, bcryptPwdPrefix, pbkdf2SHA1Prefix, pbkdf2SHA256Prefix,
pbkdf2SHA512Prefix, pbkdf2SHA256B64SaltPrefix, md5cryptPwdPrefix, md5cryptApr1PwdPrefix, md5LDAPPwdPrefix,
sha256cryptPwdPrefix, sha512cryptPwdPrefix, yescryptPwdPrefix}
pbkdf2SHA512Prefix, pbkdf2SHA256B64SaltPrefix, md5cryptPwdPrefix, md5cryptApr1PwdPrefix, md5DigestPwdPrefix,
sha256DigestPwdPrefix, sha512DigestPwdPrefix, sha256cryptPwdPrefix, sha512cryptPwdPrefix, yescryptPwdPrefix}
pbkdfPwdPrefixes = []string{pbkdf2SHA1Prefix, pbkdf2SHA256Prefix, pbkdf2SHA512Prefix, pbkdf2SHA256B64SaltPrefix}
pbkdfPwdB64SaltPrefixes = []string{pbkdf2SHA256B64SaltPrefix}
unixPwdPrefixes = []string{md5cryptPwdPrefix, md5cryptApr1PwdPrefix, sha256cryptPwdPrefix, sha512cryptPwdPrefix,
yescryptPwdPrefix}
digestPwdPrefixes = []string{md5DigestPwdPrefix, sha256DigestPwdPrefix, sha512DigestPwdPrefix}
sharedProviders = []string{PGSQLDataProviderName, MySQLDataProviderName, CockroachDataProviderName}
logSender = "dataprovider"
sqlTableUsers string
Expand Down Expand Up @@ -3211,10 +3214,8 @@ func isPasswordOK(user *User, password string) (bool, error) {
if err != nil {
return match, err
}
} else if strings.HasPrefix(user.Password, md5LDAPPwdPrefix) {
h := md5.New()
h.Write([]byte(password))
match = fmt.Sprintf("%s%x", md5LDAPPwdPrefix, h.Sum(nil)) == user.Password
} else if util.IsStringPrefixInSlice(user.Password, digestPwdPrefixes) {
match = compareDigestPasswordAndHash(user, password)
}
if err == nil && match {
cachedUserPasswords.Add(user.Username, password, user.Password)
Expand Down Expand Up @@ -3377,6 +3378,25 @@ func checkUserAndPubKey(user *User, pubKey []byte, isSSHCert bool) (User, string
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) {
if strings.HasPrefix(user.Password, yescryptPwdPrefix) {
return compareYescryptPassword(user.Password, password)
Expand Down
3 changes: 2 additions & 1 deletion internal/sftpd/sftpd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7416,7 +7416,8 @@ func TestHashedPasswords(t *testing.T) {
pwdMapping["$5$h4Aalt0fJdGX8sgv$Rd2ew0fvgzUN.DzAVlKa9QL4q/DZWo4SsKpB9.3AyZ/"] = plainPwd
pwdMapping["$apr1$OBWLeSme$WoJbB736e7kKxMBIAqilb1"] = plainPwd
pwdMapping["{MD5}5f4dcc3b5aa765d61d8327deb882cf99"] = plainPwd

pwdMapping["{SHA256}5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"] = plainPwd
pwdMapping["{SHA512}b109f3bbbc244eb82441917ed06d618b9008dd09b3befd1b5e07394c706a8bb980b1d7785e5976ec049b46df5f1326af5a2ea6d103fd07c95385ffab0cacbc86"] = plainPwd
for pwd, clearPwd := range pwdMapping {
u := getTestUser(usePubKey)
u.Password = pwd
Expand Down

0 comments on commit 74e5999

Please sign in to comment.