commit
32d2f5403c
@ -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
|
||||||
|
36
README.md
36
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**
|
**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()
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
}
|
||||||
|
|
@ -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 = "")
|
||||||
}
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -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())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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
|
@ -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>(
|
@ -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())
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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())
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user