Compare commits

..

6 Commits

Author SHA1 Message Date
9a9543a1ff bump version to 1.1.0
All checks were successful
/ publish (push) Successful in 3m59s
2025-12-13 02:34:39 +01:00
d6449e647a fix workflow 2025-12-13 01:30:59 +01:00
a43cf1467d use common plugin 2025-12-13 01:23:24 +01:00
26cff5d331 implement tests 2025-12-12 16:11:08 +01:00
8f21a346db implement common interface 2025-12-12 16:10:57 +01:00
6dec2fea74 upgrade dependencies 2025-12-12 16:06:26 +01:00
7 changed files with 198 additions and 63 deletions

View File

@@ -4,7 +4,7 @@ on:
- main - main
env: env:
GIT__TOKEN: ${{ secrets.TOKEN }} INFENDRO__TOKEN: ${{ secrets.TOKEN }}
jobs: jobs:
publish: publish:

View File

@@ -1,12 +1,11 @@
group = "com.infendro" import com.infendro.plugin.infendro
version = "1.0.3" import com.infendro.plugin.token
repositories { group = "com.infendro"
maven("https://git.infendro.com/api/packages/Infendro/maven") version = "1.1.0"
mavenCentral()
}
plugins { plugins {
alias(libs.plugins.infendro)
alias(libs.plugins.multiplatform) alias(libs.plugins.multiplatform)
id("maven-publish") id("maven-publish")
} }
@@ -18,40 +17,27 @@ kotlin {
} }
linuxX64() linuxX64()
repositories {
infendro()
mavenCentral()
}
sourceSets { sourceSets {
commonMain.dependencies { commonMain.dependencies {
implementation(libs.mac) implementation(libs.mac)
implementation(libs.hash) implementation(libs.hash)
implementation(libs.bytearray) implementation(libs.bytearray)
} }
commonTest.dependencies {
implementation(libs.test)
} }
} }
publishing { compilerOptions {
repositories { freeCompilerArgs.add("-opt-in=kotlin.ExperimentalUnsignedTypes")
maven {
url = uri("https://git.infendro.com/api/packages/Infendro/maven")
authentication.create("header", HttpHeaderAuthentication::class)
credentials(HttpHeaderCredentials::class) {
val token = secret("git.token") ?: throw GradleException()
name = "Authorization"
value = "token $token"
}
}
} }
} }
fun secret( publishing.repositories {
key: String, infendro(token)
): String? {
val property = key
val environment = key
.uppercase()
.replace(".", "__")
val prop = properties[property] as String?
val env = System.getenv(environment)
return prop ?: env
} }

View File

@@ -1,13 +1,16 @@
[versions] [versions]
kotlin = "2.2.10" infendro = "1.0.0"
mac = "1.1.2" kotlin = "2.2.21"
hash = "1.2.2" mac = "1.2.0"
bytearray = "1.1.1" hash = "1.3.2"
bytearray = "1.2.4"
[plugins] [plugins]
infendro = { id = "com.infendro.plugin", version.ref = "infendro" }
multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
[libraries] [libraries]
mac = { module = "com.infendro:mac", version.ref = "mac" } mac = { module = "com.infendro:mac", version.ref = "mac" }
hash = { module = "com.infendro:hash", version.ref = "hash" } hash = { module = "com.infendro:hash", version.ref = "hash" }
bytearray = { module = "com.infendro:bytearray", version.ref = "bytearray" } bytearray = { module = "com.infendro:bytearray", version.ref = "bytearray" }
test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }

View File

@@ -1 +1,6 @@
rootProject.name = "kdf" rootProject.name = "kdf"
pluginManagement.repositories {
maven("https://git.infendro.com/api/packages/Infendro/maven")
mavenCentral()
}

View File

@@ -0,0 +1,12 @@
package com.infendro.kdf
interface KDF {
val bytes: Int
val bits: Int
get() = bytes * 8
fun hash(
value: UByteArray,
salt: UByteArray,
): UByteArray
}

View File

@@ -1,39 +1,49 @@
package com.infendro.kdf package com.infendro.kdf
import com.infendro.bytearray.addAll import com.infendro.bytearray.toUByteArray
import com.infendro.bytearray.toByteArray
import com.infendro.bytearray.xor import com.infendro.bytearray.xor
import com.infendro.hash.HashFunction import com.infendro.hash.HashFunction
import com.infendro.mac.HMAC import com.infendro.mac.HMAC
import kotlin.math.min
class PBKDF2( class PBKDF2(
private val function: HashFunction, override val bytes: Int,
) { private val iterations: Int,
private val hmac = HMAC(function) function: HashFunction,
) : KDF {
init {
if (bytes < 1) throw IllegalArgumentException()
if (iterations < 1) throw IllegalArgumentException()
}
fun hash( private val mac = HMAC(function)
value: ByteArray,
salt: ByteArray,
iterations: Int,
length: Int,
): ByteArray {
val key = buildList {
for (i in 1..(length + function.bytes - 1) / function.bytes) {
val input = salt + i.toUInt().toByteArray()
var u = hmac.hash(value, input) override fun hash(
var result = u value: UByteArray,
salt: UByteArray,
): UByteArray {
return buildList {
var i = 1
while (size < bytes) {
addAll(
f(value, salt, i)
.sliceArray(0..<min(bytes - size, mac.bytes))
)
i++
}
}.toUByteArray()
}
private fun f(
value: UByteArray,
salt: UByteArray,
iteration: Int,
): UByteArray {
return buildList {
add(mac.hash(value, salt + iteration.toUByteArray()))
repeat(iterations - 1) { repeat(iterations - 1) {
u = hmac.hash(value, u) add(mac.hash(value, last()))
result = result xor u
} }
}.reduce { acc, bytes -> acc xor bytes }
addAll(result)
}
}
return key
.take(length)
.toByteArray()
} }
} }

View File

@@ -0,0 +1,119 @@
package com.infendro.kdf
import com.infendro.bytearray.encodeToUByteArray
import com.infendro.hash.sha2.`SHA-256`
import kotlin.test.Test
import kotlin.test.assertContentEquals
class `PBKDF2 Test` {
@Test
fun `32 bytes, 1 iteration`() {
val function = PBKDF2(32, 1, `SHA-256`)
val expected = ubyteArrayOf(
0x12U, 0x0fU, 0xb6U, 0xcfU, 0xfcU, 0xf8U, 0xb3U, 0x2cU,
0x43U, 0xe7U, 0x22U, 0x52U, 0x56U, 0xc4U, 0xf8U, 0x37U,
0xa8U, 0x65U, 0x48U, 0xc9U, 0x2cU, 0xccU, 0x35U, 0x48U,
0x08U, 0x05U, 0x98U, 0x7cU, 0xb7U, 0x0bU, 0xe1U, 0x7bU,
)
val actual = function.hash(
"password".encodeToUByteArray(),
"salt".encodeToUByteArray(),
)
assertContentEquals(expected, actual)
}
@Test
fun `32 bytes, 2 iterations`() {
val function = PBKDF2(32, 2, `SHA-256`)
val expected = ubyteArrayOf(
0xaeU, 0x4dU, 0x0cU, 0x95U, 0xafU, 0x6bU, 0x46U, 0xd3U,
0x2dU, 0x0aU, 0xdfU, 0xf9U, 0x28U, 0xf0U, 0x6dU, 0xd0U,
0x2aU, 0x30U, 0x3fU, 0x8eU, 0xf3U, 0xc2U, 0x51U, 0xdfU,
0xd6U, 0xe2U, 0xd8U, 0x5aU, 0x95U, 0x47U, 0x4cU, 0x43U,
)
val actual = function.hash(
"password".encodeToUByteArray(),
"salt".encodeToUByteArray(),
)
assertContentEquals(expected, actual)
}
@Test
fun `32 bytes, 4096 iterations`() {
val function = PBKDF2(32, 4096, `SHA-256`)
val expected = ubyteArrayOf(
0xc5U, 0xe4U, 0x78U, 0xd5U, 0x92U, 0x88U, 0xc8U, 0x41U,
0xaaU, 0x53U, 0x0dU, 0xb6U, 0x84U, 0x5cU, 0x4cU, 0x8dU,
0x96U, 0x28U, 0x93U, 0xa0U, 0x01U, 0xceU, 0x4eU, 0x11U,
0xa4U, 0x96U, 0x38U, 0x73U, 0xaaU, 0x98U, 0x13U, 0x4aU,
)
val actual = function.hash(
"password".encodeToUByteArray(),
"salt".encodeToUByteArray(),
)
assertContentEquals(expected, actual)
}
@Test
fun `40 bytes, 4096 iterations`() {
val function = PBKDF2(40, 4096, `SHA-256`)
val expected = ubyteArrayOf(
0xc5U, 0xe4U, 0x78U, 0xd5U, 0x92U, 0x88U, 0xc8U, 0x41U,
0xaaU, 0x53U, 0x0dU, 0xb6U, 0x84U, 0x5cU, 0x4cU, 0x8dU,
0x96U, 0x28U, 0x93U, 0xa0U, 0x01U, 0xceU, 0x4eU, 0x11U,
0xa4U, 0x96U, 0x38U, 0x73U, 0xaaU, 0x98U, 0x13U, 0x4aU,
0xf7U, 0xadU, 0x98U, 0xc1U, 0xb4U, 0x58U, 0xceU, 0x3fU,
)
val actual = function.hash(
"password".encodeToUByteArray(),
"salt".encodeToUByteArray(),
)
assertContentEquals(expected, actual)
}
@Test
fun `64 bytes, 1 iteration`() {
val function = PBKDF2(64, 1, `SHA-256`)
val expected = ubyteArrayOf(
0x55U, 0xacU, 0x04U, 0x6eU, 0x56U, 0xe3U, 0x08U, 0x9fU,
0xecU, 0x16U, 0x91U, 0xc2U, 0x25U, 0x44U, 0xb6U, 0x05U,
0xf9U, 0x41U, 0x85U, 0x21U, 0x6dU, 0xdeU, 0x04U, 0x65U,
0xe6U, 0x8bU, 0x9dU, 0x57U, 0xc2U, 0x0dU, 0xacU, 0xbcU,
0x49U, 0xcaU, 0x9cU, 0xccU, 0xf1U, 0x79U, 0xb6U, 0x45U,
0x99U, 0x16U, 0x64U, 0xb3U, 0x9dU, 0x77U, 0xefU, 0x31U,
0x7cU, 0x71U, 0xb8U, 0x45U, 0xb1U, 0xe3U, 0x0bU, 0xd5U,
0x09U, 0x11U, 0x20U, 0x41U, 0xd3U, 0xa1U, 0x97U, 0x83U,
)
val actual = function.hash(
"passwd".encodeToUByteArray(),
"salt".encodeToUByteArray(),
)
assertContentEquals(expected, actual)
}
@Test
fun `64 bytes, 80,000 iterations`() {
val function = PBKDF2(64, 80_000, `SHA-256`)
val expected = ubyteArrayOf(
0x4dU, 0xdcU, 0xd8U, 0xf6U, 0x0bU, 0x98U, 0xbeU, 0x21U,
0x83U, 0x0cU, 0xeeU, 0x5eU, 0xf2U, 0x27U, 0x01U, 0xf9U,
0x64U, 0x1aU, 0x44U, 0x18U, 0xd0U, 0x4cU, 0x04U, 0x14U,
0xaeU, 0xffU, 0x08U, 0x87U, 0x6bU, 0x34U, 0xabU, 0x56U,
0xa1U, 0xd4U, 0x25U, 0xa1U, 0x22U, 0x58U, 0x33U, 0x54U,
0x9aU, 0xdbU, 0x84U, 0x1bU, 0x51U, 0xc9U, 0xb3U, 0x17U,
0x6aU, 0x27U, 0x2bU, 0xdeU, 0xbbU, 0xa1U, 0xd0U, 0x78U,
0x47U, 0x8fU, 0x62U, 0xb3U, 0x97U, 0xf3U, 0x3cU, 0x8dU,
)
val actual = function.hash(
"Password".encodeToByteArray().toUByteArray(),
"NaCl".encodeToByteArray().toUByteArray()
)
assertContentEquals(expected, actual)
}
}