Added "streaming" blake2b support
This commit is contained in:
		
							parent
							
								
									f17aa19d08
								
							
						
					
					
						commit
						1852db686c
					
				@ -0,0 +1,27 @@
 | 
			
		||||
/*
 | 
			
		||||
 *    Copyright 2019 Ugljesa Jovanovic
 | 
			
		||||
 *
 | 
			
		||||
 *    Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 *    you may not use this file except in compliance with the License.
 | 
			
		||||
 *    You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *        http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 *    Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 *    distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 *    See the License for the specific language governing permissions and
 | 
			
		||||
 *    limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package com.ionspin.kotlin.crypto
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Created by Ugljesa Jovanovic
 | 
			
		||||
 * ugljesa.jovanovic@ionspin.com
 | 
			
		||||
 * on 20-Jul-2019
 | 
			
		||||
 */
 | 
			
		||||
interface Hash
 | 
			
		||||
 | 
			
		||||
interface StreamingHash : Hash
 | 
			
		||||
 | 
			
		||||
@ -18,8 +18,12 @@ package com.ionspin.kotlin.crypto.blake2b
 | 
			
		||||
 | 
			
		||||
import com.ionspin.kotlin.bignum.integer.BigInteger
 | 
			
		||||
import com.ionspin.kotlin.bignum.integer.toBigInteger
 | 
			
		||||
import com.ionspin.kotlin.crypto.hexColumsPrint
 | 
			
		||||
import com.ionspin.kotlin.crypto.rotateRight
 | 
			
		||||
import com.ionspin.kotlin.crypto.*
 | 
			
		||||
import kotlinx.coroutines.CoroutineScope
 | 
			
		||||
import kotlinx.coroutines.Dispatchers
 | 
			
		||||
import kotlinx.coroutines.Job
 | 
			
		||||
import kotlinx.coroutines.async
 | 
			
		||||
import kotlin.text.chunked
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Created by Ugljesa Jovanovic
 | 
			
		||||
@ -28,8 +32,8 @@ import com.ionspin.kotlin.crypto.rotateRight
 | 
			
		||||
 */
 | 
			
		||||
@ExperimentalStdlibApi
 | 
			
		||||
@ExperimentalUnsignedTypes
 | 
			
		||||
class Blake2b {
 | 
			
		||||
    companion object {
 | 
			
		||||
class Blake2b(val key: Array<UByte>? = null, val hashLength: Int = 64) : StreamingHash {
 | 
			
		||||
    companion object : Hash {
 | 
			
		||||
 | 
			
		||||
        const val BITS_IN_WORD = 64
 | 
			
		||||
        const val ROUNDS_IN_COMPRESS = 12
 | 
			
		||||
@ -143,9 +147,9 @@ class Blake2b {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        fun digest(inputString: String, key: String? = null): Array<UByte> {
 | 
			
		||||
            val chunked = inputString.encodeToByteArray().map {it.toUByte() }.toList().chunked(BLOCK_BYTES).map { it.toTypedArray() }.toTypedArray()
 | 
			
		||||
            val chunked = inputString.encodeToByteArray().map { it.toUByte() }.toList().chunked(BLOCK_BYTES)
 | 
			
		||||
                .map { it.toTypedArray() }.toTypedArray()
 | 
			
		||||
            val keyBytes = key?.run {
 | 
			
		||||
                encodeToByteArray().map { it.toUByte() }.toTypedArray()
 | 
			
		||||
            } ?: emptyArray()
 | 
			
		||||
@ -163,7 +167,6 @@ class Blake2b {
 | 
			
		||||
            h[0] = h[0] xor 0x01010000UL xor (secretKey.size.toULong() shl 8) xor hashLength.toULong()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            val message = if (secretKey.isEmpty()) {
 | 
			
		||||
                if (inputMessage.isEmpty()) {
 | 
			
		||||
                    Array(1) {
 | 
			
		||||
@ -181,7 +184,6 @@ class Blake2b {
 | 
			
		||||
            if (message.size > 1) {
 | 
			
		||||
                for (i in 0 until message.size - 1) {
 | 
			
		||||
                    compress(h, message[i], ((i + 1) * BLOCK_BYTES).toBigInteger(), false).copyInto(h)
 | 
			
		||||
                    h.hexColumsPrint()
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -201,6 +203,10 @@ class Blake2b {
 | 
			
		||||
            compress(h, lastBlockPadded, lastSize.toBigInteger(), true).copyInto(h)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            return formatResult(h)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private fun formatResult(h: Array<ULong>): Array<UByte> {
 | 
			
		||||
            return h.map {
 | 
			
		||||
                arrayOf(
 | 
			
		||||
                    (it and 0xFFUL).toUByte(),
 | 
			
		||||
@ -217,7 +223,7 @@ class Blake2b {
 | 
			
		||||
            }.toTypedArray()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private inline fun padToBlock(unpadded: Array<UByte>): Array<UByte> {
 | 
			
		||||
        private fun padToBlock(unpadded: Array<UByte>): Array<UByte> {
 | 
			
		||||
            if (unpadded.size == BLOCK_BYTES) {
 | 
			
		||||
                return unpadded
 | 
			
		||||
            }
 | 
			
		||||
@ -236,17 +242,106 @@ class Blake2b {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun digest(inputString: String, key: String? = null): Array<UByte> {
 | 
			
		||||
        return Blake2b.digest(inputString, key)
 | 
			
		||||
    constructor(
 | 
			
		||||
        key: String?,
 | 
			
		||||
        requestedHashLenght: Int = 64
 | 
			
		||||
    ) : this(
 | 
			
		||||
        (key?.encodeToByteArray()?.map { it.toUByte() }?.toTypedArray() ?: emptyArray<UByte>()),
 | 
			
		||||
        requestedHashLenght
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    val job = Job()
 | 
			
		||||
    val scope = CoroutineScope(Dispatchers.Default + job)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    var h = iv.copyOf()
 | 
			
		||||
    var counter = BigInteger.ZERO
 | 
			
		||||
    var bufferCounter = 0
 | 
			
		||||
    var buffer = Array<UByte>(BLOCK_BYTES) { 0U }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        h[0] = h[0] xor 0x01010000UL xor (key?.run { size.toULong() shl 8 } ?: 0UL) xor hashLength.toULong()
 | 
			
		||||
 | 
			
		||||
        if (!key.isNullOrEmpty()) {
 | 
			
		||||
            appendToBuffer(padToBlock(key), bufferCounter)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun updateBlocking(array: Array<UByte>) {
 | 
			
		||||
        if (array.isEmpty()) {
 | 
			
		||||
            throw RuntimeException("Updating with empty array is not allowed. If you need empty hash, just call digest without updating")
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        when {
 | 
			
		||||
            bufferCounter + array.size < BLOCK_BYTES -> appendToBuffer(array, bufferCounter)
 | 
			
		||||
            bufferCounter + array.size >= BLOCK_BYTES -> {
 | 
			
		||||
                val chunked = array.chunked(BLOCK_BYTES)
 | 
			
		||||
                chunked.forEach { chunk ->
 | 
			
		||||
                    if (bufferCounter + chunk.size < BLOCK_BYTES) {
 | 
			
		||||
                        appendToBuffer(chunk, bufferCounter)
 | 
			
		||||
                    } else {
 | 
			
		||||
                        chunk.copyInto(
 | 
			
		||||
                            destination = buffer,
 | 
			
		||||
                            destinationOffset = bufferCounter,
 | 
			
		||||
                            startIndex = 0,
 | 
			
		||||
                            endIndex = BLOCK_BYTES - bufferCounter
 | 
			
		||||
                        )
 | 
			
		||||
                        counter += BLOCK_BYTES
 | 
			
		||||
                        consumeBlock(buffer)
 | 
			
		||||
                        buffer = Array<UByte>(BLOCK_BYTES) {
 | 
			
		||||
                            when (it) {
 | 
			
		||||
                                in (0 until (chunk.size - (BLOCK_BYTES - bufferCounter))) -> {
 | 
			
		||||
                                    chunk[it + (BLOCK_BYTES - bufferCounter)]
 | 
			
		||||
                                }
 | 
			
		||||
                                else -> {
 | 
			
		||||
                                    0U
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                        }
 | 
			
		||||
                        bufferCounter = chunk.size - (BLOCK_BYTES - bufferCounter)
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun updateBlocking(input: String) {
 | 
			
		||||
        updateBlocking(input.encodeToByteArray().map { it.toUByte() }.toTypedArray())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun appendToBuffer(array: Array<UByte>, start: Int) {
 | 
			
		||||
        array.copyInto(destination = buffer, destinationOffset = start, startIndex = 0, endIndex = array.size)
 | 
			
		||||
        bufferCounter += array.size
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun consumeBlock(block: Array<UByte>) {
 | 
			
		||||
        h = compress(h, block, counter, false)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    suspend fun update(array: Array<UByte>) {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    fun digest(): Array<UByte> {
 | 
			
		||||
        val lastBlockPadded = padToBlock(buffer)
 | 
			
		||||
        counter += bufferCounter
 | 
			
		||||
        compress(h, lastBlockPadded, counter, true)
 | 
			
		||||
 | 
			
		||||
        val result = formatResult(h)
 | 
			
		||||
        println(result.map { it.toString(16) }.joinToString(separator = ""))
 | 
			
		||||
        return result
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    fun digestString(): String {
 | 
			
		||||
        return digest().map { it.toString(16) }.joinToString(separator = "")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -48,6 +48,21 @@ class Blake2bKnowAnswerTests {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun knownAnswerTestStreaming() {
 | 
			
		||||
 | 
			
		||||
        kat.forEach { kat ->
 | 
			
		||||
            val parsedInput = kat.input.chunked(2).map { it.toUByte(16) }.toTypedArray()
 | 
			
		||||
            val chunkedInput = parsedInput.toList().chunked(128).map { it.toTypedArray() }.toTypedArray()
 | 
			
		||||
            val blake2b = Blake2b(key = kat.key.chunked(2).map { it.toUByte(16) }.toTypedArray())
 | 
			
		||||
            chunkedInput.forEach { blake2b.updateBlocking(it) }
 | 
			
		||||
            val result = blake2b.digest()
 | 
			
		||||
            assertTrue("KAT ${kat.input} \nkey: ${kat.key} \nexpected: {${kat.hash}") {
 | 
			
		||||
                result.contentEquals(kat.hash.chunked(2).map { it.toUByte(16) }.toTypedArray())
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    val kat = arrayOf(
 | 
			
		||||
        KnownAnswerTest(
 | 
			
		||||
            input = "",
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,96 @@
 | 
			
		||||
/*
 | 
			
		||||
 *    Copyright 2019 Ugljesa Jovanovic
 | 
			
		||||
 *
 | 
			
		||||
 *    Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 *    you may not use this file except in compliance with the License.
 | 
			
		||||
 *    You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *        http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 *    Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 *    distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 *    See the License for the specific language governing permissions and
 | 
			
		||||
 *    limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package com.ionspin.kotlin.crypto.blake2b
 | 
			
		||||
 | 
			
		||||
import kotlin.test.Test
 | 
			
		||||
import kotlin.test.assertTrue
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Created by Ugljesa Jovanovic
 | 
			
		||||
 * ugljesa.jovanovic@ionspin.com
 | 
			
		||||
 * on 20-Jul-2019
 | 
			
		||||
 */
 | 
			
		||||
@ExperimentalUnsignedTypes
 | 
			
		||||
@ExperimentalStdlibApi
 | 
			
		||||
class Blake2bStreaming {
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testStreamingBlake2b() {
 | 
			
		||||
        val updates = 14
 | 
			
		||||
        val input = "1234567890"
 | 
			
		||||
        val expectedResult = arrayOf<UByte>(
 | 
			
		||||
            //@formatter:off
 | 
			
		||||
            0x2fU, 0x49U, 0xaeU, 0xb6U, 0x13U, 0xe3U, 0x4eU, 0x92U, 0x4eU, 0x17U, 0x5aU, 0x6aU, 0xf2U, 0xfaU, 0xadU,
 | 
			
		||||
            0x7bU, 0xc7U, 0x82U, 0x35U, 0xf9U, 0xc5U, 0xe4U, 0x61U, 0xc6U, 0x8fU, 0xd5U, 0xb4U, 0x07U, 0xeeU, 0x8eU,
 | 
			
		||||
            0x2fU, 0x0dU, 0x2fU, 0xb4U, 0xc0U, 0x7dU, 0x7eU, 0x4aU, 0x72U, 0x40U, 0x46U, 0x12U, 0xd9U, 0x28U, 0x99U,
 | 
			
		||||
            0xafU, 0x8aU, 0x32U, 0x8fU, 0x3bU, 0x61U, 0x4eU, 0xd7U, 0x72U, 0x44U, 0xb4U, 0x81U, 0x15U, 0x1dU, 0x40U,
 | 
			
		||||
            0xb1U, 0x1eU, 0x32U, 0xa4U
 | 
			
		||||
            //@formatter:on
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        val blake2b = Blake2b()
 | 
			
		||||
        for (i in 0 until updates) {
 | 
			
		||||
            blake2b.updateBlocking(input)
 | 
			
		||||
        }
 | 
			
		||||
        val result = blake2b.digest()
 | 
			
		||||
 | 
			
		||||
        assertTrue {
 | 
			
		||||
            result.contentEquals(expectedResult)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testDigestToString() {
 | 
			
		||||
        val updates = 14
 | 
			
		||||
        val input = "1234567890"
 | 
			
		||||
        val expectedResult = "2F49AEB613E34E924E175A6AF2FAAD7BC78235F9C5E461C68FD5B47E".toLowerCase() +
 | 
			
		||||
                "E8E2FD2FB4C07D7E4A72404612D92899AF8A328F3B614ED77244B481151D40B11E32A4".toLowerCase()
 | 
			
		||||
 | 
			
		||||
        val blake2b = Blake2b()
 | 
			
		||||
        for (i in 0 until updates) {
 | 
			
		||||
            blake2b.updateBlocking(input)
 | 
			
		||||
        }
 | 
			
		||||
        val result = blake2b.digestString()
 | 
			
		||||
        assertTrue {
 | 
			
		||||
            result == expectedResult
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testDigestWithKey() {
 | 
			
		||||
        val test = "abc"
 | 
			
		||||
        val key = "key"
 | 
			
		||||
        val blake2b = Blake2b(key)
 | 
			
		||||
        blake2b.updateBlocking(test)
 | 
			
		||||
        val result = blake2b.digest()
 | 
			
		||||
        val printout = result.map { it.toString(16) }.chunked(16)
 | 
			
		||||
        printout.forEach { println(it.joinToString(separator = " ") { it.toUpperCase() }) }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        assertTrue {
 | 
			
		||||
            result.isNotEmpty()
 | 
			
		||||
        }
 | 
			
		||||
        val expectedResult = ("5c6a9a4ae911c02fb7e71a991eb9aea371ae993d4842d206e" +
 | 
			
		||||
                "6020d46f5e41358c6d5c277c110ef86c959ed63e6ecaaaceaaff38019a43264ae06acf73b9550b1")
 | 
			
		||||
            .chunked(2).map { it.toUByte(16) }.toTypedArray()
 | 
			
		||||
 | 
			
		||||
        assertTrue {
 | 
			
		||||
            result.contentEquals(expectedResult)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user