From 4cc7c7e92af0185ba47ee2ec2e27b38e0d2611f8 Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Tue, 12 May 2020 21:09:05 +0200 Subject: [PATCH] Continuing with index work, found a bug in initial hash, was missing type --- .../kotlin/crypto/keyderivation/Argon2.kt | 204 ++++++++++++++---- .../com/ionspin/kotlin/crypto/util/Util.kt | 4 +- .../crypto/hash/keyderivation/Argon2Test.kt | 63 ++++++ 3 files changed, 225 insertions(+), 46 deletions(-) create mode 100644 multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/hash/keyderivation/Argon2Test.kt diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/Argon2.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/Argon2.kt index 1098a01..8f5d11d 100644 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/Argon2.kt +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/Argon2.kt @@ -15,6 +15,7 @@ */ package com.ionspin.kotlin.crypto.keyderivation +import com.ionspin.kotlin.bignum.integer.toBigInteger import com.ionspin.kotlin.crypto.hash.blake2b.Blake2b import com.ionspin.kotlin.crypto.util.* /** @@ -31,6 +32,7 @@ import com.ionspin.kotlin.crypto.util.* * on 08-Jan-2020 * */ +@ExperimentalStdlibApi @ExperimentalUnsignedTypes class Argon2 internal constructor( val password: Array, @@ -78,13 +80,13 @@ class Argon2 internal constructor( return Blake2b.digest(length + input) } //We can cast to int because UInt even if MAX_VALUE divided by 32 is guaranteed not to overflow - val numberOfBlocks = (1U + ((length - 1U) / 32U) - 1U).toInt() // equivalent to ceil(length/32) - 1 - val v = Array>(numberOfBlocks) { emptyArray() } + val numberOf64ByteBlocks = (1U + ((length - 1U) / 32U) - 2U).toInt() // equivalent to ceil(length/32) - 2 + val v = Array>(numberOf64ByteBlocks) { emptyArray() } v[0] = Blake2b.digest(length + input) - for (i in 1 until numberOfBlocks - 1) { + for (i in 1 until numberOf64ByteBlocks) { v[i] = Blake2b.digest(v[i - 1]) } - val remainingPartOfInput = input.copyOfRange(input.size - numberOfBlocks * 32, input.size) + val remainingPartOfInput = input.copyOfRange(length.toInt() - numberOf64ByteBlocks * 32, input.size) val vLast = Blake2b.digest(remainingPartOfInput, hashLength = remainingPartOfInput.size) val concat = (v.map { it.copyOfRange(0, 32) }) @@ -217,6 +219,7 @@ class Argon2 internal constructor( counter.toUInt().toLittleEndianUByteArray() + Array(968) { 0U } ) + secondPass.hexColumsPrint() Pair(1U, 1U) } ArgonType.Argon2d -> { @@ -255,6 +258,63 @@ class Argon2 internal constructor( val laneCounter: Int ) + private fun computeIndexNew(matrix : Array>>, lane: Int, column: Int, columnCount: Int, parallelism: Int, iteration : Int, slice : Int, argonType: ArgonType) : Pair { + val (j1, j2) = when (argonType) { + ArgonType.Argon2d -> { + val previousBlock = if (column == 0) { + matrix[lane - 1][columnCount - 1] + } else { + matrix[lane][column - 1] + } + val first32Bit = previousBlock.sliceArray(0 until 4).fromLittleEndianArrayToUInt() + val second32Bit = previousBlock.sliceArray(4 until 8).fromLittleEndianArrayToUInt() + Pair(first32Bit, second32Bit) + } + ArgonType.Argon2i -> TODO() + ArgonType.Argon2id -> TODO() + } + + + //If this is first iteration and first slice, block is taken from the current lane + val l = if (iteration == 0 && slice == 0) { + lane + } else { + val lol = (j2.toBigInteger() % parallelism).intValue() + lol + } + + //From Argon 2 2020 draft + + // The set W contains the indices that can be referenced according to + // the following rules: + // 1. If l is the current lane, then W includes the indices of all + // blocks in the last SL - 1 = 3 segments computed and finished, as + // well as the blocks computed in the current segment in the current + // pass excluding B[i][j-1]. + // + // 2. If l is not the current lane, then W includes the indices of all + // blocks in the last SL - 1 = 3 segments computed and finished in + // lane l. If B[i][j] is the first block of a segment, then the + // very last index from W is excluded. + if (iteration == 0) { + if (slice == 0) { + //All indices except the previous + val from0Until = column - 1 + } else { + if (lane == l) { + //Same lane + val from0Until = slice * (columnCount / 4) + column - 1 + } else { + val from0Until = slice * (columnCount / 4) + if(column == 0) { -1 } else { 0 } + } + } + } + + val availableIndicesSet = + + return Pair(l, j2.toInt()) + } + internal fun derive( password: Array, salt: Array, @@ -267,61 +327,100 @@ class Argon2 internal constructor( associatedData: Array, type: ArgonType ): Array { + + val toDigest = parallelism.toLittleEndianUByteArray() + tagLength.toLittleEndianUByteArray() + memorySize.toLittleEndianUByteArray() + + numberOfIterations.toLittleEndianUByteArray() + versionNumber.toLittleEndianUByteArray() + type.typeId.toUInt().toLittleEndianUByteArray() + + password.size.toUInt().toLittleEndianUByteArray() + password + + salt.size.toUInt().toLittleEndianUByteArray() + salt + + key.size.toUInt().toLittleEndianUByteArray() + key + + associatedData.size.toUInt().toLittleEndianUByteArray() + associatedData + toDigest.hexColumsPrint(16) val h0 = Blake2b.digest( parallelism.toLittleEndianUByteArray() + tagLength.toLittleEndianUByteArray() + memorySize.toLittleEndianUByteArray() + - numberOfIterations.toLittleEndianUByteArray() + versionNumber.toLittleEndianUByteArray() + + numberOfIterations.toLittleEndianUByteArray() + versionNumber.toLittleEndianUByteArray() + type.typeId.toUInt().toLittleEndianUByteArray()+ password.size.toUInt().toLittleEndianUByteArray() + password + salt.size.toUInt().toLittleEndianUByteArray() + salt + key.size.toUInt().toLittleEndianUByteArray() + key + associatedData.size.toUInt().toLittleEndianUByteArray() + associatedData ) - val blockCount = (memorySize / (4U * parallelism)) * (4U * parallelism) // TODO hmmm - val columnCount = blockCount / parallelism + h0.hexColumsPrint(8) + + val blockCount = (memorySize / (4U * parallelism)) * (4U * parallelism) + val columnCount = (blockCount / parallelism).toInt() + val segmentLength = columnCount / 4 + + // First iteration + + //Allocate memory as Array of parallelism rows (lanes) and columnCount columns + val matrix = Array(parallelism.toInt()) { + Array(columnCount) { + Array(1024) { 0U } + } + } +// matrix.hexPrint() + + //Compute B[i][0] + for (i in 0 until parallelism.toInt()) { + matrix[i][0] = + argonBlake2bArbitraryLenghtHash( + h0 + 0.toUInt().toLittleEndianUByteArray() + i.toUInt().toLittleEndianUByteArray(), + 1024U + ) + } + + //Compute B[i][1] + for (i in 0 until parallelism.toInt()) { + matrix[i][1] = + argonBlake2bArbitraryLenghtHash( + h0 + 1.toUInt().toLittleEndianUByteArray() + i.toUInt().toLittleEndianUByteArray(), + 1024U + ) + } + + //Compute B[i][j] + //Using B[i][j] = G(B[i][j], B[l][z]) where l and z are provided bu computeIndexes + for (i in 0 until parallelism.toInt()) { + for (j in 2..columnCount) { + val (l, z) = computeIndexNew(matrix, i, j, columnCount, parallelism.toInt(), 0, 0, type) + matrix[i][j] = compressionFunctionG(matrix[i][j], matrix[l][z]) + } + } + //Remaining iteration + val remainingIterations = (1..numberOfIterations.toInt()).map { iteration -> + + for (i in 0 until parallelism.toInt()) { + for (j in 0 until columnCount) { +// val indexContext = IndexContext( +// indexMatrix = emptyArray(), +// parallelism = parallelism, +// pass = pass, +// lane = i, +// column = j, +// blockCount = blockCount, +// iterationCount = numberOfIterations, +// type = type, +// laneCounter = 0 +// ) + + val (l,z) = computeIndexNew(matrix, i, j, columnCount, parallelism.toInt(), iteration, iteration / segmentLength, type) + if (j == 0) { + matrix[i][j] = compressionFunctionG(matrix[i][columnCount - 1], matrix[l][z]) + } else { + matrix[i][j] = compressionFunctionG(matrix[i][j - 1], matrix[l][z]) + } - //TODO pass handling - val allPasses = (0..numberOfIterations.toLong()).map { pass -> - //Allocate memory as Array of parallelism rows and columnCount colums - val matrix = Array(parallelism.toInt()) { - Array(columnCount.toInt()) { - Array(1024) { 0U } } } - //Compute B[i][0] - for (i in 0..parallelism.toInt()) { - matrix[i][0] = - argonBlake2bArbitraryLenghtHash( - h0 + 0.toUInt().toLittleEndianUByteArray() + i.toUInt().toLittleEndianUByteArray(), - 64U - ) - } - //Compute B[i][1] - for (i in 0..parallelism.toInt()) { - matrix[i][0] = - argonBlake2bArbitraryLenghtHash( - h0 + 1.toUInt().toLittleEndianUByteArray() + i.toUInt().toLittleEndianUByteArray(), - 64U - ) - } - for (i in 0..parallelism.toInt()) { - for (j in 1..columnCount.toInt()) { - - val counter = 0 //TODO handle counter - computeIndexes(matrix, parallelism, pass, i, j, blockCount, numberOfIterations, type) - val iPrim = -1 - val jPrim = -1 - matrix[i][j] = compressionFunctionG(matrix[i][j - 1], matrix[iPrim][jPrim]) - } - } - - val result = matrix.foldIndexed(emptyArray()) { index, acc, arrayOfArrays -> + val result = matrix.foldIndexed(emptyArray()) { lane, acc, laneArray -> return if (acc.size == 0) { - acc + arrayOfArrays[columnCount.toInt() - 1] + acc + laneArray[columnCount - 1] // add last element in first lane to the accumulator } else { - acc.mapIndexed { index, it -> it xor arrayOfArrays[columnCount.toInt() - 1][index] } + // For each element in our accumulator, xor it with an appropriate element from the last column in current lane (from 1 to `parallelism`) + acc.mapIndexed { index, it -> it xor laneArray[columnCount - 1][index] } .toTypedArray() } } @@ -330,10 +429,27 @@ class Argon2 internal constructor( - return allPasses.foldRight(emptyArray()) { arrayOfUBytes, acc -> acc xor arrayOfUBytes } //TODO placeholder + return remainingIterations.foldRight(emptyArray()) { arrayOfUBytes, acc -> acc xor arrayOfUBytes } //TODO placeholder } } + fun calculate(): Array { + return derive( + password, salt, parallelism, tagLength, memorySize, numberOfIterations, versionNumber, key, associatedData, type + ) + } + } + +internal object ArgonDebugUtils { + fun Array>>.hexPrint() { + forEachIndexed { i, lane -> + lane.forEachIndexed { j, column -> + println("Printing position at [$i], [$j]") + column.hexColumsPrint(32) + } + } + } +} diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/util/Util.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/util/Util.kt index e936bc8..ebc0085 100644 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/util/Util.kt +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/util/Util.kt @@ -26,8 +26,8 @@ fun Array.hexColumsPrint() { printout.forEach { println(it.joinToString(separator = " ") { it.toUpperCase() }) } } -fun Array.hexColumsPrint() { - val printout = this.map { it.toString(16) }.chunked(16) +fun Array.hexColumsPrint(chunk : Int = 16) { + val printout = this.map { it.toString(16).padStart(2, '0') }.chunked(chunk) printout.forEach { println(it.joinToString(separator = " ") { it.toUpperCase() }) } } diff --git a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/hash/keyderivation/Argon2Test.kt b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/hash/keyderivation/Argon2Test.kt new file mode 100644 index 0000000..6a59e61 --- /dev/null +++ b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/hash/keyderivation/Argon2Test.kt @@ -0,0 +1,63 @@ +/* + * 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. + */ + +@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE") + +package com.ionspin.kotlin.crypto.hash.keyderivation + +import com.ionspin.kotlin.crypto.keyderivation.Argon2 +import kotlin.test.Test + +/** + * Created by Ugljesa Jovanovic + * ugljesa.jovanovic@ionspin.com + * on 10-May-2020 + */ +@ExperimentalStdlibApi +class Argon2Test { + + @Test + fun debugTest() { + val memory = 32U //KiB + val iterations = 3U + val parallelism = 4U + val tagLength = 32U + val password: Array = arrayOf( + 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, + 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, + 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, + 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U, 0x01U + ) + val salt: Array = arrayOf(0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U, 0x02U) + val secret: Array = arrayOf(0x03U, 0x03U, 0x03U, 0x03U, 0x03U, 0x03U, 0x03U, 0x03U) + val associatedData: Array = arrayOf(0x04U, 0x04U, 0x04U, 0x04U, 0x04U, 0x04U, 0x04U, 0x04U, 0x04U, 0x04U, 0x04U, 0x04U) + + val digest = Argon2( + password, + salt, + parallelism, + tagLength, + memory, + iterations, + 0x13U, + secret, + associatedData, + type = Argon2.ArgonType.Argon2d + ) + val result = digest.calculate() + + } +} \ No newline at end of file