diff --git a/src/main/resources/update/gitbucket-core_4.25.xml b/src/main/resources/update/gitbucket-core_4.25.xml new file mode 100644 index 0000000..b02b11c --- /dev/null +++ b/src/main/resources/update/gitbucket-core_4.25.xml @@ -0,0 +1,4 @@ + + + + diff --git a/src/main/scala/gitbucket/core/GitBucketCoreModule.scala b/src/main/scala/gitbucket/core/GitBucketCoreModule.scala index 24885e4..33b19f4 100644 --- a/src/main/scala/gitbucket/core/GitBucketCoreModule.scala +++ b/src/main/scala/gitbucket/core/GitBucketCoreModule.scala @@ -53,5 +53,6 @@ new Version("4.23.0", new LiquibaseMigration("update/gitbucket-core_4.23.xml")), new Version("4.23.1"), new Version("4.24.0", new LiquibaseMigration("update/gitbucket-core_4.24.xml")), - new Version("4.24.1") + new Version("4.24.1"), + new Version("4.25.0", new LiquibaseMigration("update/gitbucket-core_4.25.xml")) ) diff --git a/src/main/scala/gitbucket/core/controller/AccountController.scala b/src/main/scala/gitbucket/core/controller/AccountController.scala index 99a0bac..a8fc4ce 100644 --- a/src/main/scala/gitbucket/core/controller/AccountController.scala +++ b/src/main/scala/gitbucket/core/controller/AccountController.scala @@ -322,7 +322,7 @@ account => updateAccount( account.copy( - password = form.password.map(sha1).getOrElse(account.password), + password = form.password.map(pbkdf2_sha256).getOrElse(account.password), fullName = form.fullName, mailAddress = form.mailAddress, description = form.description, @@ -563,7 +563,7 @@ if (context.settings.allowAccountRegistration) { createAccount( form.userName, - sha1(form.password), + pbkdf2_sha256(form.password), form.fullName, form.mailAddress, false, diff --git a/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala b/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala index fc82666..69f0223 100644 --- a/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala +++ b/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala @@ -417,7 +417,7 @@ post("/admin/users/_newuser", newUserForm)(adminOnly { form => createAccount( form.userName, - sha1(form.password), + pbkdf2_sha256(form.password), form.fullName, form.mailAddress, form.isAdmin, @@ -457,7 +457,7 @@ updateAccount( account.copy( - password = form.password.map(sha1).getOrElse(account.password), + password = form.password.map(pbkdf2_sha256).getOrElse(account.password), fullName = form.fullName, mailAddress = form.mailAddress, isAdmin = form.isAdmin, diff --git a/src/main/scala/gitbucket/core/service/AccountService.scala b/src/main/scala/gitbucket/core/service/AccountService.scala index 8f0ba63..ea851b1 100644 --- a/src/main/scala/gitbucket/core/service/AccountService.scala +++ b/src/main/scala/gitbucket/core/service/AccountService.scala @@ -33,7 +33,16 @@ * Authenticate by internal database. */ private def defaultAuthentication(userName: String, password: String)(implicit s: Session) = { + val pbkdf2re = """^\$pbkdf2-sha256\$(\d+)\$([0-9a-zA-Z+/=]+)\$([0-9a-zA-Z+/=]+)$""".r getAccountByUserName(userName).collect { + case account if !account.isGroupAccount => + account.password match { + case pbkdf2re(iter, salt, hash) if (pbkdf2_sha256(iter.toInt, salt, password) == hash) => Some(account) + case p if p == sha1(password) => + updateAccount(account.copy(password = pbkdf2_sha256(password))) + Some(account) + case _ => None + } case account if (!account.isGroupAccount && account.password == sha1(password)) => Some(account) } getOrElse None } diff --git a/src/main/scala/gitbucket/core/util/StringUtil.scala b/src/main/scala/gitbucket/core/util/StringUtil.scala index bbbe5b4..b470eeb 100644 --- a/src/main/scala/gitbucket/core/util/StringUtil.scala +++ b/src/main/scala/gitbucket/core/util/StringUtil.scala @@ -1,10 +1,13 @@ package gitbucket.core.util import java.net.{URLDecoder, URLEncoder} +import java.security.SecureRandom import java.util.{Base64, UUID} import org.mozilla.universalchardet.UniversalDetector import SyntaxSugars._ +import javax.crypto.SecretKeyFactory +import javax.crypto.spec.PBEKeySpec import org.apache.commons.io.input.BOMInputStream import org.apache.commons.io.IOUtils @@ -16,6 +19,32 @@ UUID.randomUUID().toString.substring(0, 16) } + def base64Encode(value: Array[Byte]): String = { + Base64.getEncoder.encodeToString(value) + } + + def base64Decode(value: String): Array[Byte] = { + Base64.getDecoder.decode(value) + } + + def pbkdf2_sha256(iter: Int, salt: String, value: String): String = { + val keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256") + val ks = new PBEKeySpec(value.toCharArray, base64Decode(salt), iter, 256) + val s = keyFactory.generateSecret(ks) + base64Encode(s.getEncoded) + } + + def pbkdf2_sha256(value: String) = { + val keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256") + val secureRandom = new SecureRandom + val salt: Array[Byte] = new Array(32) + secureRandom.nextBytes(salt) + val iter = 100000 + val ks = new PBEKeySpec(value.toCharArray, salt, iter, 256) + val s = keyFactory.generateSecret(ks) + s"""$$pbkdf2-sha256$$${iter}$$${base64Encode(salt)}$$${base64Encode(s.getEncoded)}""" + } + def sha1(value: String): String = defining(java.security.MessageDigest.getInstance("SHA-1")) { md => md.update(value.getBytes)