Merge pull request #2 from ionspin/sha-improvement

Sha improvement
This commit is contained in:
Ugljesa Jovanovic 2019-07-21 13:02:50 +02:00 committed by GitHub
commit 32d2f5403c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1088 additions and 592 deletions

View File

@ -1,7 +1,10 @@
## Descriptive changelog ## Descriptive changelog
(All dates are DD.MM.YYYY) (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 #### Initial release - 0.0.1 - 20.7.2019
- Implemented Blake2b and SHA256/512 - Implemented Blake2b and SHA256/512

View File

@ -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** **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. 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 ## 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 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 You need to deliver the complete data that is to be hashed in one go
```kotlin ```kotlin
@ -62,9 +68,9 @@ val result = Blake2b.digest(input)
Result is returned as a `Array<Byte>` Result is returned as a `Array<Byte>`
#### Using a `Blake2b` instance ##### Updatable instance version
You can create an instance and feed the data by using `update(input : Array<Byte>)` call. Once all data is supplied, You can create an instance and feed the data by using `update(input : Array<Byte>)` call. Once all data is supplied,
you should call `digest()` or `digestString()` convinence method that converts the `Array<Byte>` into hexadecimal string. you should call `digest()` or `digestString()` convenience method that converts the `Array<Byte>` into hexadecimal string.
If you want to use Blake2b with a key, you should supply it when creating the `Blake2b` instance. 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). 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<Byte>` as input You need to deliver the complete data that is to be hashed in one go. You can either provide the `Array<Byte>` as input
or `String`. Result is always returned as `Array<Byte>` (At least in verision 0.0.1) or `String`. Result is always returned as `Array<Byte>` (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<Byte>` Result is returned as a `Array<Byte>`
##### 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()
```

View File

@ -14,14 +14,37 @@
* limitations under the License. * limitations under the License.
*/ */
package com.ionspin.kotlin.crypto package com.ionspin.kotlin.crypto.hash
/** /**
* Created by Ugljesa Jovanovic * Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com * ugljesa.jovanovic@ionspin.com
* on 20-Jul-2019 * on 20-Jul-2019
*/ */
interface Hash interface Hash {
val MAX_HASH_BYTES : Int
}
interface UpdateableHash : Hash @ExperimentalUnsignedTypes
interface UpdatableHash : Hash {
fun update(data : Array<UByte>)
fun update(data : String)
fun digest() : Array<UByte>
fun digestString() : String
}
@ExperimentalUnsignedTypes
interface StatelessHash : Hash {
fun digest(inputString: String, key: String? = null, hashLength: Int = MAX_HASH_BYTES): Array<UByte>
fun digest(
inputMessage: Array<UByte> = emptyArray(),
key: Array<UByte> = emptyArray(),
hashLength: Int = MAX_HASH_BYTES
): Array<UByte>
}

View File

@ -14,14 +14,13 @@
* limitations under the License. * 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.BigInteger
import com.ionspin.kotlin.bignum.integer.toBigInteger import com.ionspin.kotlin.bignum.integer.toBigInteger
import com.ionspin.kotlin.crypto.* import com.ionspin.kotlin.crypto.*
import kotlinx.coroutines.CoroutineScope import com.ionspin.kotlin.crypto.hash.StatelessHash
import kotlinx.coroutines.Dispatchers import com.ionspin.kotlin.crypto.hash.UpdatableHash
import kotlinx.coroutines.Job
/** /**
* Created by Ugljesa Jovanovic * Created by Ugljesa Jovanovic
@ -30,13 +29,14 @@ import kotlinx.coroutines.Job
*/ */
@ExperimentalUnsignedTypes @ExperimentalUnsignedTypes
class Blake2b(val key: Array<UByte>? = null, val hashLength: Int = 64) : UpdateableHash { class Blake2b(val key: Array<UByte>? = null, val hashLength: Int = 64) : UpdatableHash {
companion object : Hash {
companion object : StatelessHash {
const val BITS_IN_WORD = 64 const val BITS_IN_WORD = 64
const val ROUNDS_IN_COMPRESS = 12 const val ROUNDS_IN_COMPRESS = 12
const val BLOCK_BYTES = 128 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 MIN_HASH_BYTES = 1
const val MAX_KEY_BYTES = 64 const val MAX_KEY_BYTES = 64
const val MIN_KEY_BYTES = 0 const val MIN_KEY_BYTES = 0
@ -143,38 +143,39 @@ class Blake2b(val key: Array<UByte>? = null, val hashLength: Int = 64) : Updatea
} }
@ExperimentalStdlibApi @ExperimentalStdlibApi
fun digest(inputString: String, key: String? = null): Array<UByte> { override fun digest(inputString: String, key: String?, hashLength: Int): Array<UByte> {
val chunked = inputString.encodeToByteArray().map { it.toUByte() }.toList().chunked(BLOCK_BYTES) val array = inputString.encodeToByteArray().map { it.toUByte() }.toList().toTypedArray()
.map { it.toTypedArray() }.toTypedArray()
val keyBytes = key?.run { val keyBytes = key?.run {
encodeToByteArray().map { it.toUByte() }.toTypedArray() encodeToByteArray().map { it.toUByte() }.toTypedArray()
} ?: emptyArray() } ?: emptyArray()
return digest(inputMessage = chunked, secretKey = keyBytes) return digest(inputMessage = array, key = keyBytes)
} }
fun digest( override fun digest(
inputMessage: Array<Array<UByte>> = emptyArray(), inputMessage: Array<UByte>,
secretKey: Array<UByte> = emptyArray(), key: Array<UByte>,
hashLength: Int = MAX_HASH_BYTES hashLength: Int
): Array<UByte> { ): Array<UByte> {
val chunkedMessage = inputMessage.chunked(BLOCK_BYTES)
val h = iv.copyOf() 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()) { val message = if (key.isEmpty()) {
if (inputMessage.isEmpty()) { if (chunkedMessage.isEmpty()) {
Array(1) { Array(1) {
Array<UByte>(128) { Array<UByte>(128) {
0U 0U
} }
} }
} else { } else {
inputMessage chunkedMessage
} }
} else { } else {
arrayOf(padToBlock(secretKey), *inputMessage) arrayOf(padToBlock(key), *chunkedMessage)
} }
if (message.size > 1) { if (message.size > 1) {
@ -246,6 +247,8 @@ class Blake2b(val key: Array<UByte>? = null, val hashLength: Int = 64) : Updatea
requestedHashLenght requestedHashLenght
) )
override val MAX_HASH_BYTES: Int = Blake2b.MAX_HASH_BYTES
var h = iv.copyOf() var h = iv.copyOf()
var counter = BigInteger.ZERO var counter = BigInteger.ZERO
var bufferCounter = 0 var bufferCounter = 0
@ -260,15 +263,15 @@ class Blake2b(val key: Array<UByte>? = null, val hashLength: Int = 64) : Updatea
} }
} }
fun update(array: Array<UByte>) { override fun update(data: Array<UByte>) {
if (array.isEmpty()) { if (data.isEmpty()) {
throw RuntimeException("Updating with empty array is not allowed. If you need empty hash, just call digest without updating") throw RuntimeException("Updating with empty array is not allowed. If you need empty hash, just call digest without updating")
} }
when { when {
bufferCounter + array.size < BLOCK_BYTES -> appendToBuffer(array, bufferCounter) bufferCounter + data.size < BLOCK_BYTES -> appendToBuffer(data, bufferCounter)
bufferCounter + array.size >= BLOCK_BYTES -> { bufferCounter + data.size >= BLOCK_BYTES -> {
val chunked = array.chunked(BLOCK_BYTES) val chunked = data.chunked(BLOCK_BYTES)
chunked.forEach { chunk -> chunked.forEach { chunk ->
if (bufferCounter + chunk.size < BLOCK_BYTES) { if (bufferCounter + chunk.size < BLOCK_BYTES) {
appendToBuffer(chunk, bufferCounter) appendToBuffer(chunk, bufferCounter)
@ -301,8 +304,8 @@ class Blake2b(val key: Array<UByte>? = null, val hashLength: Int = 64) : Updatea
} }
@ExperimentalStdlibApi @ExperimentalStdlibApi
fun update(input: String) { override fun update(data: String) {
update(input.encodeToByteArray().map { it.toUByte() }.toTypedArray()) update(data.encodeToByteArray().map { it.toUByte() }.toTypedArray())
} }
private fun appendToBuffer(array: Array<UByte>, start: Int) { private fun appendToBuffer(array: Array<UByte>, start: Int) {
@ -314,7 +317,7 @@ class Blake2b(val key: Array<UByte>? = null, val hashLength: Int = 64) : Updatea
h = compress(h, block, counter, false) h = compress(h, block, counter, false)
} }
fun digest(): Array<UByte> { override fun digest(): Array<UByte> {
val lastBlockPadded = padToBlock(buffer) val lastBlockPadded = padToBlock(buffer)
counter += bufferCounter counter += bufferCounter
compress(h, lastBlockPadded, counter, true) compress(h, lastBlockPadded, counter, true)
@ -325,7 +328,7 @@ class Blake2b(val key: Array<UByte>? = null, val hashLength: Int = 64) : Updatea
} }
fun digestString(): String { override fun digestString(): String {
return digest().map { it.toString(16) }.joinToString(separator = "") return digest().map { it.toString(16) }.joinToString(separator = "")
} }

View File

@ -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<UByte> {
return digest(
inputString.encodeToByteArray().map { it.toUByte() }.toTypedArray(),
key?.run { encodeToByteArray().map { it.toUByte() }.toTypedArray() } ?: emptyArray<UByte>(),
hashLength)
}
override fun digest(inputMessage: Array<UByte>, key: Array<UByte>, hashLength: Int): Array<UByte> {
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<UByte>): Array<UInt> {
val w = Array<UInt>(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<UInt>, w: Array<UInt>): Array<UInt> {
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<UByte> {
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<UByte>(zeroAddAmount + 1) {
when (it) {
0 -> 0b10000000U
else -> 0U
}
}
return expansionArray
}
private fun ULong.toPaddedByteArray(): Array<UByte> {
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<UByte> {
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<UByte>(BLOCK_SIZE_IN_BYTES) { 0U }
@ExperimentalStdlibApi
override fun update(data: String) {
return update(data.encodeToByteArray().map { it.toUByte() }.toTypedArray())
}
override fun update(data: Array<UByte>) {
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<UByte>(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<UByte>) {
val w = expandChunk(block)
mix(h, w).copyInto(h)
}
override fun digest(): Array<UByte> {
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<UByte>, start: Int) {
array.copyInto(destination = buffer, destinationOffset = start, startIndex = 0, endIndex = array.size)
bufferCounter += array.size
}
}

View File

@ -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<UByte> {
return digest(
inputString.encodeToByteArray().map { it.toUByte() }.toTypedArray(),
key?.run { encodeToByteArray().map { it.toUByte() }.toTypedArray() } ?: emptyArray<UByte>(),
hashLength = hashLength
)
}
override fun digest(inputMessage: Array<UByte>, key: Array<UByte>, hashLength: Int): Array<UByte> {
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<UByte>): Array<ULong> {
val w = Array<ULong>(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<ULong>, w: Array<ULong>): Array<ULong> {
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<UByte> {
val originalMessageSizeInBits = originalSizeInBytes * 8
val expandedRemainderOf1024 = (originalMessageSizeInBits + 129) % BLOCK_SIZE
val zeroAddAmount = when (expandedRemainderOf1024) {
0 -> 0
else -> (BLOCK_SIZE - expandedRemainderOf1024) / 8
}
val expansionArray = Array<UByte>(zeroAddAmount + 1) {
when (it) {
0 -> 0b10000000U
else -> 0U
}
}
return expansionArray
}
private fun ULong.toPaddedByteArray(): Array<UByte> {
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<UByte> {
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<UByte>(BLOCK_SIZE_IN_BYTES) { 0U }
@ExperimentalStdlibApi
override fun update(data: String) {
return update(data.encodeToByteArray().map { it.toUByte() }.toTypedArray())
}
override fun update(data: Array<UByte>) {
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<UByte>(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<UByte>) {
val w = expandChunk(block)
mix(h, w).copyInto(h)
}
override fun digest(): Array<UByte> {
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<UByte>, start: Int) {
array.copyInto(destination = buffer, destinationOffset = start, startIndex = 0, endIndex = array.size)
bufferCounter += array.size
}
}

View File

@ -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<UByte> {
return digest(message.encodeToByteArray().map { it.toUByte() }.toTypedArray())
}
fun digest(message: Array<UByte>) : Array<UByte> {
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<UByte>(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<UInt>(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<UByte> {
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<UByte> {
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")
}
}
}
}
}

View File

@ -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<UByte> {
return digest(message.encodeToByteArray().map { it.toUByte() }.toTypedArray())
}
fun digest(message: Array<UByte>): Array<UByte> {
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<UByte>(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<ULong>(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<UByte> {
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<UByte> {
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
}
}
}
}
}

View File

@ -16,9 +16,9 @@
package com.ionspin.kotlin.crypto package com.ionspin.kotlin.crypto
import com.ionspin.kotlin.crypto.blake2b.Blake2b import com.ionspin.kotlin.crypto.hash.blake2b.Blake2b
import com.ionspin.kotlin.crypto.sha.Sha256 import com.ionspin.kotlin.crypto.hash.sha.Sha256
import com.ionspin.kotlin.crypto.sha.Sha512 import com.ionspin.kotlin.crypto.hash.sha.Sha512
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertTrue import kotlin.test.assertTrue
@ -74,7 +74,7 @@ class ReadmeTest {
@Test @Test
fun sha256Example() { fun sha256Example() {
val input ="abc" val input ="abc"
val result = Sha256.digest(message = input) val result = Sha256.digest(inputString = input)
val expectedResult = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" val expectedResult = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
assertTrue { assertTrue {
result.contentEquals(expectedResult.chunked(2).map { it.toUByte(16) }.toTypedArray()) result.contentEquals(expectedResult.chunked(2).map { it.toUByte(16) }.toTypedArray())
@ -87,7 +87,7 @@ class ReadmeTest {
@Test @Test
fun sha512Example() { fun sha512Example() {
val input ="abc" 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)}) println(result.map {it.toString(16)})
val expectedResult = "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a" + val expectedResult = "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a" +
"2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f" "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())
}
} }
} }

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.ionspin.kotlin.crypto.blake2b package com.ionspin.kotlin.crypto.hash.blake2b
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertTrue import kotlin.test.assertTrue

View File

@ -14,11 +14,8 @@
* limitations under the License. * 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.Test
import kotlin.test.assertTrue import kotlin.test.assertTrue
@ -32,7 +29,7 @@ import kotlin.test.assertTrue
class Blake2bInstanceTest { class Blake2bInstanceTest {
@Test @Test
fun testUpdateableBlake2b() { fun testUpdatableBlake2b() {
val updates = 14 val updates = 14
val input = "1234567890" val input = "1234567890"
val expectedResult = arrayOf<UByte>( val expectedResult = arrayOf<UByte>(

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.ionspin.kotlin.crypto.blake2b package com.ionspin.kotlin.crypto.hash.blake2b
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertTrue import kotlin.test.assertTrue
@ -37,10 +37,9 @@ class Blake2bKnowAnswerTests {
fun knownAnswerTest() { fun knownAnswerTest() {
kat.forEach { kat.forEach {
val parsedInput = it.input.chunked(2).map { it.toUByte(16) }.toTypedArray() 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( val result = Blake2b.digest(
inputMessage = chunkedInput, inputMessage = parsedInput,
secretKey = it.key.chunked(2).map { it.toUByte(16) }.toTypedArray() key = it.key.chunked(2).map { it.toUByte(16) }.toTypedArray()
) )
assertTrue { assertTrue {
result.contentEquals(it.hash.chunked(2).map { it.toUByte(16) }.toTypedArray()) result.contentEquals(it.hash.chunked(2).map { it.toUByte(16) }.toTypedArray())

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.ionspin.kotlin.crypto.sha package com.ionspin.kotlin.crypto.hash.sha
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertTrue import kotlin.test.assertTrue
@ -31,7 +31,7 @@ class Sha256Test {
@Test @Test
fun testWellKnownValue() { fun testWellKnownValue() {
val result = Sha256.digest(message = "abc") val result = Sha256.digest(inputString = "abc")
val expectedResult = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" val expectedResult = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
assertTrue { assertTrue {
result.contentEquals(expectedResult.chunked(2).map { it.toUByte(16) }.toTypedArray()) result.contentEquals(expectedResult.chunked(2).map { it.toUByte(16) }.toTypedArray())
@ -44,10 +44,37 @@ class Sha256Test {
@Test @Test
fun testWellKnownDoubleBlock() { fun testWellKnownDoubleBlock() {
val resultDoubleBlock = Sha256.digest(message = "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq") val resultDoubleBlock = Sha256.digest(inputString = "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq")
val expectedResultForDoubleBlock = "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1" val expectedResultForDoubleBlock = "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1"
assertTrue { assertTrue {
resultDoubleBlock.contentEquals(expectedResultForDoubleBlock.chunked(2).map { it.toUByte(16) }.toTypedArray()) 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())
}
}
} }

View File

@ -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())
}
}
}

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.ionspin.kotlin.crypto.sha package com.ionspin.kotlin.crypto.hash.sha
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertTrue import kotlin.test.assertTrue
@ -30,7 +30,7 @@ class Sha512Test {
@Test @Test
fun testWellKnownValue() { 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)}) println(result.map {it.toString(16)})
val expectedResult = "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a" + val expectedResult = "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a" +
"2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f" "2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"
@ -44,12 +44,12 @@ class Sha512Test {
@ExperimentalUnsignedTypes @ExperimentalUnsignedTypes
@ExperimentalStdlibApi @ExperimentalStdlibApi
@Test @Test
fun testWellKnownDoubleBlock() { fun testWellKnown3() {
val sha512 = Sha512()
val resultDoubleBlock = Sha512.digest(message = "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmn" + sha512.update(data = "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu")
"hijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu") val resultDoubleBlock = sha512.digest()
val expectedResultForDoubleBlock = "8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018" + val expectedResultForDoubleBlock = "8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018" +
"501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909" "501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909"
assertTrue { assertTrue {
resultDoubleBlock.contentEquals(expectedResultForDoubleBlock.chunked(2).map { it.toUByte(16) }.toTypedArray()) resultDoubleBlock.contentEquals(expectedResultForDoubleBlock.chunked(2).map { it.toUByte(16) }.toTypedArray())
} }
@ -62,7 +62,7 @@ class Sha512Test {
for (i in 0 until 1000000) { for (i in 0 until 1000000) {
inputBuilder.append("a") 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" val expectedResultForDoubleBlock = "e718483d0ce769644e2e42c7bc15b4638e1f98b13b2044285632a803afa973ebde0ff244877ea60a4cb0432ce577c31beb009c5c2c49aa2e4eadb217ad8cc09b"
assertTrue { assertTrue {
resultDoubleBlock.contentEquals(expectedResultForDoubleBlock.chunked(2).map { it.toUByte(16) }.toTypedArray()) resultDoubleBlock.contentEquals(expectedResultForDoubleBlock.chunked(2).map { it.toUByte(16) }.toTypedArray())

View File

@ -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())
}
}
}