diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d5abb8..e72430e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,10 @@ ## Descriptive changelog (All dates are DD.MM.YYYY) - +#### Updatable SHA hash implementation - 0.0.2 - 21.7.2019 +- Added "updatable" version for SHA +- Moved sha and blake to hash package +- Updated tests #### Initial release - 0.0.1 - 20.7.2019 - Implemented Blake2b and SHA256/512 diff --git a/README.md b/README.md index f232204..0aeaf5b 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ This is an extremely early release, currently only consisting of Blake2b and SHA **The API will move fast and break often until v1.0** -Make SHA hashes "updateable" like Blake2b +Make SHA hashes "updatable" like Blake2b After that tenative plan is to add 25519 curve based signing and key exchange next. @@ -48,11 +48,17 @@ implementation("com.ionspin.kotlin:crypto:0.0.1-SNAPSHOT") ## Usage -### Blake2b +### Hashes + +Hashes are provided in two versions, "stateless", usually the companion object of the hash, +which takes the data to be hashed in one go, and "updatable" which can be fed data in chunks. + + +#### Blake2b You can use Blake 2b in two modes -#### Using a `Blake2b` object +##### Stateless version You need to deliver the complete data that is to be hashed in one go ```kotlin @@ -62,9 +68,9 @@ val result = Blake2b.digest(input) Result is returned as a `Array` -#### Using a `Blake2b` instance +##### Updatable instance version You can create an instance and feed the data by using `update(input : Array)` call. Once all data is supplied, -you should call `digest()` or `digestString()` convinence method that converts the `Array` into hexadecimal string. +you should call `digest()` or `digestString()` convenience method that converts the `Array` into hexadecimal string. If you want to use Blake2b with a key, you should supply it when creating the `Blake2b` instance. @@ -77,7 +83,9 @@ val result = blake2b.digest() ``` After digest is called, the instance is reset and can be reused (Keep in mind key stays the same for the particular instance). -### SHA2 (SHA256 and SHA512) +#### SHA2 (SHA256 and SHA512) + +##### Stateless version You need to deliver the complete data that is to be hashed in one go. You can either provide the `Array` as input or `String`. Result is always returned as `Array` (At least in verision 0.0.1) @@ -94,6 +102,22 @@ val result = Sha512.digest(message = input.encodeToByteArray().map { it.toUByte( Result is returned as a `Array` +##### Updateable version + +Or you can use the updatable instance version + +```kotlin +val sha256 = Sha256() +sha256.update("abc") +val result = sha256.digest() +``` + +```kotlin +val sha512 = Sha512() +sha512.update("abc") +val result = sha512.digest() +``` + diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/Hash.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/hash/Hash.kt similarity index 53% rename from multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/Hash.kt rename to multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/hash/Hash.kt index 0a89f27..23f9cfd 100644 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/Hash.kt +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/hash/Hash.kt @@ -14,14 +14,37 @@ * limitations under the License. */ -package com.ionspin.kotlin.crypto +package com.ionspin.kotlin.crypto.hash + /** * Created by Ugljesa Jovanovic * ugljesa.jovanovic@ionspin.com * on 20-Jul-2019 */ -interface Hash +interface Hash { + val MAX_HASH_BYTES : Int +} -interface UpdateableHash : Hash +@ExperimentalUnsignedTypes +interface UpdatableHash : Hash { + fun update(data : Array) + + fun update(data : String) + + fun digest() : Array + + fun digestString() : String +} + +@ExperimentalUnsignedTypes +interface StatelessHash : Hash { + fun digest(inputString: String, key: String? = null, hashLength: Int = MAX_HASH_BYTES): Array + + fun digest( + inputMessage: Array = emptyArray(), + key: Array = emptyArray(), + hashLength: Int = MAX_HASH_BYTES + ): Array +} diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/blake2b/Blake2b.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/hash/blake2b/Blake2b.kt similarity index 87% rename from multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/blake2b/Blake2b.kt rename to multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/hash/blake2b/Blake2b.kt index 937ea57..b462faa 100644 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/blake2b/Blake2b.kt +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/hash/blake2b/Blake2b.kt @@ -14,14 +14,13 @@ * limitations under the License. */ -package com.ionspin.kotlin.crypto.blake2b +package com.ionspin.kotlin.crypto.hash.blake2b import com.ionspin.kotlin.bignum.integer.BigInteger import com.ionspin.kotlin.bignum.integer.toBigInteger import com.ionspin.kotlin.crypto.* -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job +import com.ionspin.kotlin.crypto.hash.StatelessHash +import com.ionspin.kotlin.crypto.hash.UpdatableHash /** * Created by Ugljesa Jovanovic @@ -30,13 +29,14 @@ import kotlinx.coroutines.Job */ @ExperimentalUnsignedTypes -class Blake2b(val key: Array? = null, val hashLength: Int = 64) : UpdateableHash { - companion object : Hash { +class Blake2b(val key: Array? = null, val hashLength: Int = 64) : UpdatableHash { + + companion object : StatelessHash { const val BITS_IN_WORD = 64 const val ROUNDS_IN_COMPRESS = 12 const val BLOCK_BYTES = 128 - const val MAX_HASH_BYTES = 64 + override val MAX_HASH_BYTES = 64 const val MIN_HASH_BYTES = 1 const val MAX_KEY_BYTES = 64 const val MIN_KEY_BYTES = 0 @@ -143,38 +143,39 @@ class Blake2b(val key: Array? = null, val hashLength: Int = 64) : Updatea } @ExperimentalStdlibApi - fun digest(inputString: String, key: String? = null): Array { - val chunked = inputString.encodeToByteArray().map { it.toUByte() }.toList().chunked(BLOCK_BYTES) - .map { it.toTypedArray() }.toTypedArray() + override fun digest(inputString: String, key: String?, hashLength: Int): Array { + val array = inputString.encodeToByteArray().map { it.toUByte() }.toList().toTypedArray() val keyBytes = key?.run { encodeToByteArray().map { it.toUByte() }.toTypedArray() } ?: emptyArray() - return digest(inputMessage = chunked, secretKey = keyBytes) + return digest(inputMessage = array, key = keyBytes) } - fun digest( - inputMessage: Array> = emptyArray(), - secretKey: Array = emptyArray(), - hashLength: Int = MAX_HASH_BYTES + override fun digest( + inputMessage: Array, + key: Array, + hashLength: Int ): Array { + val chunkedMessage = inputMessage.chunked(BLOCK_BYTES) + val h = iv.copyOf() - h[0] = h[0] xor 0x01010000UL xor (secretKey.size.toULong() shl 8) xor hashLength.toULong() + h[0] = h[0] xor 0x01010000UL xor (key.size.toULong() shl 8) xor hashLength.toULong() - val message = if (secretKey.isEmpty()) { - if (inputMessage.isEmpty()) { + val message = if (key.isEmpty()) { + if (chunkedMessage.isEmpty()) { Array(1) { Array(128) { 0U } } } else { - inputMessage + chunkedMessage } } else { - arrayOf(padToBlock(secretKey), *inputMessage) + arrayOf(padToBlock(key), *chunkedMessage) } if (message.size > 1) { @@ -246,6 +247,8 @@ class Blake2b(val key: Array? = null, val hashLength: Int = 64) : Updatea requestedHashLenght ) + override val MAX_HASH_BYTES: Int = Blake2b.MAX_HASH_BYTES + var h = iv.copyOf() var counter = BigInteger.ZERO var bufferCounter = 0 @@ -260,15 +263,15 @@ class Blake2b(val key: Array? = null, val hashLength: Int = 64) : Updatea } } - fun update(array: Array) { - if (array.isEmpty()) { + override fun update(data: Array) { + if (data.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) + bufferCounter + data.size < BLOCK_BYTES -> appendToBuffer(data, bufferCounter) + bufferCounter + data.size >= BLOCK_BYTES -> { + val chunked = data.chunked(BLOCK_BYTES) chunked.forEach { chunk -> if (bufferCounter + chunk.size < BLOCK_BYTES) { appendToBuffer(chunk, bufferCounter) @@ -301,8 +304,8 @@ class Blake2b(val key: Array? = null, val hashLength: Int = 64) : Updatea } @ExperimentalStdlibApi - fun update(input: String) { - update(input.encodeToByteArray().map { it.toUByte() }.toTypedArray()) + override fun update(data: String) { + update(data.encodeToByteArray().map { it.toUByte() }.toTypedArray()) } private fun appendToBuffer(array: Array, start: Int) { @@ -314,7 +317,7 @@ class Blake2b(val key: Array? = null, val hashLength: Int = 64) : Updatea h = compress(h, block, counter, false) } - fun digest(): Array { + override fun digest(): Array { val lastBlockPadded = padToBlock(buffer) counter += bufferCounter compress(h, lastBlockPadded, counter, true) @@ -325,7 +328,7 @@ class Blake2b(val key: Array? = null, val hashLength: Int = 64) : Updatea } - fun digestString(): String { + override fun digestString(): String { return digest().map { it.toString(16) }.joinToString(separator = "") } diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/hash/sha/Sha256.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/hash/sha/Sha256.kt new file mode 100644 index 0000000..34995a7 --- /dev/null +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/hash/sha/Sha256.kt @@ -0,0 +1,328 @@ +/* + * 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.hash.sha + +import com.ionspin.kotlin.crypto.chunked +import com.ionspin.kotlin.crypto.hash.StatelessHash +import com.ionspin.kotlin.crypto.hash.UpdatableHash +import com.ionspin.kotlin.crypto.rotateRight + + +/** + * Created by Ugljesa Jovanovic + * ugljesa.jovanovic@ionspin.com + * on 17-Jul-2019 + */ + + +@ExperimentalUnsignedTypes +class Sha256 : UpdatableHash { + + override val MAX_HASH_BYTES: Int = 32 + + + companion object : StatelessHash { + const val BLOCK_SIZE = 512 + const val BLOCK_SIZE_IN_BYTES = 64 + const val UINT_MASK = 0xFFFFFFFFU + const val BYTE_MASK_FROM_ULONG = 0xFFUL + const val BYTE_MASK_FROM_UINT = 0xFFU + + override val MAX_HASH_BYTES: Int = 32 + + val iv = arrayOf( + 0x6a09e667U, + 0xbb67ae85U, + 0x3c6ef372U, + 0xa54ff53aU, + 0x510e527fU, + 0x9b05688cU, + 0x1f83d9abU, + 0x5be0cd19U + ) + + val k = arrayOf( + 0x428a2f98U, 0x71374491U, 0xb5c0fbcfU, 0xe9b5dba5U, 0x3956c25bU, 0x59f111f1U, 0x923f82a4U, 0xab1c5ed5U, + 0xd807aa98U, 0x12835b01U, 0x243185beU, 0x550c7dc3U, 0x72be5d74U, 0x80deb1feU, 0x9bdc06a7U, 0xc19bf174U, + 0xe49b69c1U, 0xefbe4786U, 0x0fc19dc6U, 0x240ca1ccU, 0x2de92c6fU, 0x4a7484aaU, 0x5cb0a9dcU, 0x76f988daU, + 0x983e5152U, 0xa831c66dU, 0xb00327c8U, 0xbf597fc7U, 0xc6e00bf3U, 0xd5a79147U, 0x06ca6351U, 0x14292967U, + 0x27b70a85U, 0x2e1b2138U, 0x4d2c6dfcU, 0x53380d13U, 0x650a7354U, 0x766a0abbU, 0x81c2c92eU, 0x92722c85U, + 0xa2bfe8a1U, 0xa81a664bU, 0xc24b8b70U, 0xc76c51a3U, 0xd192e819U, 0xd6990624U, 0xf40e3585U, 0x106aa070U, + 0x19a4c116U, 0x1e376c08U, 0x2748774cU, 0x34b0bcb5U, 0x391c0cb3U, 0x4ed8aa4aU, 0x5b9cca4fU, 0x682e6ff3U, + 0x748f82eeU, 0x78a5636fU, 0x84c87814U, 0x8cc70208U, 0x90befffaU, 0xa4506cebU, 0xbef9a3f7U, 0xc67178f2U + ) + + @ExperimentalStdlibApi + override fun digest(inputString: String, key: String?, hashLength: Int): Array { + return digest( + inputString.encodeToByteArray().map { it.toUByte() }.toTypedArray(), + key?.run { encodeToByteArray().map { it.toUByte() }.toTypedArray() } ?: emptyArray(), + hashLength) + } + + override fun digest(inputMessage: Array, key: Array, hashLength: Int): Array { + + var h = iv.copyOf() + + val expansionArray = createExpansionArray(inputMessage.size) + + val chunks = ( + inputMessage + + expansionArray + + (inputMessage.size * 8).toULong().toPaddedByteArray() + ) + .chunked(BLOCK_SIZE_IN_BYTES) + + chunks.forEach { chunk -> + val w = expandChunk(chunk) + mix(h, w).copyInto(h) + + } + + val digest = h[0].toPaddedByteArray() + + h[1].toPaddedByteArray() + + h[2].toPaddedByteArray() + + h[3].toPaddedByteArray() + + h[4].toPaddedByteArray() + + h[5].toPaddedByteArray() + + h[6].toPaddedByteArray() + + h[7].toPaddedByteArray() + return digest + } + + private fun scheduleSigma0(value: UInt): UInt { + return value.rotateRight(7) xor value.rotateRight(18) xor (value shr 3) + } + + private fun scheduleSigma1(value: UInt): UInt { + return value.rotateRight(17) xor value.rotateRight(19) xor (value shr 10) + } + + private fun compressionSigma0(a: UInt): UInt { + return (a rotateRight 2) xor (a rotateRight 13) xor (a rotateRight 22) + } + + private fun compressionSigma1(e: UInt): UInt { + return (e rotateRight 6) xor (e rotateRight 11) xor (e rotateRight 25) + } + + private fun ch(x: UInt, y: UInt, z: UInt): UInt { + return ((x and y) xor ((x xor UINT_MASK) and z)) + } + + private fun maj(x: UInt, y: UInt, z: UInt): UInt { + return (((x and y) xor (x and z) xor (y and z))) + } + + private fun expandChunk(chunk: Array): Array { + val w = Array(BLOCK_SIZE_IN_BYTES) { + when (it) { + in 0 until 16 -> { + var collected = (chunk[(it * 4)].toUInt() shl 24) + + (chunk[(it * 4) + 1].toUInt() shl 16) + + (chunk[(it * 4) + 2].toUInt() shl 8) + + (chunk[(it * 4) + 3].toUInt()) + collected + } + else -> 0U + } + } + for (i in 16 until BLOCK_SIZE_IN_BYTES) { + val s0 = scheduleSigma0(w[i - 15]) + val s1 = scheduleSigma1(w[i - 2]) + w[i] = w[i - 16] + s0 + w[i - 7] + s1 + } + return w + } + + private fun mix(h: Array, w: Array): Array { + var paramA = h[0] + var paramB = h[1] + var paramC = h[2] + var paramD = h[3] + var paramE = h[4] + var paramF = h[5] + var paramG = h[6] + var paramH = h[7] + + for (i in 0 until BLOCK_SIZE_IN_BYTES) { + val s1 = compressionSigma1(paramE) + val ch = ch(paramE, paramF, paramG) + val temp1 = paramH + s1 + ch + k[i] + w[i] + val s0 = compressionSigma0(paramA) + val maj = maj(paramA, paramB, paramC) + val temp2 = s0 + maj + paramH = paramG + paramG = paramF + paramF = paramE + paramE = paramD + temp1 + paramD = paramC + paramC = paramB + paramB = paramA + paramA = temp1 + temp2 + } + + h[0] += paramA + h[1] += paramB + h[2] += paramC + h[3] += paramD + h[4] += paramE + h[5] += paramF + h[6] += paramG + h[7] += paramH + return h + } + + + fun createExpansionArray(originalSizeInBytes: Int): Array { + val originalMessageSizeInBits = originalSizeInBytes * 8 + + + //K such that L + 1 + K + 64 is a multiple of 512 + val expandedRemainderOf512 = (originalMessageSizeInBits + BLOCK_SIZE_IN_BYTES + 1) % BLOCK_SIZE + val zeroAddAmount = when (expandedRemainderOf512) { + 0 -> 0 + else -> (BLOCK_SIZE - expandedRemainderOf512) / 8 + } + val expansionArray = Array(zeroAddAmount + 1) { + when (it) { + 0 -> 0b10000000U + else -> 0U + } + } + return expansionArray + } + + private fun ULong.toPaddedByteArray(): Array { + val byteMask = BYTE_MASK_FROM_ULONG + return Array(8) { + when (it) { + 7 -> (this and byteMask).toUByte() + 6 -> ((this shr 8) and byteMask).toUByte() + 5 -> ((this shr 16) and byteMask).toUByte() + 4 -> ((this shr 24) and byteMask).toUByte() + 3 -> ((this shr 32) and byteMask).toUByte() + 2 -> ((this shr 40) and byteMask).toUByte() + 1 -> ((this shr 48) and byteMask).toUByte() + 0 -> ((this shr 54) and byteMask).toUByte() + else -> throw RuntimeException("Invalid conversion") + } + } + } + + private fun UInt.toPaddedByteArray(): Array { + val byteMask = BYTE_MASK_FROM_UINT + return Array(4) { + when (it) { + 3 -> (this and byteMask).toUByte() + 2 -> ((this shr 8) and byteMask).toUByte() + 1 -> ((this shr 16) and byteMask).toUByte() + 0 -> ((this shr 24) and byteMask).toUByte() + else -> throw RuntimeException("Invalid conversion") + } + } + } + + } + + var h = iv.copyOf() + var counter = 0 + var bufferCounter = 0 + var buffer = Array(BLOCK_SIZE_IN_BYTES) { 0U } + + @ExperimentalStdlibApi + override fun update(data: String) { + return update(data.encodeToByteArray().map { it.toUByte() }.toTypedArray()) + } + + override fun update(data: Array) { + if (data.isEmpty()) { + throw RuntimeException("Updating with empty array is not allowed. If you need empty hash, just call digest without updating") + } + + when { + bufferCounter + data.size < BLOCK_SIZE_IN_BYTES -> appendToBuffer(data, bufferCounter) + bufferCounter + data.size >= BLOCK_SIZE_IN_BYTES -> { + val chunked = data.chunked(BLOCK_SIZE_IN_BYTES) + chunked.forEach { chunk -> + if (bufferCounter + chunk.size < BLOCK_SIZE_IN_BYTES) { + appendToBuffer(chunk, bufferCounter) + } else { + chunk.copyInto( + destination = buffer, + destinationOffset = bufferCounter, + startIndex = 0, + endIndex = BLOCK_SIZE_IN_BYTES - bufferCounter + ) + counter += BLOCK_SIZE_IN_BYTES + consumeBlock(buffer) + buffer = Array(BLOCK_SIZE_IN_BYTES) { + when (it) { + in (0 until (chunk.size - (BLOCK_SIZE_IN_BYTES - bufferCounter))) -> { + chunk[it + (BLOCK_SIZE_IN_BYTES - bufferCounter)] + } + else -> { + 0U + } + } + + } + bufferCounter = chunk.size - (BLOCK_SIZE_IN_BYTES - bufferCounter) + } + } + + } + } + } + + private fun consumeBlock(block: Array) { + val w = expandChunk(block) + mix(h, w).copyInto(h) + } + + override fun digest(): Array { + val length = counter + bufferCounter + val expansionArray = createExpansionArray(length) + val finalBlock = + buffer.copyOfRange(0, bufferCounter) + expansionArray + (length * 8).toULong().toPaddedByteArray() + finalBlock.chunked(BLOCK_SIZE_IN_BYTES).forEach { + consumeBlock(it) + } + + + val digest = h[0].toPaddedByteArray() + + h[1].toPaddedByteArray() + + h[2].toPaddedByteArray() + + h[3].toPaddedByteArray() + + h[4].toPaddedByteArray() + + h[5].toPaddedByteArray() + + h[6].toPaddedByteArray() + + h[7].toPaddedByteArray() + return digest + } + + override fun digestString(): String { + return digest().map { it.toString(16) }.joinToString(separator = "") + } + + private fun appendToBuffer(array: Array, start: Int) { + array.copyInto(destination = buffer, destinationOffset = start, startIndex = 0, endIndex = array.size) + bufferCounter += array.size + } + + +} \ No newline at end of file diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/hash/sha/Sha512.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/hash/sha/Sha512.kt new file mode 100644 index 0000000..217175e --- /dev/null +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/hash/sha/Sha512.kt @@ -0,0 +1,403 @@ +/* + * 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.hash.sha + +import com.ionspin.kotlin.crypto.chunked +import com.ionspin.kotlin.crypto.hash.StatelessHash +import com.ionspin.kotlin.crypto.hash.UpdatableHash +import com.ionspin.kotlin.crypto.rotateRight + +/** + * Created by Ugljesa Jovanovic + * ugljesa.jovanovic@ionspin.com + * on 18-Jul-2019 + */ + +@ExperimentalUnsignedTypes +class Sha512 : UpdatableHash { + + override val MAX_HASH_BYTES: Int = 32 + + companion object : StatelessHash { + const val BLOCK_SIZE = 1024 + const val BLOCK_SIZE_IN_BYTES = 128 + const val CHUNK_SIZE = 80 + const val ULONG_MASK = 0xFFFFFFFFFFFFFFFFUL + + override val MAX_HASH_BYTES: Int = 32 + + val k = arrayOf( + 0x428a2f98d728ae22UL, + 0x7137449123ef65cdUL, + 0xb5c0fbcfec4d3b2fUL, + 0xe9b5dba58189dbbcUL, + 0x3956c25bf348b538UL, + 0x59f111f1b605d019UL, + 0x923f82a4af194f9bUL, + 0xab1c5ed5da6d8118UL, + 0xd807aa98a3030242UL, + 0x12835b0145706fbeUL, + 0x243185be4ee4b28cUL, + 0x550c7dc3d5ffb4e2UL, + 0x72be5d74f27b896fUL, + 0x80deb1fe3b1696b1UL, + 0x9bdc06a725c71235UL, + 0xc19bf174cf692694UL, + 0xe49b69c19ef14ad2UL, + 0xefbe4786384f25e3UL, + 0x0fc19dc68b8cd5b5UL, + 0x240ca1cc77ac9c65UL, + 0x2de92c6f592b0275UL, + 0x4a7484aa6ea6e483UL, + 0x5cb0a9dcbd41fbd4UL, + 0x76f988da831153b5UL, + 0x983e5152ee66dfabUL, + 0xa831c66d2db43210UL, + 0xb00327c898fb213fUL, + 0xbf597fc7beef0ee4UL, + 0xc6e00bf33da88fc2UL, + 0xd5a79147930aa725UL, + 0x06ca6351e003826fUL, + 0x142929670a0e6e70UL, + 0x27b70a8546d22ffcUL, + 0x2e1b21385c26c926UL, + 0x4d2c6dfc5ac42aedUL, + 0x53380d139d95b3dfUL, + 0x650a73548baf63deUL, + 0x766a0abb3c77b2a8UL, + 0x81c2c92e47edaee6UL, + 0x92722c851482353bUL, + 0xa2bfe8a14cf10364UL, + 0xa81a664bbc423001UL, + 0xc24b8b70d0f89791UL, + 0xc76c51a30654be30UL, + 0xd192e819d6ef5218UL, + 0xd69906245565a910UL, + 0xf40e35855771202aUL, + 0x106aa07032bbd1b8UL, + 0x19a4c116b8d2d0c8UL, + 0x1e376c085141ab53UL, + 0x2748774cdf8eeb99UL, + 0x34b0bcb5e19b48a8UL, + 0x391c0cb3c5c95a63UL, + 0x4ed8aa4ae3418acbUL, + 0x5b9cca4f7763e373UL, + 0x682e6ff3d6b2b8a3UL, + 0x748f82ee5defb2fcUL, + 0x78a5636f43172f60UL, + 0x84c87814a1f0ab72UL, + 0x8cc702081a6439ecUL, + 0x90befffa23631e28UL, + 0xa4506cebde82bde9UL, + 0xbef9a3f7b2c67915UL, + 0xc67178f2e372532bUL, + 0xca273eceea26619cUL, + 0xd186b8c721c0c207UL, + 0xeada7dd6cde0eb1eUL, + 0xf57d4f7fee6ed178UL, + 0x06f067aa72176fbaUL, + 0x0a637dc5a2c898a6UL, + 0x113f9804bef90daeUL, + 0x1b710b35131c471bUL, + 0x28db77f523047d84UL, + 0x32caab7b40c72493UL, + 0x3c9ebe0a15c9bebcUL, + 0x431d67c49c100d4cUL, + 0x4cc5d4becb3e42b6UL, + 0x597f299cfc657e2aUL, + 0x5fcb6fab3ad6faecUL, + 0x6c44198c4a475817UL + ) + + val iv = arrayOf( + 0x6a09e667f3bcc908UL, + 0xbb67ae8584caa73bUL, + 0x3c6ef372fe94f82bUL, + 0xa54ff53a5f1d36f1UL, + 0x510e527fade682d1UL, + 0x9b05688c2b3e6c1fUL, + 0x1f83d9abfb41bd6bUL, + 0x5be0cd19137e2179UL + ) + + @ExperimentalStdlibApi + override fun digest(inputString: String, key: String?, hashLength: Int): Array { + return digest( + inputString.encodeToByteArray().map { it.toUByte() }.toTypedArray(), + key?.run { encodeToByteArray().map { it.toUByte() }.toTypedArray() } ?: emptyArray(), + hashLength = hashLength + ) + } + + override fun digest(inputMessage: Array, key: Array, hashLength: Int): Array { + + var h = iv.copyOf() + + val expansionArray = createExpansionArray(inputMessage.size) + + val chunks = + (inputMessage + expansionArray + (inputMessage.size * 8).toULong().toPadded128BitByteArray()).chunked( + BLOCK_SIZE_IN_BYTES + ) + + chunks.forEach { chunk -> + val w = expandChunk(chunk) + mix(h, w) + + } + + val digest = + h[0].toPaddedByteArray() + + h[1].toPaddedByteArray() + + h[2].toPaddedByteArray() + + h[3].toPaddedByteArray() + + h[4].toPaddedByteArray() + + h[5].toPaddedByteArray() + + h[6].toPaddedByteArray() + + h[7].toPaddedByteArray() + return digest + } + + private fun scheduleSigma0(value: ULong): ULong { + return value.rotateRight(1) xor value.rotateRight(8) xor (value shr 7) + } + + private fun scheduleSigma1(value: ULong): ULong { + return value.rotateRight(19) xor value.rotateRight(61) xor (value shr 6) + } + + private fun compressionSigma0(e: ULong): ULong { + return (e rotateRight 28) xor (e rotateRight 34) xor (e rotateRight 39) + } + + private fun compressionSigma1(a: ULong): ULong { + return (a rotateRight 14) xor (a rotateRight 18) xor (a rotateRight 41) + } + + private fun ch(x: ULong, y: ULong, z: ULong): ULong { + return ((x and y) xor ((x xor ULONG_MASK) and z)) + } + + private fun maj(x: ULong, y: ULong, z: ULong): ULong { + return ((x and y) xor (x and z) xor (y and z)) + } + + private fun expandChunk(chunk: Array): Array { + val w = Array(CHUNK_SIZE) { + when (it) { + in 0 until 16 -> { + var collected = (chunk[(it * 8)].toULong() shl 56) + + (chunk[(it * 8) + 1].toULong() shl 48) + + (chunk[(it * 8) + 2].toULong() shl 40) + + (chunk[(it * 8) + 3].toULong() shl 32) + + (chunk[(it * 8) + 4].toULong() shl 24) + + (chunk[(it * 8) + 5].toULong() shl 16) + + (chunk[(it * 8) + 6].toULong() shl 8) + + (chunk[(it * 8) + 7].toULong()) + collected + } + else -> 0UL + } + } + for (i in 16 until CHUNK_SIZE) { + val s0 = scheduleSigma0(w[i - 15]) + val s1 = scheduleSigma1(w[i - 2]) + w[i] = w[i - 16] + s0 + w[i - 7] + s1 + } + return w + } + + private fun mix(h: Array, w: Array): Array { + var paramA = h[0] + var paramB = h[1] + var paramC = h[2] + var paramD = h[3] + var paramE = h[4] + var paramF = h[5] + var paramG = h[6] + var paramH = h[7] + + for (i in 0 until CHUNK_SIZE) { + val s1 = compressionSigma1(paramE) + val ch = ch(paramE, paramF, paramG) + val temp1 = paramH + s1 + ch + k[i] + w[i] + val s0 = compressionSigma0(paramA) + val maj = maj(paramA, paramB, paramC) + val temp2 = s0 + maj + paramH = paramG + paramG = paramF + paramF = paramE + paramE = paramD + temp1 + paramD = paramC + paramC = paramB + paramB = paramA + paramA = temp1 + temp2 + } + + h[0] += paramA + h[1] += paramB + h[2] += paramC + h[3] += paramD + h[4] += paramE + h[5] += paramF + h[6] += paramG + h[7] += paramH + return h + } + + fun createExpansionArray(originalSizeInBytes: Int): Array { + val originalMessageSizeInBits = originalSizeInBytes * 8 + + val expandedRemainderOf1024 = (originalMessageSizeInBits + 129) % BLOCK_SIZE + val zeroAddAmount = when (expandedRemainderOf1024) { + 0 -> 0 + else -> (BLOCK_SIZE - expandedRemainderOf1024) / 8 + } + val expansionArray = Array(zeroAddAmount + 1) { + when (it) { + 0 -> 0b10000000U + else -> 0U + } + } + return expansionArray + } + + + private fun ULong.toPaddedByteArray(): Array { + val byteMask = 0xFFUL + //Ignore messages longer than 64 bits for now + return Array(8) { + when (it) { + 7 -> (this and byteMask).toUByte() + 6 -> ((this shr 8) and byteMask).toUByte() + 5 -> ((this shr 16) and byteMask).toUByte() + 4 -> ((this shr 24) and byteMask).toUByte() + 3 -> ((this shr 32) and byteMask).toUByte() + 2 -> ((this shr 40) and byteMask).toUByte() + 1 -> ((this shr 48) and byteMask).toUByte() + 0 -> ((this shr 56) and byteMask).toUByte() + else -> 0U + } + } + } + + private fun ULong.toPadded128BitByteArray(): Array { + val byteMask = 0xFFUL + //Ignore messages longer than 64 bits for now + return Array(16) { + when (it) { + 15 -> (this and byteMask).toUByte() + 14 -> ((this shr 8) and byteMask).toUByte() + 13 -> ((this shr 16) and byteMask).toUByte() + 12 -> ((this shr 24) and byteMask).toUByte() + 11 -> ((this shr 32) and byteMask).toUByte() + 10 -> ((this shr 40) and byteMask).toUByte() + 9 -> ((this shr 48) and byteMask).toUByte() + 8 -> ((this shr 54) and byteMask).toUByte() + else -> 0U + } + } + } + } + + var h = iv.copyOf() + var counter = 0 + var bufferCounter = 0 + var buffer = Array(BLOCK_SIZE_IN_BYTES) { 0U } + + @ExperimentalStdlibApi + override fun update(data: String) { + return update(data.encodeToByteArray().map { it.toUByte() }.toTypedArray()) + } + + override fun update(data: Array) { + if (data.isEmpty()) { + throw RuntimeException("Updating with empty array is not allowed. If you need empty hash, just call digest without updating") + } + + when { + bufferCounter + data.size < BLOCK_SIZE_IN_BYTES -> appendToBuffer(data, bufferCounter) + bufferCounter + data.size >= BLOCK_SIZE_IN_BYTES -> { + val chunked = data.chunked(BLOCK_SIZE_IN_BYTES) + chunked.forEach { chunk -> + if (bufferCounter + chunk.size < BLOCK_SIZE_IN_BYTES) { + appendToBuffer(chunk, bufferCounter) + } else { + chunk.copyInto( + destination = buffer, + destinationOffset = bufferCounter, + startIndex = 0, + endIndex = BLOCK_SIZE_IN_BYTES - bufferCounter + ) + counter += BLOCK_SIZE_IN_BYTES + consumeBlock(buffer) + buffer = Array(BLOCK_SIZE_IN_BYTES) { + when (it) { + in (0 until (chunk.size - (BLOCK_SIZE_IN_BYTES - bufferCounter))) -> { + chunk[it + (BLOCK_SIZE_IN_BYTES - bufferCounter)] + } + else -> { + 0U + } + } + + } + bufferCounter = chunk.size - (BLOCK_SIZE_IN_BYTES - bufferCounter) + } + } + + } + } + } + + private fun consumeBlock(block: Array) { + val w = expandChunk(block) + mix(h, w).copyInto(h) + } + + override fun digest(): Array { + val length = counter + bufferCounter + val expansionArray = createExpansionArray(length) + val finalBlock = + buffer.copyOfRange(0, bufferCounter) + expansionArray + (length * 8).toULong().toPadded128BitByteArray() + finalBlock.chunked(BLOCK_SIZE_IN_BYTES).forEach { + consumeBlock(it) + } + + + val digest = h[0].toPaddedByteArray() + + h[1].toPaddedByteArray() + + h[2].toPaddedByteArray() + + h[3].toPaddedByteArray() + + h[4].toPaddedByteArray() + + h[5].toPaddedByteArray() + + h[6].toPaddedByteArray() + + h[7].toPaddedByteArray() + return digest + } + + override fun digestString(): String { + return digest().map { it.toString(16) }.joinToString(separator = "") + } + + private fun appendToBuffer(array: Array, start: Int) { + array.copyInto(destination = buffer, destinationOffset = start, startIndex = 0, endIndex = array.size) + bufferCounter += array.size + } + + +} \ No newline at end of file diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/sha/Sha256.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/sha/Sha256.kt deleted file mode 100644 index 2f506db..0000000 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/sha/Sha256.kt +++ /dev/null @@ -1,229 +0,0 @@ -/* - * 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.sha - -import com.ionspin.kotlin.crypto.Hash -import com.ionspin.kotlin.crypto.chunked -import com.ionspin.kotlin.crypto.rotateRight - - -/** - * Created by Ugljesa Jovanovic - * ugljesa.jovanovic@ionspin.com - * on 17-Jul-2019 - */ - - -@ExperimentalUnsignedTypes -class Sha256() : Hash { - - companion object { - const val BLOCK_SIZE = 512 - const val W_SIZE = 64 - const val UINT_MASK = 0xFFFFFFFFU - const val BYTE_MASK_FROM_ULONG = 0xFFUL - const val BYTE_MASK_FROM_UINT = 0xFFU - - val iv = arrayOf( - 0x6a09e667U, - 0xbb67ae85U, - 0x3c6ef372U, - 0xa54ff53aU, - 0x510e527fU, - 0x9b05688cU, - 0x1f83d9abU, - 0x5be0cd19U - ) - - val k = arrayOf( - 0x428a2f98U, 0x71374491U, 0xb5c0fbcfU, 0xe9b5dba5U, 0x3956c25bU, 0x59f111f1U, 0x923f82a4U, 0xab1c5ed5U, - 0xd807aa98U, 0x12835b01U, 0x243185beU, 0x550c7dc3U, 0x72be5d74U, 0x80deb1feU, 0x9bdc06a7U, 0xc19bf174U, - 0xe49b69c1U, 0xefbe4786U, 0x0fc19dc6U, 0x240ca1ccU, 0x2de92c6fU, 0x4a7484aaU, 0x5cb0a9dcU, 0x76f988daU, - 0x983e5152U, 0xa831c66dU, 0xb00327c8U, 0xbf597fc7U, 0xc6e00bf3U, 0xd5a79147U, 0x06ca6351U, 0x14292967U, - 0x27b70a85U, 0x2e1b2138U, 0x4d2c6dfcU, 0x53380d13U, 0x650a7354U, 0x766a0abbU, 0x81c2c92eU, 0x92722c85U, - 0xa2bfe8a1U, 0xa81a664bU, 0xc24b8b70U, 0xc76c51a3U, 0xd192e819U, 0xd6990624U, 0xf40e3585U, 0x106aa070U, - 0x19a4c116U, 0x1e376c08U, 0x2748774cU, 0x34b0bcb5U, 0x391c0cb3U, 0x4ed8aa4aU, 0x5b9cca4fU, 0x682e6ff3U, - 0x748f82eeU, 0x78a5636fU, 0x84c87814U, 0x8cc70208U, 0x90befffaU, 0xa4506cebU, 0xbef9a3f7U, 0xc67178f2U - ) - - @ExperimentalStdlibApi - fun digest(message : String) : Array { - return digest(message.encodeToByteArray().map { it.toUByte() }.toTypedArray()) - } - - fun digest(message: Array) : Array { - - var h0 = 0x6a09e667U - var h1 = 0xbb67ae85U - var h2 = 0x3c6ef372U - var h3 = 0xa54ff53aU - var h4 = 0x510e527fU - var h5 = 0x9b05688cU - var h6 = 0x1f83d9abU - var h7 = 0x5be0cd19U - - val originalMessageSizeInBits = message.size * 8 - - - //K such that L + 1 + K + 64 is a multiple of 512 - val expandedRemainderOf512 = (originalMessageSizeInBits + 65) % BLOCK_SIZE - val zeroAddAmount = when (expandedRemainderOf512) { - 0 -> 0 - else -> (BLOCK_SIZE - expandedRemainderOf512) / 8 - } - val expansionArray = Array(zeroAddAmount + 1) { - when (it) { - 0 -> 0b10000000U //TODO This wont work if there the byte needs to be shared with the L (length) ULong - else -> 0U - } - } - - val chunks = (message + expansionArray + originalMessageSizeInBits.toULong().toPaddedByteArray()).chunked(64) - - chunks.forEach { chunk -> - val w = Array(W_SIZE) { - when (it) { - in 0 until 16 -> { - var collected = (chunk[(it * 4)].toUInt() shl 24) + - (chunk[(it * 4) + 1].toUInt() shl 16 ) + - (chunk[(it * 4) + 2].toUInt() shl 8 ) + - (chunk[(it * 4) + 3].toUInt()) - collected - } - else -> 0U - } - } - for (i in 16 until W_SIZE) { - val s0 = scheduleSigma0(w[i - 15]) - val s1 = scheduleSigma1(w[i - 2]) - w[i] = w[i-16] + s0 + w[i - 7] + s1 - } - - var a = h0 - var b = h1 - var c = h2 - var d = h3 - var e = h4 - var f = h5 - var g = h6 - var h = h7 - - for (i in 0 until W_SIZE) { - val s1 = compressionSigma1(e) - val ch = ch(e, f, g) - val temp1 = h + s1 + ch + k[i] + w[i] - val s0 = compressionSigma0(a) - val maj = maj(a,b,c) - val temp2 = s0 + maj - h = g - g = f - f = e - e = d + temp1 - d = c - c = b - b = a - a = temp1 + temp2 - } - - h0 += a - h1 += b - h2 += c - h3 += d - h4 += e - h5 += f - h6 += g - h7 += h - - } - - val digest = h0.toPaddedByteArray() + - h1.toPaddedByteArray() + - h2.toPaddedByteArray() + - h3.toPaddedByteArray() + - h4.toPaddedByteArray() + - h5.toPaddedByteArray() + - h6.toPaddedByteArray() + - h7.toPaddedByteArray() - return digest - } - - private fun scheduleSigma0(value: UInt): UInt { - return value.rotateRight(7) xor value.rotateRight(18) xor (value shr 3) - } - - private fun scheduleSigma1(value : UInt) : UInt { - return value.rotateRight(17) xor value.rotateRight(19) xor (value shr 10) - } - - private fun compressionSigma0(a : UInt) : UInt { - return (a rotateRight 2) xor (a rotateRight 13) xor (a rotateRight 22) - } - - private fun compressionSigma1(e : UInt) : UInt { - return (e rotateRight 6) xor (e rotateRight 11) xor (e rotateRight 25) - } - - private fun ch(x : UInt, y : UInt, z : UInt) : UInt { - return ((x and y) xor ((x xor UINT_MASK) and z)) - } - - private fun maj(x : UInt, y : UInt, z : UInt) : UInt { - return (((x and y) xor (x and z) xor (y and z))) - } - - - - private fun ULong.toPaddedByteArray(): Array { - val byteMask = BYTE_MASK_FROM_ULONG - return Array(8) { - when (it) { - 7 -> (this and byteMask).toUByte() - 6 -> ((this shr 8) and byteMask).toUByte() - 5 -> ((this shr 16) and byteMask).toUByte() - 4 -> ((this shr 24) and byteMask).toUByte() - 3 -> ((this shr 32) and byteMask).toUByte() - 2 -> ((this shr 40) and byteMask).toUByte() - 1 -> ((this shr 48) and byteMask).toUByte() - 0 -> ((this shr 54) and byteMask).toUByte() - else -> throw RuntimeException("Invalid conversion") - } - } - } - - private fun UInt.toPaddedByteArray(): Array { - val byteMask = BYTE_MASK_FROM_UINT - return Array(4) { - when (it) { - 3 -> (this and byteMask).toUByte() - 2 -> ((this shr 8) and byteMask).toUByte() - 1 -> ((this shr 16) and byteMask).toUByte() - 0 -> ((this shr 24) and byteMask).toUByte() - else -> throw RuntimeException("Invalid conversion") - } - } - } - - } - - - - - - - - -} \ No newline at end of file diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/sha/Sha512.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/sha/Sha512.kt deleted file mode 100644 index ba6a9f5..0000000 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/sha/Sha512.kt +++ /dev/null @@ -1,298 +0,0 @@ -/* - * 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.sha - -import com.ionspin.kotlin.crypto.Hash -import com.ionspin.kotlin.crypto.chunked -import com.ionspin.kotlin.crypto.rotateRight - -/** - * Created by Ugljesa Jovanovic - * ugljesa.jovanovic@ionspin.com - * on 18-Jul-2019 - */ - -@ExperimentalUnsignedTypes -class Sha512 : Hash { - companion object { - const val BLOCK_SIZE = 1024 - const val ULONG_MASK = 0xFFFFFFFFFFFFFFFFUL - - val k = arrayOf( - 0x428a2f98d728ae22UL, - 0x7137449123ef65cdUL, - 0xb5c0fbcfec4d3b2fUL, - 0xe9b5dba58189dbbcUL, - 0x3956c25bf348b538UL, - 0x59f111f1b605d019UL, - 0x923f82a4af194f9bUL, - 0xab1c5ed5da6d8118UL, - 0xd807aa98a3030242UL, - 0x12835b0145706fbeUL, - 0x243185be4ee4b28cUL, - 0x550c7dc3d5ffb4e2UL, - 0x72be5d74f27b896fUL, - 0x80deb1fe3b1696b1UL, - 0x9bdc06a725c71235UL, - 0xc19bf174cf692694UL, - 0xe49b69c19ef14ad2UL, - 0xefbe4786384f25e3UL, - 0x0fc19dc68b8cd5b5UL, - 0x240ca1cc77ac9c65UL, - 0x2de92c6f592b0275UL, - 0x4a7484aa6ea6e483UL, - 0x5cb0a9dcbd41fbd4UL, - 0x76f988da831153b5UL, - 0x983e5152ee66dfabUL, - 0xa831c66d2db43210UL, - 0xb00327c898fb213fUL, - 0xbf597fc7beef0ee4UL, - 0xc6e00bf33da88fc2UL, - 0xd5a79147930aa725UL, - 0x06ca6351e003826fUL, - 0x142929670a0e6e70UL, - 0x27b70a8546d22ffcUL, - 0x2e1b21385c26c926UL, - 0x4d2c6dfc5ac42aedUL, - 0x53380d139d95b3dfUL, - 0x650a73548baf63deUL, - 0x766a0abb3c77b2a8UL, - 0x81c2c92e47edaee6UL, - 0x92722c851482353bUL, - 0xa2bfe8a14cf10364UL, - 0xa81a664bbc423001UL, - 0xc24b8b70d0f89791UL, - 0xc76c51a30654be30UL, - 0xd192e819d6ef5218UL, - 0xd69906245565a910UL, - 0xf40e35855771202aUL, - 0x106aa07032bbd1b8UL, - 0x19a4c116b8d2d0c8UL, - 0x1e376c085141ab53UL, - 0x2748774cdf8eeb99UL, - 0x34b0bcb5e19b48a8UL, - 0x391c0cb3c5c95a63UL, - 0x4ed8aa4ae3418acbUL, - 0x5b9cca4f7763e373UL, - 0x682e6ff3d6b2b8a3UL, - 0x748f82ee5defb2fcUL, - 0x78a5636f43172f60UL, - 0x84c87814a1f0ab72UL, - 0x8cc702081a6439ecUL, - 0x90befffa23631e28UL, - 0xa4506cebde82bde9UL, - 0xbef9a3f7b2c67915UL, - 0xc67178f2e372532bUL, - 0xca273eceea26619cUL, - 0xd186b8c721c0c207UL, - 0xeada7dd6cde0eb1eUL, - 0xf57d4f7fee6ed178UL, - 0x06f067aa72176fbaUL, - 0x0a637dc5a2c898a6UL, - 0x113f9804bef90daeUL, - 0x1b710b35131c471bUL, - 0x28db77f523047d84UL, - 0x32caab7b40c72493UL, - 0x3c9ebe0a15c9bebcUL, - 0x431d67c49c100d4cUL, - 0x4cc5d4becb3e42b6UL, - 0x597f299cfc657e2aUL, - 0x5fcb6fab3ad6faecUL, - 0x6c44198c4a475817UL - ) - - val iv = arrayOf( - 0x6a09e667f3bcc908UL, - 0xbb67ae8584caa73bUL, - 0x3c6ef372fe94f82bUL, - 0xa54ff53a5f1d36f1UL, - 0x510e527fade682d1UL, - 0x9b05688c2b3e6c1fUL, - 0x1f83d9abfb41bd6bUL, - 0x5be0cd19137e2179UL - ) - - @ExperimentalStdlibApi - fun digest(message: String): Array { - return digest(message.encodeToByteArray().map { it.toUByte() }.toTypedArray()) - } - - fun digest(message: Array): Array { - - var h0 = 0x6a09e667f3bcc908UL - var h1 = 0xbb67ae8584caa73bUL - var h2 = 0x3c6ef372fe94f82bUL - var h3 = 0xa54ff53a5f1d36f1UL - var h4 = 0x510e527fade682d1UL - var h5 = 0x9b05688c2b3e6c1fUL - var h6 = 0x1f83d9abfb41bd6bUL - var h7 = 0x5be0cd19137e2179UL - - val originalMessageSizeInBits = message.size * 8 - - - //K such that L + 1 + K + 64 is a multiple of 512 - val expandedRemainderOf1024 = (originalMessageSizeInBits + 129) % BLOCK_SIZE - val zeroAddAmount = when (expandedRemainderOf1024) { - 0 -> 0 - else -> (BLOCK_SIZE - expandedRemainderOf1024) / 8 - } - val expansionArray = Array(zeroAddAmount + 1) { - when (it) { - 0 -> 0b10000000U //TODO This wont work if there the byte needs to be shared with the L (length) ULong - else -> 0U - } - } - - val chunks = - (message + expansionArray + originalMessageSizeInBits.toULong().toPadded128BitByteArray()).chunked(128) - - chunks.forEach { chunk -> - val w = Array(80) { - when (it) { - in 0 until 16 -> { - var collected = (chunk[(it * 8)].toULong() shl 56) + - (chunk[(it * 8) + 1].toULong() shl 48) + - (chunk[(it * 8) + 2].toULong() shl 40) + - (chunk[(it * 8) + 3].toULong() shl 32) + - (chunk[(it * 8) + 4].toULong() shl 24) + - (chunk[(it * 8) + 5].toULong() shl 16) + - (chunk[(it * 8) + 6].toULong() shl 8) + - (chunk[(it * 8) + 7].toULong()) - collected - } - else -> 0UL - } - } - for (i in 16 until 80) { - val s0 = scheduleSigma0(w[i - 15]) - val s1 = scheduleSigma1(w[i - 2]) - w[i] = w[i - 16] + s0 + w[i - 7] + s1 - } - - var a = h0 - var b = h1 - var c = h2 - var d = h3 - var e = h4 - var f = h5 - var g = h6 - var h = h7 - - for (i in 0 until 80) { - val s1 = compressionSigma1(e) - val ch = ch(e, f, g) - val temp1 = h + s1 + ch + k[i] + w[i] - val s0 = compressionSigma0(a) - val maj = maj(a, b, c) - val temp2 = s0 + maj - h = g - g = f - f = e - e = d + temp1 - d = c - c = b - b = a - a = temp1 + temp2 - } - - h0 += a - h1 += b - h2 += c - h3 += d - h4 += e - h5 += f - h6 += g - h7 += h - - } - - val digest = h0.toPaddedByteArray() + - h1.toPaddedByteArray() + - h2.toPaddedByteArray() + - h3.toPaddedByteArray() + - h4.toPaddedByteArray() + - h5.toPaddedByteArray() + - h6.toPaddedByteArray() + - h7.toPaddedByteArray() - return digest - } - - private fun scheduleSigma0(value: ULong): ULong { - return value.rotateRight(1) xor value.rotateRight(8) xor (value shr 7) - } - - private fun scheduleSigma1(value: ULong): ULong { - return value.rotateRight(19) xor value.rotateRight(61) xor (value shr 6) - } - - private fun compressionSigma0(e: ULong): ULong { - return (e rotateRight 28) xor (e rotateRight 34) xor (e rotateRight 39) - } - - private fun compressionSigma1(a: ULong): ULong { - return (a rotateRight 14) xor (a rotateRight 18) xor (a rotateRight 41) - } - - private fun ch(x: ULong, y: ULong, z: ULong): ULong { - return ((x and y) xor ((x xor ULONG_MASK) and z)) - } - - private fun maj(x: ULong, y: ULong, z: ULong): ULong { - return ((x and y) xor (x and z) xor (y and z)) - } - - - private fun ULong.toPaddedByteArray(): Array { - val byteMask = 0xFFUL - //Ignore messages longer than 64 bits for now - return Array(8) { - when (it) { - 7 -> (this and byteMask).toUByte() - 6 -> ((this shr 8) and byteMask).toUByte() - 5 -> ((this shr 16) and byteMask).toUByte() - 4 -> ((this shr 24) and byteMask).toUByte() - 3 -> ((this shr 32) and byteMask).toUByte() - 2 -> ((this shr 40) and byteMask).toUByte() - 1 -> ((this shr 48) and byteMask).toUByte() - 0 -> ((this shr 56) and byteMask).toUByte() - else -> 0U - } - } - } - - private fun ULong.toPadded128BitByteArray(): Array { - val byteMask = 0xFFUL - //Ignore messages longer than 64 bits for now - return Array(16) { - when (it) { - 15 -> (this and byteMask).toUByte() - 14 -> ((this shr 8) and byteMask).toUByte() - 13 -> ((this shr 16) and byteMask).toUByte() - 12 -> ((this shr 24) and byteMask).toUByte() - 11 -> ((this shr 32) and byteMask).toUByte() - 10 -> ((this shr 40) and byteMask).toUByte() - 9 -> ((this shr 48) and byteMask).toUByte() - 8 -> ((this shr 54) and byteMask).toUByte() - else -> 0U - } - } - } - } - - -} \ No newline at end of file diff --git a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/ReadmeTest.kt b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/ReadmeTest.kt index 4f39cf1..ee449cb 100644 --- a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/ReadmeTest.kt +++ b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/ReadmeTest.kt @@ -16,9 +16,9 @@ package com.ionspin.kotlin.crypto -import com.ionspin.kotlin.crypto.blake2b.Blake2b -import com.ionspin.kotlin.crypto.sha.Sha256 -import com.ionspin.kotlin.crypto.sha.Sha512 +import com.ionspin.kotlin.crypto.hash.blake2b.Blake2b +import com.ionspin.kotlin.crypto.hash.sha.Sha256 +import com.ionspin.kotlin.crypto.hash.sha.Sha512 import kotlin.test.Test import kotlin.test.assertTrue @@ -74,7 +74,7 @@ class ReadmeTest { @Test fun sha256Example() { val input ="abc" - val result = Sha256.digest(message = input) + val result = Sha256.digest(inputString = input) val expectedResult = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" assertTrue { result.contentEquals(expectedResult.chunked(2).map { it.toUByte(16) }.toTypedArray()) @@ -87,7 +87,7 @@ class ReadmeTest { @Test fun sha512Example() { val input ="abc" - val result = Sha512.digest(message = input.encodeToByteArray().map { it.toUByte() }.toTypedArray()) + val result = Sha512.digest(inputMessage = input.encodeToByteArray().map { it.toUByte() }.toTypedArray()) println(result.map {it.toString(16)}) val expectedResult = "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a" + "2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f" @@ -96,5 +96,32 @@ class ReadmeTest { } + } + + @ExperimentalStdlibApi + @Test + fun sha256UpdatableExample() { + val sha256 = Sha256() + sha256.update("abc") + val result = sha256.digest() + val expectedResult = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" + assertTrue { + result.contentEquals(expectedResult.chunked(2).map { it.toUByte(16) }.toTypedArray()) + } + } + + @ExperimentalStdlibApi + @Test + fun sha512UpdatableExample() { + val sha512 = Sha512() + sha512.update("abc") + val result = sha512.digest() + val expectedResult = "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a" + + "2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f" + assertTrue { + result.contentEquals(expectedResult.chunked(2).map { it.toUByte(16) }.toTypedArray()) + } + + } } \ No newline at end of file diff --git a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/blake2b/Blake2BTest.kt b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/hash/blake2b/Blake2BTest.kt similarity index 99% rename from multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/blake2b/Blake2BTest.kt rename to multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/hash/blake2b/Blake2BTest.kt index 5916900..d4bdd51 100644 --- a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/blake2b/Blake2BTest.kt +++ b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/hash/blake2b/Blake2BTest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.ionspin.kotlin.crypto.blake2b +package com.ionspin.kotlin.crypto.hash.blake2b import kotlin.test.Test import kotlin.test.assertTrue diff --git a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/blake2b/Blake2bInstanceTest.kt b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/hash/blake2b/Blake2bInstanceTest.kt similarity index 93% rename from multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/blake2b/Blake2bInstanceTest.kt rename to multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/hash/blake2b/Blake2bInstanceTest.kt index 020232e..44eaaa0 100644 --- a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/blake2b/Blake2bInstanceTest.kt +++ b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/hash/blake2b/Blake2bInstanceTest.kt @@ -14,11 +14,8 @@ * limitations under the License. */ -package com.ionspin.kotlin.crypto.blake2b +package com.ionspin.kotlin.crypto.hash.blake2b -import com.ionspin.kotlin.crypto.util.testBlocking -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch import kotlin.test.Test import kotlin.test.assertTrue @@ -32,7 +29,7 @@ import kotlin.test.assertTrue class Blake2bInstanceTest { @Test - fun testUpdateableBlake2b() { + fun testUpdatableBlake2b() { val updates = 14 val input = "1234567890" val expectedResult = arrayOf( diff --git a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/blake2b/Blake2bKnowAnswerTests.kt b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/hash/blake2b/Blake2bKnowAnswerTests.kt similarity index 99% rename from multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/blake2b/Blake2bKnowAnswerTests.kt rename to multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/hash/blake2b/Blake2bKnowAnswerTests.kt index ddfb35c..c1c4bab 100644 --- a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/blake2b/Blake2bKnowAnswerTests.kt +++ b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/hash/blake2b/Blake2bKnowAnswerTests.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.ionspin.kotlin.crypto.blake2b +package com.ionspin.kotlin.crypto.hash.blake2b import kotlin.test.Test import kotlin.test.assertTrue @@ -37,10 +37,9 @@ class Blake2bKnowAnswerTests { fun knownAnswerTest() { kat.forEach { val parsedInput = it.input.chunked(2).map { it.toUByte(16) }.toTypedArray() - val chunkedInput = parsedInput.toList().chunked(128).map { it.toTypedArray() }.toTypedArray() val result = Blake2b.digest( - inputMessage = chunkedInput, - secretKey = it.key.chunked(2).map { it.toUByte(16) }.toTypedArray() + inputMessage = parsedInput, + key = it.key.chunked(2).map { it.toUByte(16) }.toTypedArray() ) assertTrue { result.contentEquals(it.hash.chunked(2).map { it.toUByte(16) }.toTypedArray()) diff --git a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/sha/Sha256Test.kt b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/hash/sha/Sha256Test.kt similarity index 50% rename from multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/sha/Sha256Test.kt rename to multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/hash/sha/Sha256Test.kt index 57507bd..7aec043 100644 --- a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/sha/Sha256Test.kt +++ b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/hash/sha/Sha256Test.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.ionspin.kotlin.crypto.sha +package com.ionspin.kotlin.crypto.hash.sha import kotlin.test.Test import kotlin.test.assertTrue @@ -31,7 +31,7 @@ class Sha256Test { @Test fun testWellKnownValue() { - val result = Sha256.digest(message = "abc") + val result = Sha256.digest(inputString = "abc") val expectedResult = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" assertTrue { result.contentEquals(expectedResult.chunked(2).map { it.toUByte(16) }.toTypedArray()) @@ -44,10 +44,37 @@ class Sha256Test { @Test fun testWellKnownDoubleBlock() { - val resultDoubleBlock = Sha256.digest(message = "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq") + val resultDoubleBlock = Sha256.digest(inputString = "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq") val expectedResultForDoubleBlock = "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1" assertTrue { resultDoubleBlock.contentEquals(expectedResultForDoubleBlock.chunked(2).map { it.toUByte(16) }.toTypedArray()) } } + + @ExperimentalStdlibApi + @Test + fun testWellKnown3() { //It's good that I'm consistent with names. + + + val resultDoubleBlock = Sha256.digest(inputString = "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu") + println(resultDoubleBlock.map{ it.toString(16)}.joinToString(separator = "")) + val expectedResultForDoubleBlock = "cf5b16a778af8380036ce59e7b0492370b249b11e8f07a51afac45037afee9d1" + assertTrue { + resultDoubleBlock.contentEquals(expectedResultForDoubleBlock.chunked(2).map { it.toUByte(16) }.toTypedArray()) + } + } + + @ExperimentalStdlibApi + @Test + fun testWellKnownLong() { + val inputBuilder = StringBuilder() + for (i in 0 until 1000000) { + inputBuilder.append("a") + } + val resultDoubleBlock = Sha256.digest(inputMessage = (inputBuilder.toString()).encodeToByteArray().map { it.toUByte() }.toTypedArray()) + val expectedResultForDoubleBlock = "cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0" + assertTrue { + resultDoubleBlock.contentEquals(expectedResultForDoubleBlock.chunked(2).map { it.toUByte(16) }.toTypedArray()) + } + } } \ No newline at end of file diff --git a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/hash/sha/Sha256UpdateableTest.kt b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/hash/sha/Sha256UpdateableTest.kt new file mode 100644 index 0000000..620d2fd --- /dev/null +++ b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/hash/sha/Sha256UpdateableTest.kt @@ -0,0 +1,100 @@ +/* + * 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.hash.sha + +import kotlin.test.Ignore +import kotlin.test.Test +import kotlin.test.assertTrue + +/** + * Created by Ugljesa Jovanovic + * ugljesa.jovanovic@ionspin.com + * on 17-Jul-2019 + */ +@ExperimentalUnsignedTypes +class Sha256UpdatableTest { + + @ExperimentalStdlibApi + @Test + fun testWellKnownValue() { + val sha256 = Sha256() + sha256.update("abc") + val result = sha256.digest() + val expectedResult = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" + assertTrue { + result.contentEquals(expectedResult.chunked(2).map { it.toUByte(16) }.toTypedArray()) + } + } + + @ExperimentalStdlibApi + @Test + fun testWellKnownDoubleBlock() { + val sha256 = Sha256() + sha256.update(data = "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq") + val resultDoubleBlock = sha256.digest() + println(resultDoubleBlock.map{ it.toString(16)}.joinToString(separator = "")) + val expectedResultForDoubleBlock = "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1" + assertTrue { + resultDoubleBlock.contentEquals(expectedResultForDoubleBlock.chunked(2).map { it.toUByte(16) }.toTypedArray()) + } + } + + @ExperimentalStdlibApi + @Test + fun testWellKnown3() { //It's good that I'm consistent with names. + val sha256 = Sha256() + sha256.update(data = "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu") + val resultDoubleBlock = sha256.digest() + println(resultDoubleBlock.map{ it.toString(16)}.joinToString(separator = "")) + val expectedResultForDoubleBlock = "cf5b16a778af8380036ce59e7b0492370b249b11e8f07a51afac45037afee9d1" + assertTrue { + resultDoubleBlock.contentEquals(expectedResultForDoubleBlock.chunked(2).map { it.toUByte(16) }.toTypedArray()) + } + } + + @ExperimentalStdlibApi + @Test + fun testWellKnownLong() { + val sha256 = Sha256() + for (i in 0 until 10000) { + sha256.update("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + } + val resultDoubleBlock = sha256.digest() + val expectedResultForDoubleBlock = "cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0" + assertTrue { + resultDoubleBlock.contentEquals(expectedResultForDoubleBlock.chunked(2).map { it.toUByte(16) }.toTypedArray()) + } + } + + @Ignore() + @ExperimentalStdlibApi + @Test + fun testWellKnownLonger() { + val sha256 = Sha256() + for (i in 0 until 16_777_216) { + if (i % 10000 == 0) { + println("$i/16777216") + } + sha256.update("abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmno") + } + val resultDoubleBlock = sha256.digest() + val expectedResultForDoubleBlock = "50e72a0e26442fe2552dc3938ac58658228c0cbfb1d2ca872ae435266fcd055e" + assertTrue { + resultDoubleBlock.contentEquals(expectedResultForDoubleBlock.chunked(2).map { it.toUByte(16) }.toTypedArray()) + } + } +} \ No newline at end of file diff --git a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/sha/Sha512Test.kt b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/hash/sha/Sha512Test.kt similarity index 76% rename from multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/sha/Sha512Test.kt rename to multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/hash/sha/Sha512Test.kt index 5a375e3..353c79d 100644 --- a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/sha/Sha512Test.kt +++ b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/hash/sha/Sha512Test.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.ionspin.kotlin.crypto.sha +package com.ionspin.kotlin.crypto.hash.sha import kotlin.test.Test import kotlin.test.assertTrue @@ -30,7 +30,7 @@ class Sha512Test { @Test fun testWellKnownValue() { - val result = Sha512.digest(message = "abc".encodeToByteArray().map { it.toUByte() }.toTypedArray()) + val result = Sha512.digest(inputMessage = "abc".encodeToByteArray().map { it.toUByte() }.toTypedArray()) println(result.map {it.toString(16)}) val expectedResult = "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a" + "2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f" @@ -44,12 +44,12 @@ class Sha512Test { @ExperimentalUnsignedTypes @ExperimentalStdlibApi @Test - fun testWellKnownDoubleBlock() { - - val resultDoubleBlock = Sha512.digest(message = "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmn" + - "hijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu") + fun testWellKnown3() { + val sha512 = Sha512() + sha512.update(data = "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu") + val resultDoubleBlock = sha512.digest() val expectedResultForDoubleBlock = "8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018" + - "501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909" + "501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909" assertTrue { resultDoubleBlock.contentEquals(expectedResultForDoubleBlock.chunked(2).map { it.toUByte(16) }.toTypedArray()) } @@ -62,7 +62,7 @@ class Sha512Test { for (i in 0 until 1000000) { inputBuilder.append("a") } - val resultDoubleBlock = Sha512.digest(message = (inputBuilder.toString()).encodeToByteArray().map { it.toUByte() }.toTypedArray()) + val resultDoubleBlock = Sha512.digest(inputMessage = (inputBuilder.toString()).encodeToByteArray().map { it.toUByte() }.toTypedArray()) val expectedResultForDoubleBlock = "e718483d0ce769644e2e42c7bc15b4638e1f98b13b2044285632a803afa973ebde0ff244877ea60a4cb0432ce577c31beb009c5c2c49aa2e4eadb217ad8cc09b" assertTrue { resultDoubleBlock.contentEquals(expectedResultForDoubleBlock.chunked(2).map { it.toUByte(16) }.toTypedArray()) diff --git a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/hash/sha/Sha512UpdateableTest.kt b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/hash/sha/Sha512UpdateableTest.kt new file mode 100644 index 0000000..9facd3b --- /dev/null +++ b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/hash/sha/Sha512UpdateableTest.kt @@ -0,0 +1,89 @@ +/* + * 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.hash.sha + +import kotlin.test.Ignore +import kotlin.test.Test +import kotlin.test.assertTrue + +/** + * Created by Ugljesa Jovanovic + * ugljesa.jovanovic@ionspin.com + * on 21-Jul-2019 + */ +class Sha512UpdatableTest { + @ExperimentalStdlibApi + @Test + fun testWellKnownValue() { + val sha512 = Sha512() + sha512.update("abc") + val result = sha512.digest() + val expectedResult = "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a" + + "2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f" + assertTrue { + result.contentEquals(expectedResult.chunked(2).map { it.toUByte(16) }.toTypedArray()) + } + + + } + + @ExperimentalStdlibApi + @Test + fun testWellKnownDoubleBlock() { + val sha512 = Sha512() + sha512.update(data = "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu") + val resultDoubleBlock = sha512.digest() + println(resultDoubleBlock.map{ it.toString(16)}.joinToString(separator = "")) + val expectedResultForDoubleBlock = "8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018" + + "501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909" + assertTrue { + resultDoubleBlock.contentEquals(expectedResultForDoubleBlock.chunked(2).map { it.toUByte(16) }.toTypedArray()) + } + } + + @ExperimentalStdlibApi + @Test + fun testWellKnownLong() { + val sha512 = Sha512() + for (i in 0 until 10000) { + sha512.update("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + } + val resultDoubleBlock = sha512.digest() + val expectedResultForDoubleBlock = "e718483d0ce769644e2e42c7bc15b4638e1f98b13b2044285632a803afa973ebde0ff244877ea60a4cb0432ce577c31beb009c5c2c49aa2e4eadb217ad8cc09b" + assertTrue { + resultDoubleBlock.contentEquals(expectedResultForDoubleBlock.chunked(2).map { it.toUByte(16) }.toTypedArray()) + } + } + + @Ignore() //Interestingly enough I'm not the only one having trouble with this test. + @ExperimentalStdlibApi + @Test + fun testWellKnownLonger() { + val sha512 = Sha512() + for (i in 0 until 16_777_216) { + if (i % 10000 == 0) { + println("$i/16777216") + } + sha512.update("abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmno") + } + val resultDoubleBlock = sha512.digest() + val expectedResultForDoubleBlock = "b47c933421ea2db149ad6e10fce6c7f93d0752380180ffd7f4629a712134831d77be6091b819ed352c2967a2e2d4fa5050723c9630691f1a05a7281dbe6c1086" + assertTrue { + resultDoubleBlock.contentEquals(expectedResultForDoubleBlock.chunked(2).map { it.toUByte(16) }.toTypedArray()) + } + } +} \ No newline at end of file