aboutsummaryrefslogtreecommitdiffstats
path: root/models
diff options
context:
space:
mode:
authorEpicCoder <EpicCoder@users.noreply.github.com>2019-07-07 08:01:01 +0200
committertechknowlogick <techknowlogick@gitea.io>2019-07-07 02:01:01 -0400
commit8d9d6aa903baf3662fa31bceb489291564a873d1 (patch)
tree7a952e1c0fce29af1de19df54fa570e24ff09fcc /models
parent1b85b248e42a9d45d4dc278d399ddab3edcedfdb (diff)
downloadgitea-8d9d6aa903baf3662fa31bceb489291564a873d1.tar.gz
gitea-8d9d6aa903baf3662fa31bceb489291564a873d1.zip
Add additional password hash algorithms (closes #5859) (#6023)
Diffstat (limited to 'models')
-rw-r--r--models/login_source.go10
-rw-r--r--models/user.go48
-rw-r--r--models/user_test.go38
3 files changed, 74 insertions, 22 deletions
diff --git a/models/login_source.go b/models/login_source.go
index 626c232772..26544588c1 100644
--- a/models/login_source.go
+++ b/models/login_source.go
@@ -22,6 +22,7 @@ import (
"code.gitea.io/gitea/modules/auth/oauth2"
"code.gitea.io/gitea/modules/auth/pam"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
)
@@ -665,6 +666,15 @@ func UserSignIn(username, password string) (*User, error) {
switch user.LoginType {
case LoginNoType, LoginPlain, LoginOAuth2:
if user.IsPasswordSet() && user.ValidatePassword(password) {
+
+ // Update password hash if server password hash algorithm have changed
+ if user.PasswdHashAlgo != setting.PasswordHashAlgo {
+ user.HashPassword(password)
+ if err := UpdateUserCols(user, "passwd", "passwd_hash_algo"); err != nil {
+ return nil, err
+ }
+ }
+
// WARN: DON'T check user.IsActive, that will be checked on reqSign so that
// user could be hint to resend confirm email.
if user.ProhibitLogin {
diff --git a/models/user.go b/models/user.go
index aa392b1ea9..fc0dfee187 100644
--- a/models/user.go
+++ b/models/user.go
@@ -33,7 +33,10 @@ import (
"github.com/Unknwon/com"
"github.com/go-xorm/xorm"
+ "golang.org/x/crypto/argon2"
+ "golang.org/x/crypto/bcrypt"
"golang.org/x/crypto/pbkdf2"
+ "golang.org/x/crypto/scrypt"
"golang.org/x/crypto/ssh"
"xorm.io/builder"
"xorm.io/core"
@@ -50,6 +53,13 @@ const (
UserTypeOrganization
)
+const (
+ algoBcrypt = "bcrypt"
+ algoScrypt = "scrypt"
+ algoArgon2 = "argon2"
+ algoPbkdf2 = "pbkdf2"
+)
+
const syncExternalUsers = "sync_external_users"
var (
@@ -82,6 +92,7 @@ type User struct {
Email string `xorm:"NOT NULL"`
KeepEmailPrivate bool
Passwd string `xorm:"NOT NULL"`
+ PasswdHashAlgo string `xorm:"NOT NULL DEFAULT 'pbkdf2'"`
// MustChangePassword is an attribute that determines if a user
// is to change his/her password after registration.
@@ -430,25 +441,48 @@ func (u *User) NewGitSig() *git.Signature {
}
}
-func hashPassword(passwd, salt string) string {
- tempPasswd := pbkdf2.Key([]byte(passwd), []byte(salt), 10000, 50, sha256.New)
+func hashPassword(passwd, salt, algo string) string {
+ var tempPasswd []byte
+
+ switch algo {
+ case algoBcrypt:
+ tempPasswd, _ = bcrypt.GenerateFromPassword([]byte(passwd), bcrypt.DefaultCost)
+ return string(tempPasswd)
+ case algoScrypt:
+ tempPasswd, _ = scrypt.Key([]byte(passwd), []byte(salt), 65536, 16, 2, 50)
+ case algoArgon2:
+ tempPasswd = argon2.IDKey([]byte(passwd), []byte(salt), 2, 65536, 8, 50)
+ case algoPbkdf2:
+ fallthrough
+ default:
+ tempPasswd = pbkdf2.Key([]byte(passwd), []byte(salt), 10000, 50, sha256.New)
+ }
+
return fmt.Sprintf("%x", tempPasswd)
}
-// HashPassword hashes a password using PBKDF.
+// HashPassword hashes a password using the algorithm defined in the config value of PASSWORD_HASH_ALGO.
func (u *User) HashPassword(passwd string) {
- u.Passwd = hashPassword(passwd, u.Salt)
+ u.PasswdHashAlgo = setting.PasswordHashAlgo
+ u.Passwd = hashPassword(passwd, u.Salt, setting.PasswordHashAlgo)
}
// ValidatePassword checks if given password matches the one belongs to the user.
func (u *User) ValidatePassword(passwd string) bool {
- tempHash := hashPassword(passwd, u.Salt)
- return subtle.ConstantTimeCompare([]byte(u.Passwd), []byte(tempHash)) == 1
+ tempHash := hashPassword(passwd, u.Salt, u.PasswdHashAlgo)
+
+ if u.PasswdHashAlgo != algoBcrypt && subtle.ConstantTimeCompare([]byte(u.Passwd), []byte(tempHash)) == 1 {
+ return true
+ }
+ if u.PasswdHashAlgo == algoBcrypt && bcrypt.CompareHashAndPassword([]byte(u.Passwd), []byte(passwd)) == nil {
+ return true
+ }
+ return false
}
// IsPasswordSet checks if the password is set or left empty
func (u *User) IsPasswordSet() bool {
- return !u.ValidatePassword("")
+ return len(u.Passwd) > 0
}
// UploadAvatar saves custom avatar for user.
diff --git a/models/user_test.go b/models/user_test.go
index 6af9752c9b..10420a143f 100644
--- a/models/user_test.go
+++ b/models/user_test.go
@@ -147,21 +147,29 @@ func TestHashPasswordDeterministic(t *testing.T) {
b := make([]byte, 16)
rand.Read(b)
u := &User{Salt: string(b)}
- for i := 0; i < 50; i++ {
- // generate a random password
- rand.Read(b)
- pass := string(b)
-
- // save the current password in the user - hash it and store the result
- u.HashPassword(pass)
- r1 := u.Passwd
-
- // run again
- u.HashPassword(pass)
- r2 := u.Passwd
-
- // assert equal (given the same salt+pass, the same result is produced)
- assert.Equal(t, r1, r2)
+ algos := []string{"pbkdf2", "argon2", "scrypt", "bcrypt"}
+ for j := 0; j < len(algos); j++ {
+ u.PasswdHashAlgo = algos[j]
+ for i := 0; i < 50; i++ {
+ // generate a random password
+ rand.Read(b)
+ pass := string(b)
+
+ // save the current password in the user - hash it and store the result
+ u.HashPassword(pass)
+ r1 := u.Passwd
+
+ // run again
+ u.HashPassword(pass)
+ r2 := u.Passwd
+
+ // assert equal (given the same salt+pass, the same result is produced) except bcrypt
+ if u.PasswdHashAlgo == "bcrypt" {
+ assert.NotEqual(t, r1, r2)
+ } else {
+ assert.Equal(t, r1, r2)
+ }
+ }
}
}