Compare commits

...

2 Commits

Author SHA1 Message Date
e3619a2df1 implement keccak 2025-11-09 20:53:01 +01:00
88d673922a refactor package structure 2025-11-09 20:19:10 +01:00
15 changed files with 187 additions and 21 deletions

View File

@@ -23,6 +23,10 @@ kotlin {
implementation(libs.bytearray)
}
}
compilerOptions {
freeCompilerArgs.add("-opt-in=kotlin.ExperimentalUnsignedTypes")
}
}
publishing {

View File

@@ -1,7 +1,8 @@
package com.infendro.hash
sealed class HashFunction {
abstract class HashFunction {
abstract val bytes: Int
@Suppress("UNUSED")
val bits: Int
get() = bytes * 8

View File

@@ -0,0 +1,113 @@
package com.infendro.hash.keccak
import com.infendro.bytearray.ByteOrder.LITTLE_ENDIAN
import com.infendro.bytearray.addAll
import com.infendro.bytearray.toByteArray
import com.infendro.bytearray.toULongArray
import com.infendro.hash.HashFunction
import kotlin.math.min
@Suppress("UNUSED")
open class Keccak internal constructor(
override val bytes: Int,
private val rate: Int,
private val domain: Byte,
) : HashFunction() {
init {
if (rate % 8 != 0) {
throw IllegalArgumentException("rate must be divisible by 8")
}
}
private val rc = ulongArrayOf(
0x0000000000000001UL, 0x0000000000008082UL, 0x800000000000808aUL, 0x8000000080008000UL,
0x000000000000808bUL, 0x0000000080000001UL, 0x8000000080008081UL, 0x8000000000008009UL,
0x000000000000008aUL, 0x0000000000000088UL, 0x0000000080008009UL, 0x000000008000000aUL,
0x000000008000808bUL, 0x800000000000008bUL, 0x8000000000008089UL, 0x8000000000008003UL,
0x8000000000008002UL, 0x8000000000000080UL, 0x000000000000800aUL, 0x800000008000000aUL,
0x8000000080008081UL, 0x8000000000008080UL, 0x0000000080000001UL, 0x8000000080008008UL
)
private val rotation = arrayOf(
intArrayOf(0, 36, 3, 41, 18),
intArrayOf(1, 44, 10, 45, 2),
intArrayOf(62, 6, 43, 15, 61),
intArrayOf(28, 55, 25, 21, 56),
intArrayOf(27, 20, 39, 8, 14),
)
override fun hash(
value: ByteArray,
): ByteArray {
val paddedValue = buildList {
addAll(value)
add(domain)
while ((size % rate) != (rate - 1)) {
add(0x00.toByte())
}
add(0x80.toByte())
}
val state = ULongArray(25)
// absorb
val chunks = paddedValue
.chunked(rate)
for (chunk in chunks) {
val numbers = chunk.toULongArray(LITTLE_ENDIAN)
for ((i, number) in numbers.withIndex()) {
state[i] = state[i] xor number
}
permute(state)
}
// squeeze
return buildList(bytes) {
var i = 0
while (size < bytes) {
if (i == rate / 8) {
permute(state)
i = 0
}
addAll(
state[i++].toByteArray(LITTLE_ENDIAN)
.take(min(bytes - size, 8))
)
}
}.toByteArray()
}
private fun permute(
a: ULongArray,
) {
val b = ULongArray(25)
val c = ULongArray(5)
val d = ULongArray(5)
repeat(24) { round ->
// θ
for (x in 0..4) {
c[x] = a[x] xor a[x + 5] xor a[x + 10] xor a[x + 15] xor a[x + 20]
}
for (x in 0..4) {
d[x] = c[(x + 4) % 5] xor c[(x + 1) % 5].rotateLeft(1)
}
for (x in 0..4) for (y in 0..4) {
a[x + 5 * y] = a[x + 5 * y] xor d[x]
}
// ρ and π
for (x in 0..4) for (y in 0..4) {
b[y + 5 * ((2 * x + 3 * y) % 5)] = a[x + 5 * y].rotateLeft(rotation[x][y])
}
// χ
for (x in 0..4) for (y in 0..4) {
a[x + 5 * y] = b[x + 5 * y] xor ((b[((x + 1) % 5) + 5 * y].inv()) and b[((x + 2) % 5) + 5 * y])
}
// ι
a[0] = a[0] xor rc[round]
}
}
}

View File

@@ -0,0 +1,6 @@
package com.infendro.hash.keccak.sha3
import com.infendro.hash.keccak.Keccak
@Suppress("UNUSED")
object `SHA3-224` : Keccak(28, 144, 0x06)

View File

@@ -0,0 +1,6 @@
package com.infendro.hash.keccak.sha3
import com.infendro.hash.keccak.Keccak
@Suppress("UNUSED")
object `SHA3-256` : Keccak(32, 136, 0x06)

View File

@@ -0,0 +1,6 @@
package com.infendro.hash.keccak.sha3
import com.infendro.hash.keccak.Keccak
@Suppress("UNUSED")
object `SHA3-384` : Keccak(48, 104, 0x06)

View File

@@ -0,0 +1,6 @@
package com.infendro.hash.keccak.sha3
import com.infendro.hash.keccak.Keccak
@Suppress("UNUSED")
object `SHA3-512` : Keccak(64, 72, 0x06)

View File

@@ -0,0 +1,6 @@
package com.infendro.hash.keccak.shake
import com.infendro.hash.keccak.Keccak
@Suppress("UNUSED")
class SHAKE128(bytes: Int) : Keccak(bytes, 168, 0x1F)

View File

@@ -0,0 +1,6 @@
package com.infendro.hash.keccak.shake
import com.infendro.hash.keccak.Keccak
@Suppress("UNUSED")
class SHAKE256(bytes: Int) : Keccak(bytes, 136, 0x1F)

View File

@@ -1,10 +1,12 @@
package com.infendro.hash
package com.infendro.hash.md
import com.infendro.bytearray.ByteOrder.LITTLE_ENDIAN
import com.infendro.bytearray.ByteOrder
import com.infendro.bytearray.addAll
import com.infendro.bytearray.toByteArray
import com.infendro.bytearray.toUIntArray
import com.infendro.hash.HashFunction
@Suppress("UNUSED")
object MD5 : HashFunction() {
override val bytes: Int
get() = 16
@@ -62,14 +64,14 @@ object MD5 : HashFunction() {
while (size % 64 != 56) {
add(0x00.toByte())
}
addAll(ml.toByteArray(LITTLE_ENDIAN))
addAll(ml.toByteArray(ByteOrder.LITTLE_ENDIAN))
}
val chunks = paddedValue
.chunked(64)
for (chunk in chunks) {
val w = buildList {
addAll(chunk.toUIntArray(LITTLE_ENDIAN))
addAll(chunk.toUIntArray(ByteOrder.LITTLE_ENDIAN))
}
var a = a0
@@ -103,10 +105,10 @@ object MD5 : HashFunction() {
}
return byteArrayOf(
*a0.toByteArray(LITTLE_ENDIAN),
*b0.toByteArray(LITTLE_ENDIAN),
*c0.toByteArray(LITTLE_ENDIAN),
*d0.toByteArray(LITTLE_ENDIAN),
*a0.toByteArray(ByteOrder.LITTLE_ENDIAN),
*b0.toByteArray(ByteOrder.LITTLE_ENDIAN),
*c0.toByteArray(ByteOrder.LITTLE_ENDIAN),
*d0.toByteArray(ByteOrder.LITTLE_ENDIAN),
)
}
}
}

View File

@@ -1,9 +1,11 @@
package com.infendro.hash
package com.infendro.hash.sha1
import com.infendro.bytearray.addAll
import com.infendro.bytearray.toByteArray
import com.infendro.bytearray.toUIntArray
import com.infendro.hash.HashFunction
@Suppress("UNUSED")
object SHA1 : HashFunction() {
override val bytes: Int
get() = 20
@@ -80,4 +82,4 @@ object SHA1 : HashFunction() {
*h4.toByteArray(),
)
}
}
}

View File

@@ -1,10 +1,12 @@
package com.infendro.hash
package com.infendro.hash.sha2
import com.infendro.bytearray.addAll
import com.infendro.bytearray.toByteArray
import com.infendro.bytearray.toUIntArray
import com.infendro.hash.HashFunction
object SHA224 : HashFunction() {
@Suppress("UNUSED")
object `SHA-224` : HashFunction() {
override val bytes: Int
get() = 28
@@ -63,10 +65,10 @@ object SHA224 : HashFunction() {
var h = h7
for (i in 0..<64) {
val s0 = a.rotateRight(2) xor a.rotateRight(13) xor a.rotateRight(22)
val s1 = e.rotateRight(6) xor e.rotateRight(11) xor e.rotateRight(25)
val ch = (e and f) xor (e.inv() and g)
val temp1 = h + s1 + ch + k[i] + w[i]
val s0 = a.rotateRight(2) xor a.rotateRight(13) xor a.rotateRight(22)
val maj = (a and b) xor (a and c) xor (b and c)
val temp2 = s0 + maj

View File

@@ -1,10 +1,12 @@
package com.infendro.hash
package com.infendro.hash.sha2
import com.infendro.bytearray.addAll
import com.infendro.bytearray.toByteArray
import com.infendro.bytearray.toUIntArray
import com.infendro.hash.HashFunction
object SHA256 : HashFunction() {
@Suppress("UNUSED")
object `SHA-256` : HashFunction() {
override val bytes: Int
get() = 32

View File

@@ -1,10 +1,12 @@
package com.infendro.hash
package com.infendro.hash.sha2
import com.infendro.bytearray.addAll
import com.infendro.bytearray.toByteArray
import com.infendro.bytearray.toULongArray
import com.infendro.hash.HashFunction
object SHA384 : HashFunction() {
@Suppress("UNUSED")
object `SHA-384` : HashFunction() {
override val bytes: Int
get() = 48

View File

@@ -1,10 +1,12 @@
package com.infendro.hash
package com.infendro.hash.sha2
import com.infendro.bytearray.addAll
import com.infendro.bytearray.toByteArray
import com.infendro.bytearray.toULongArray
import com.infendro.hash.HashFunction
object SHA512 : HashFunction() {
@Suppress("UNUSED")
object `SHA-512` : HashFunction() {
override val bytes: Int
get() = 64