parametrized password hashing algorithm for basic authentication

This commit is contained in:
2025-02-20 16:27:06 +08:00
parent ad00ebee9b
commit 591f6e2af4
2 changed files with 57 additions and 8 deletions

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)
}
}
}