parametrized password hashing algorithm for basic authentication
This commit is contained in:
@@ -7,7 +7,18 @@ import javax.crypto.SecretKeyFactory
|
||||
import javax.crypto.spec.PBEKeySpec
|
||||
|
||||
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 {
|
||||
val result = ByteArray(arr1.size + arr2.size)
|
||||
@@ -23,22 +34,22 @@ object PasswordSecurity {
|
||||
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 result = ByteArray(16)
|
||||
nextBytes(result)
|
||||
result
|
||||
}
|
||||
val spec: KeySpec = PBEKeySpec(password.toCharArray(), actualSalt, 10, KEY_LENGTH)
|
||||
val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
|
||||
val spec: KeySpec = PBEKeySpec(password.toCharArray(), actualSalt, algorithm.iterations, algorithm.keyLength)
|
||||
val factory = SecretKeyFactory.getInstance(algorithm.codeName)
|
||||
val hash = factory.generateSecret(spec).encoded
|
||||
return String(Base64.getEncoder().encode(concat(hash, actualSalt)))
|
||||
}
|
||||
|
||||
fun decodePasswordHash(passwordHash : String) : Pair<ByteArray, ByteArray> {
|
||||
val decoded = Base64.getDecoder().decode(passwordHash)
|
||||
val hash = ByteArray(KEY_LENGTH / 8)
|
||||
val salt = ByteArray(decoded.size - KEY_LENGTH / 8)
|
||||
fun decodePasswordHash(encodedPasswordHash : String, algorithm: Algorithm = Algorithm.PBKDF2WithHmacSHA512) : Pair<ByteArray, ByteArray> {
|
||||
val decoded = Base64.getDecoder().decode(encodedPasswordHash)
|
||||
val hash = ByteArray(algorithm.keyLength / 8)
|
||||
val salt = ByteArray(decoded.size - algorithm.keyLength / 8)
|
||||
System.arraycopy(decoded, 0, hash, 0, hash.size)
|
||||
System.arraycopy(decoded, hash.size, salt, 0, salt.size)
|
||||
return hash to salt
|
||||
|
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user