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
|
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
|
||||||
|
@@ -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