Added sha256 updateable mode
This commit is contained in:
		
							parent
							
								
									3528dd390f
								
							
						
					
					
						commit
						187282232e
					
				@ -29,11 +29,11 @@ import com.ionspin.kotlin.crypto.rotateRight
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ExperimentalUnsignedTypes
 | 
			
		||||
class Sha256() : Hash {
 | 
			
		||||
class Sha256 : Hash {
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        const val BLOCK_SIZE = 512
 | 
			
		||||
        const val W_SIZE = 64
 | 
			
		||||
        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
 | 
			
		||||
@ -61,103 +61,37 @@ class Sha256() : Hash {
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        @ExperimentalStdlibApi
 | 
			
		||||
        fun digest(message : String) : Array<UByte> {
 | 
			
		||||
        fun digest(message: String): Array<UByte> {
 | 
			
		||||
            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 h1 = 0xbb67ae85U
 | 
			
		||||
            var h2 = 0x3c6ef372U
 | 
			
		||||
            var h3 = 0xa54ff53aU
 | 
			
		||||
            var h4 = 0x510e527fU
 | 
			
		||||
            var h5 = 0x9b05688cU
 | 
			
		||||
            var h6 = 0x1f83d9abU
 | 
			
		||||
            var h7 = 0x5be0cd19U
 | 
			
		||||
            var h = iv.copyOf()
 | 
			
		||||
 | 
			
		||||
            val originalMessageSizeInBits = message.size * 8
 | 
			
		||||
            val expansionArray = createExpansionArray(message.size)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            //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)
 | 
			
		||||
            val chunks = (
 | 
			
		||||
                        message +
 | 
			
		||||
                                expansionArray +
 | 
			
		||||
                                (message.size * 8).toULong().toPaddedByteArray()
 | 
			
		||||
                        )
 | 
			
		||||
                    .chunked(BLOCK_SIZE_IN_BYTES)
 | 
			
		||||
 | 
			
		||||
            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 w = expandChunk(chunk)
 | 
			
		||||
                mix(h, w).copyInto(h)
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            val digest =  h0.toPaddedByteArray() +
 | 
			
		||||
                    h1.toPaddedByteArray() +
 | 
			
		||||
                    h2.toPaddedByteArray() +
 | 
			
		||||
                    h3.toPaddedByteArray() +
 | 
			
		||||
                    h4.toPaddedByteArray() +
 | 
			
		||||
                    h5.toPaddedByteArray() +
 | 
			
		||||
                    h6.toPaddedByteArray() +
 | 
			
		||||
                    h7.toPaddedByteArray()
 | 
			
		||||
            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
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -165,27 +99,104 @@ class Sha256() : Hash {
 | 
			
		||||
            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)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private fun compressionSigma0(a : UInt) : UInt {
 | 
			
		||||
            return (a rotateRight  2) xor (a rotateRight 13) xor (a rotateRight 22)
 | 
			
		||||
        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 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 {
 | 
			
		||||
        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 {
 | 
			
		||||
        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
 | 
			
		||||
@ -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
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -146,14 +146,14 @@ class Sha512 : Hash {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            //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) {
 | 
			
		||||
                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
 | 
			
		||||
                    0 -> 0b10000000U
 | 
			
		||||
                    else -> 0U
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -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())
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user