Compare commits

...

2 Commits

Author SHA1 Message Date
591f6e2af4 parametrized password hashing algorithm for basic authentication 2025-02-20 16:46:15 +08:00
ad00ebee9b made logback configuration file overridable in Docker image without changing ENTRYPOINT
All checks were successful
CI / build (push) Successful in 3m30s
2025-02-20 13:39:27 +08:00
7 changed files with 70 additions and 11 deletions

View File

@@ -13,4 +13,5 @@ RUN mkdir plugins
WORKDIR /home/luser/plugins WORKDIR /home/luser/plugins
RUN --mount=type=bind,source=.,target=/build/distributions tar -xf /build/distributions/rbcs-server-memcache*.tar RUN --mount=type=bind,source=.,target=/build/distributions tar -xf /build/distributions/rbcs-server-memcache*.tar
WORKDIR /home/luser WORKDIR /home/luser
ENTRYPOINT ["java", "-XX:+UseSerialGC", "-XX:GCTimeRatio=24", "-jar", "/home/luser/rbcs.jar", "server"] ADD logback.xml .
ENTRYPOINT ["java", "-Dlogback.configurationFile=logback.xml", "-XX:+UseSerialGC", "-XX:GCTimeRatio=24", "-jar", "/home/luser/rbcs.jar", "server"]

View File

@@ -30,6 +30,9 @@ Provider<Copy> prepareDockerBuild = tasks.register('prepareDockerBuild', Copy) {
into project.layout.buildDirectory.file('docker') into project.layout.buildDirectory.file('docker')
from(configurations.docker) from(configurations.docker)
from(file('Dockerfile')) from(file('Dockerfile'))
from(rootProject.file('conf')) {
include 'logback.xml'
}
} }
Provider<DockerBuildImage> dockerBuild = tasks.register('dockerBuildImage', DockerBuildImage) { Provider<DockerBuildImage> dockerBuild = tasks.register('dockerBuildImage', DockerBuildImage) {
@@ -63,5 +66,3 @@ Provider<DockerPushImage> dockerPush = tasks.register('dockerPushImage', DockerP
} }
images = [dockerTag.flatMap{ it.tag }, dockerTagMemcache.flatMap{ it.tag }] images = [dockerTag.flatMap{ it.tag }, dockerTagMemcache.flatMap{ it.tag }]
} }

View File

@@ -85,6 +85,14 @@ tasks.named(JlinkPlugin.JLINK_TASK_NAME, JlinkTask) {
mainModule = 'net.woggioni.rbcs.cli' mainModule = 'net.woggioni.rbcs.cli'
} }
tasks.named(JavaPlugin.PROCESS_RESOURCES_TASK_NAME, ProcessResources) {
from(rootProject.file('conf')) {
into('net/woggioni/rbcs/cli')
include 'logback.xml'
include 'logging.properties'
}
}
artifacts { artifacts {
release(envelopeJarTaskProvider) release(envelopeJarTaskProvider)
} }

View File

@@ -7,7 +7,18 @@ import javax.crypto.SecretKeyFactory
import javax.crypto.spec.PBEKeySpec import javax.crypto.spec.PBEKeySpec
object PasswordSecurity { object PasswordSecurity {
private const val KEY_LENGTH = 256
enum class Algorithm(
val codeName : String,
val keyLength : Int,
val iterations : Int) {
PBEWithHmacSHA512_224AndAES_256("PBEWithHmacSHA512/224AndAES_256", 64, 1),
PBEWithHmacSHA1AndAES_256("PBEWithHmacSHA1AndAES_256",64, 1),
PBEWithHmacSHA384AndAES_128("PBEWithHmacSHA384AndAES_128", 64,1),
PBEWithHmacSHA384AndAES_256("PBEWithHmacSHA384AndAES_256",64,1),
PBKDF2WithHmacSHA512("PBKDF2WithHmacSHA512",512, 1),
PBKDF2WithHmacSHA384("PBKDF2WithHmacSHA384",384, 1);
}
private fun concat(arr1: ByteArray, arr2: ByteArray): ByteArray { private fun concat(arr1: ByteArray, arr2: ByteArray): ByteArray {
val result = ByteArray(arr1.size + arr2.size) val result = ByteArray(arr1.size + arr2.size)
@@ -23,22 +34,22 @@ object PasswordSecurity {
return result return result
} }
fun hashPassword(password : String, salt : String? = null) : String { fun hashPassword(password : String, salt : String? = null, algorithm : Algorithm = Algorithm.PBKDF2WithHmacSHA512) : String {
val actualSalt = salt?.let(Base64.getDecoder()::decode) ?: SecureRandom().run { val actualSalt = salt?.let(Base64.getDecoder()::decode) ?: SecureRandom().run {
val result = ByteArray(16) val result = ByteArray(16)
nextBytes(result) nextBytes(result)
result result
} }
val spec: KeySpec = PBEKeySpec(password.toCharArray(), actualSalt, 10, KEY_LENGTH) val spec: KeySpec = PBEKeySpec(password.toCharArray(), actualSalt, algorithm.iterations, algorithm.keyLength)
val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1") val factory = SecretKeyFactory.getInstance(algorithm.codeName)
val hash = factory.generateSecret(spec).encoded val hash = factory.generateSecret(spec).encoded
return String(Base64.getEncoder().encode(concat(hash, actualSalt))) return String(Base64.getEncoder().encode(concat(hash, actualSalt)))
} }
fun decodePasswordHash(passwordHash : String) : Pair<ByteArray, ByteArray> { fun decodePasswordHash(encodedPasswordHash : String, algorithm: Algorithm = Algorithm.PBKDF2WithHmacSHA512) : Pair<ByteArray, ByteArray> {
val decoded = Base64.getDecoder().decode(passwordHash) val decoded = Base64.getDecoder().decode(encodedPasswordHash)
val hash = ByteArray(KEY_LENGTH / 8) val hash = ByteArray(algorithm.keyLength / 8)
val salt = ByteArray(decoded.size - KEY_LENGTH / 8) val salt = ByteArray(decoded.size - algorithm.keyLength / 8)
System.arraycopy(decoded, 0, hash, 0, hash.size) System.arraycopy(decoded, 0, hash, 0, hash.size)
System.arraycopy(decoded, hash.size, salt, 0, salt.size) System.arraycopy(decoded, hash.size, salt, 0, salt.size)
return hash to salt return hash to salt

View File

@@ -0,0 +1,38 @@
package net.woggioni.rbcs.common
import net.woggioni.rbcs.common.PasswordSecurity.decodePasswordHash
import net.woggioni.rbcs.common.PasswordSecurity.hashPassword
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.EnumSource
import java.security.Provider
import java.security.Security
import java.util.Base64
class PasswordHashingTest {
@EnumSource(PasswordSecurity.Algorithm::class)
@ParameterizedTest
fun test(algo: PasswordSecurity.Algorithm) {
val password = "password"
val encoded = hashPassword(password, algorithm = algo)
val (_, salt) = decodePasswordHash(encoded, algo)
Assertions.assertEquals(encoded,
hashPassword(password, salt = salt.let(Base64.getEncoder()::encodeToString), algorithm = algo)
)
}
@Test
fun listAvailableAlgorithms() {
Security.getProviders().asSequence()
.flatMap { provider: Provider -> provider.services.asSequence() }
.filter { service: Provider.Service -> "SecretKeyFactory" == service.type }
.map(Provider.Service::getAlgorithm)
.forEach {
println(it)
}
}
}