Added sha256 updateable mode

This commit is contained in:
Ugljesa Jovanovic 2019-07-20 23:46:17 +02:00
parent 3528dd390f
commit 187282232e
No known key found for this signature in database
GPG Key ID: 46D004C9820EBB98
3 changed files with 241 additions and 98 deletions

View File

@ -29,11 +29,11 @@ import com.ionspin.kotlin.crypto.rotateRight
@ExperimentalUnsignedTypes @ExperimentalUnsignedTypes
class Sha256() : Hash { class Sha256 : Hash {
companion object { companion object {
const val BLOCK_SIZE = 512 const val BLOCK_SIZE = 512
const val W_SIZE = 64 const val BLOCK_SIZE_IN_BYTES = 64
const val UINT_MASK = 0xFFFFFFFFU const val UINT_MASK = 0xFFFFFFFFU
const val BYTE_MASK_FROM_ULONG = 0xFFUL const val BYTE_MASK_FROM_ULONG = 0xFFUL
const val BYTE_MASK_FROM_UINT = 0xFFU const val BYTE_MASK_FROM_UINT = 0xFFU
@ -61,103 +61,37 @@ class Sha256() : Hash {
) )
@ExperimentalStdlibApi @ExperimentalStdlibApi
fun digest(message : String) : Array<UByte> { fun digest(message: String): Array<UByte> {
return digest(message.encodeToByteArray().map { it.toUByte() }.toTypedArray()) return digest(message.encodeToByteArray().map { it.toUByte() }.toTypedArray())
} }
fun digest(message: Array<UByte>) : Array<UByte> { fun digest(message: Array<UByte>): Array<UByte> {
var h0 = 0x6a09e667U var h = iv.copyOf()
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 val expansionArray = createExpansionArray(message.size)
val chunks = (
//K such that L + 1 + K + 64 is a multiple of 512 message +
val expandedRemainderOf512 = (originalMessageSizeInBits + 65) % BLOCK_SIZE expansionArray +
val zeroAddAmount = when (expandedRemainderOf512) { (message.size * 8).toULong().toPaddedByteArray()
0 -> 0 )
else -> (BLOCK_SIZE - expandedRemainderOf512) / 8 .chunked(BLOCK_SIZE_IN_BYTES)
}
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 -> chunks.forEach { chunk ->
val w = Array<UInt>(W_SIZE) { val w = expandChunk(chunk)
when (it) { mix(h, w).copyInto(h)
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() + val digest = h[0].toPaddedByteArray() +
h1.toPaddedByteArray() + h[1].toPaddedByteArray() +
h2.toPaddedByteArray() + h[2].toPaddedByteArray() +
h3.toPaddedByteArray() + h[3].toPaddedByteArray() +
h4.toPaddedByteArray() + h[4].toPaddedByteArray() +
h5.toPaddedByteArray() + h[5].toPaddedByteArray() +
h6.toPaddedByteArray() + h[6].toPaddedByteArray() +
h7.toPaddedByteArray() h[7].toPaddedByteArray()
return digest return digest
} }
@ -165,27 +99,104 @@ class Sha256() : Hash {
return value.rotateRight(7) xor value.rotateRight(18) xor (value shr 3) return value.rotateRight(7) xor value.rotateRight(18) xor (value shr 3)
} }
private fun scheduleSigma1(value : UInt) : UInt { private fun scheduleSigma1(value: UInt): UInt {
return value.rotateRight(17) xor value.rotateRight(19) xor (value shr 10) return value.rotateRight(17) xor value.rotateRight(19) xor (value shr 10)
} }
private fun compressionSigma0(a : UInt) : UInt { private fun compressionSigma0(a: UInt): UInt {
return (a rotateRight 2) xor (a rotateRight 13) xor (a rotateRight 22) return (a rotateRight 2) xor (a rotateRight 13) xor (a rotateRight 22)
} }
private fun compressionSigma1(e : UInt) : UInt { private fun compressionSigma1(e: UInt): UInt {
return (e rotateRight 6) xor (e rotateRight 11) xor (e rotateRight 25) return (e rotateRight 6) xor (e rotateRight 11) xor (e rotateRight 25)
} }
private fun ch(x : UInt, y : UInt, z : UInt) : UInt { private fun ch(x: UInt, y: UInt, z: UInt): UInt {
return ((x and y) xor ((x xor UINT_MASK) and z)) return ((x and y) xor ((x xor UINT_MASK) and z))
} }
private fun maj(x : UInt, y : UInt, z : UInt) : UInt { private fun maj(x: UInt, y: UInt, z: UInt): UInt {
return (((x and y) xor (x and z) xor (y and z))) 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> { private fun ULong.toPaddedByteArray(): Array<UByte> {
val byteMask = BYTE_MASK_FROM_ULONG val byteMask = BYTE_MASK_FROM_ULONG
@ -219,10 +230,85 @@ class Sha256() : Hash {
} }
var h = iv.copyOf()
var counter = 0
var bufferCounter = 0
var buffer = Array<UByte>(BLOCK_SIZE_IN_BYTES) { 0U }
@ExperimentalStdlibApi
fun update(message: String) {
return update(message.encodeToByteArray().map { it.toUByte() }.toTypedArray())
}
fun update(array: Array<UByte>) {
if (array.isEmpty()) {
throw RuntimeException("Updating with empty array is not allowed. If you need empty hash, just call digest without updating")
}
when {
bufferCounter + array.size < BLOCK_SIZE_IN_BYTES -> appendToBuffer(array, bufferCounter)
bufferCounter + array.size >= BLOCK_SIZE_IN_BYTES -> {
val chunked = array.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)
}
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
}
private fun appendToBuffer(array: Array<UByte>, start: Int) {
array.copyInto(destination = buffer, destinationOffset = start, startIndex = 0, endIndex = array.size)
bufferCounter += array.size
}

View File

@ -146,14 +146,14 @@ class Sha512 : Hash {
//K such that L + 1 + K + 64 is a multiple of 512 //K such that L + 1 + K + 64 is a multiple of 512
val expandedRemainderOf1024 = (originalMessageSizeInBits + 129) % BLOCK_SIZE val expandedRemainderOf1024 = (originalMessageSizeInBits + 130) % BLOCK_SIZE
val zeroAddAmount = when (expandedRemainderOf1024) { val zeroAddAmount = when (expandedRemainderOf1024) {
0 -> 0 0 -> 0
else -> (BLOCK_SIZE - expandedRemainderOf1024) / 8 else -> (BLOCK_SIZE - expandedRemainderOf1024) / 8
} }
val expansionArray = Array<UByte>(zeroAddAmount + 1) { val expansionArray = Array<UByte>(zeroAddAmount + 1) {
when (it) { when (it) {
0 -> 0b10000000U //TODO This wont work if there the byte needs to be shared with the L (length) ULong 0 -> 0b10000000U
else -> 0U else -> 0U
} }
} }

View File

@ -0,0 +1,57 @@
/*
* 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.chunked
import kotlin.test.Test
import kotlin.test.assertTrue
/**
* Created by Ugljesa Jovanovic
* ugljesa.jovanovic@ionspin.com
* on 17-Jul-2019
*/
@ExperimentalUnsignedTypes
class Sha256UpdateableTest {
@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(message = "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())
}
}
}