async hashing of channels and flows (big data)
This commit is contained in:
parent
054252a3ce
commit
95c052a22c
@ -47,6 +47,8 @@ kotlin {
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.0")
|
||||
|
||||
implementation("com.ionspin.kotlin:multiplatform-crypto-libsodium-bindings:0.9.2")
|
||||
implementation(platform("org.kotlincrypto.hash:bom:0.5.1"))
|
||||
implementation("org.kotlincrypto.hash:sha3")
|
||||
api("com.ionspin.kotlin:bignum:0.3.9")
|
||||
api("net.sergeych:mp_bintools:0.1.7")
|
||||
api("net.sergeych:mp_stools:1.5.1")
|
||||
|
@ -2,21 +2,114 @@ package net.sergeych.crypto2
|
||||
|
||||
import com.ionspin.kotlin.crypto.generichash.GenericHash
|
||||
import com.ionspin.kotlin.crypto.util.encodeToUByteArray
|
||||
import org.komputing.khash.keccak.Keccak
|
||||
import org.komputing.khash.keccak.KeccakParameter
|
||||
import kotlinx.coroutines.channels.ReceiveChannel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import org.kotlincrypto.hash.sha3.SHA3_256
|
||||
import org.kotlincrypto.hash.sha3.SHA3_384
|
||||
|
||||
private interface StreamProcessor {
|
||||
fun update(data: UByteArray)
|
||||
fun final(): UByteArray
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash support for crypto2. We implement only secure as for the crypto2 publication time functions,
|
||||
* and do not include broken algorithms like SHA1 and SHA2, Md5, etc. To calculate hashes use:
|
||||
*
|
||||
* - [digest] to calculate a hash of the block in memory
|
||||
* - [ofChannel] to calculate hash of long or slow data in a channel
|
||||
* - [ofFlow] to calculate hash of the data in the [Flow<UByteArray>]
|
||||
*/
|
||||
@Suppress("unused")
|
||||
enum class Hash(val perform: (UByteArray)->UByteArray) {
|
||||
Blake2b({ GenericHash.genericHash(it) }),
|
||||
Blake2b2l({ blake2b2l(it) }),
|
||||
Sha3_384({ Keccak.digest(it.asByteArray(), KeccakParameter.SHA3_384).asUByteArray()}),
|
||||
Sha3_256({ Keccak.digest(it.asByteArray(), KeccakParameter.SHA3_256).asUByteArray()}),
|
||||
enum class Hash(private val direct: ((UByteArray) -> UByteArray)? = null,private val streamProcessor: () -> StreamProcessor) {
|
||||
|
||||
Blake2b(
|
||||
// direct blacke2 is faster than stream:
|
||||
{ GenericHash.genericHash(it) }, {
|
||||
object : StreamProcessor {
|
||||
val state = GenericHash.genericHashInit()
|
||||
override fun update(data: UByteArray) {
|
||||
GenericHash.genericHashUpdate(state, data)
|
||||
}
|
||||
|
||||
override fun final(): UByteArray =
|
||||
GenericHash.genericHashFinal(state)
|
||||
}
|
||||
}),
|
||||
Sha3_384(
|
||||
// direct Keccaak currently is slower
|
||||
null,
|
||||
{
|
||||
object : StreamProcessor {
|
||||
val state = SHA3_384()
|
||||
override fun update(data: UByteArray) {
|
||||
state.update(data.asByteArray())
|
||||
}
|
||||
|
||||
override fun final(): UByteArray = state.digest().asUByteArray()
|
||||
}
|
||||
}),
|
||||
Sha3_256(null,
|
||||
{
|
||||
object : StreamProcessor {
|
||||
val state = SHA3_256()
|
||||
override fun update(data: UByteArray) {
|
||||
state.update(data.asByteArray())
|
||||
}
|
||||
|
||||
override fun final(): UByteArray = state.digest().asUByteArray()
|
||||
}
|
||||
});
|
||||
|
||||
@Deprecated("will be removed in favor of digest()", ReplaceWith("digest()"))
|
||||
fun perform(src: UByteArray): UByteArray = digest(src)
|
||||
|
||||
/**
|
||||
* Calculate digest for the in-memory data
|
||||
* @param src data to calculate hash digest over
|
||||
* @return calculated hash value
|
||||
*/
|
||||
fun digest(src: UByteArray): UByteArray = direct?.invoke(src) ?: streamProcessor().also { it.update(src)}.final()
|
||||
|
||||
/**
|
||||
* Collect the flow and return the hash digest of all the data. Let calculate hashes on data
|
||||
* that are too big to fit in memory
|
||||
*/
|
||||
suspend fun ofFlow(source: Flow<UByteArray>): UByteArray {
|
||||
val sp = streamProcessor()
|
||||
source.collect { sp.update(it) }
|
||||
return sp.final()
|
||||
}
|
||||
|
||||
/**
|
||||
* Read all data from the channel and return the hash digest of all the data. Let calculate hashes on data
|
||||
* that are too big to fit in memory
|
||||
*/
|
||||
suspend fun ofChannel(source: ReceiveChannel<UByteArray>): UByteArray {
|
||||
val sp = streamProcessor()
|
||||
for( block in source) sp.update(block)
|
||||
return sp.final()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private val defaultSuffix1 = "All lay loads on a willing horse".encodeToUByteArray()
|
||||
private val defaultSuffix2 = "A stitch in time saves nine".encodeToUByteArray()
|
||||
|
||||
fun blake2b(src: UByteArray): UByteArray = Hash.Blake2b.perform(src)
|
||||
fun blake2b2l(src: UByteArray): UByteArray =
|
||||
blake2b(blake2b(src) + defaultSuffix1 + src)
|
||||
/**
|
||||
* Caclulate [Hash.Blake2b] hash for [src], shortcut for [Hash.Blake2b.digest]
|
||||
*/
|
||||
fun blake2b(src: UByteArray): UByteArray = Hash.Blake2b.digest(src)
|
||||
|
||||
/**
|
||||
* Double linked Blake2b using the default or specified suffix. This should be more hard to
|
||||
* brute force.collision attack than just [blake2b]. Note that different suffixes provide different
|
||||
* results.
|
||||
*/
|
||||
fun blake2b2l(src: UByteArray,suffix: UByteArray = defaultSuffix1): UByteArray =
|
||||
blake2b(blake2b(src) + suffix + src)
|
||||
|
||||
/**
|
||||
* Triple linked [blake2b], even more prone to collision attacks than [blake2b2l].
|
||||
*/
|
||||
fun blake2b3l(src: UByteArray): UByteArray = blake2b(blake2b2l(src) + defaultSuffix2 + src)
|
||||
|
@ -4,6 +4,7 @@ import com.ionspin.kotlin.bignum.integer.BigInteger
|
||||
import org.komputing.khash.keccak.extensions.fillWith
|
||||
import kotlin.math.min
|
||||
|
||||
@Deprecated("use Hash enum instead, will be removed in next major release", ReplaceWith("Hash", "net.sergeych.crypto2.Hash"))
|
||||
object Keccak {
|
||||
|
||||
private val BIT_65 = BigInteger.ONE shl (64)
|
||||
|
38
src/commonTest/kotlin/HashTest.kt
Normal file
38
src/commonTest/kotlin/HashTest.kt
Normal file
@ -0,0 +1,38 @@
|
||||
import kotlinx.coroutines.flow.asFlow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.datetime.Clock
|
||||
import net.sergeych.crypto2.Hash
|
||||
import net.sergeych.crypto2.initCrypto
|
||||
import kotlin.random.Random
|
||||
import kotlin.random.nextUBytes
|
||||
import kotlin.test.*
|
||||
|
||||
@Suppress("UNUSED_PARAMETER", "UNUSED_VARIABLE")
|
||||
suspend fun <T> sw(label: String, f: suspend () -> T): T {
|
||||
val t1 = Clock.System.now()
|
||||
val result = f()
|
||||
val t2 = Clock.System.now()
|
||||
// println("$label: ${t2 - t1}")
|
||||
return result
|
||||
}
|
||||
|
||||
class HashTest {
|
||||
@Test
|
||||
fun testEqualMethods() {
|
||||
fun testMethod(h: Hash) = runTest {
|
||||
initCrypto()
|
||||
val a = Random.Default.nextUBytes(1024)
|
||||
val b = Random.Default.nextUBytes(1024)
|
||||
val c = a + b
|
||||
val h1 = sw("dir $h") { h.digest(c) }
|
||||
val h2 = sw("ind $h") { h.ofFlow(listOf(a, b).asFlow()) }
|
||||
|
||||
assertContentEquals(h1, h2)
|
||||
}
|
||||
for (i in 0..10) {
|
||||
testMethod(Hash.Blake2b)
|
||||
testMethod(Hash.Sha3_256)
|
||||
testMethod(Hash.Sha3_384)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user