From cb7ca575abf458a5d706dae70ba8e95687b3a8c7 Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Sun, 5 Jan 2020 20:21:34 +0100 Subject: [PATCH 01/29] Working SRNG implementation for nodeJs and browser js --- multiplatform-crypto/build.gradle.kts | 39 ++++++++++++++++-- .../ionspin/kotlin/bignum/integer/Placeholder | 0 .../kotlin/com/ionspin/kotlin/crypto/SRNG.kt | 29 ++++++++++---- .../com/ionspin/kotlin/crypto/SRNGJsTest.kt | 40 +++++++++++++++++++ 4 files changed, 97 insertions(+), 11 deletions(-) delete mode 100644 multiplatform-crypto/src/jsMain/kotlin/com/ionspin/kotlin/bignum/integer/Placeholder create mode 100644 multiplatform-crypto/src/jsTest/kotlin/com/ionspin/kotlin/crypto/SRNGJsTest.kt diff --git a/multiplatform-crypto/build.gradle.kts b/multiplatform-crypto/build.gradle.kts index ea519f3..e7749e9 100644 --- a/multiplatform-crypto/build.gradle.kts +++ b/multiplatform-crypto/build.gradle.kts @@ -19,6 +19,8 @@ import com.moowork.gradle.node.task.NodeTask import org.gradle.api.tasks.testing.logging.TestLogging +import org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeTest +import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTest import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile plugins { @@ -63,13 +65,21 @@ kotlin { println("Destination dir ${it.compileKotlinTask.destinationDir}") } } - nodejs() { + browser { + testTask { + useKarma { + useChrome() + } + } + } + nodejs { testTask { useMocha() { timeout = "10s" } } } + } linuxX64("linux") { binaries { @@ -304,13 +314,34 @@ tasks { } } + val linuxTest by getting(KotlinNativeTest::class) { + + testLogging { + events("PASSED", "FAILED", "SKIPPED") + // showStandardStreams = true + } + } + + val jsNodeTest by getting(KotlinJsTest::class) { + + testLogging { + events("PASSED", "FAILED", "SKIPPED") + showStandardStreams = true + } + } + + val jsBrowserTest by getting(KotlinJsTest::class) { + + testLogging { + events("PASSED", "FAILED", "SKIPPED") + showStandardStreams = true + } + } + } - - - signing { isRequired = false sign(publishing.publications) diff --git a/multiplatform-crypto/src/jsMain/kotlin/com/ionspin/kotlin/bignum/integer/Placeholder b/multiplatform-crypto/src/jsMain/kotlin/com/ionspin/kotlin/bignum/integer/Placeholder deleted file mode 100644 index e69de29..0000000 diff --git a/multiplatform-crypto/src/jsMain/kotlin/com/ionspin/kotlin/crypto/SRNG.kt b/multiplatform-crypto/src/jsMain/kotlin/com/ionspin/kotlin/crypto/SRNG.kt index 0a7a85c..e7d7e7d 100644 --- a/multiplatform-crypto/src/jsMain/kotlin/com/ionspin/kotlin/crypto/SRNG.kt +++ b/multiplatform-crypto/src/jsMain/kotlin/com/ionspin/kotlin/crypto/SRNG.kt @@ -25,12 +25,27 @@ actual object SRNG { var counter = 0 @ExperimentalUnsignedTypes actual fun getRandomBytes(amount: Int): Array { -// val runningOnNode = js("(typeof window === 'undefined')").unsafeCast() -// if (runningOnNode) { -// js("var crypto = require('crypto')").asDynamic().randomBytes(amount) -// } else { -// throw RuntimeException("Secure random not supported yet for non-nodejs environment") -// } - return Array(amount) { (counter++).toUByte() } // TODO Wow. Such random. Very entropy. + val runningOnNode = js( + "if (typeof window === 'undefined') {\n" + + " true;\n" + + " } else {\n" + + " false;\n" + + " }" + ) + val randomBytes = if (runningOnNode) { + js("require('crypto')").randomBytes(amount).toJSON().data + } else { + js( + """ + var randomArray = new Uint8Array(amount); + var crypto = (self.crypto || self.msCrypto); + crypto.getRandomValues(randomArray); + """ + ) + var randomArrayResult = js("Array.prototype.slice.call(randomArray);") + randomArrayResult + } + + return randomBytes as Array } } \ No newline at end of file diff --git a/multiplatform-crypto/src/jsTest/kotlin/com/ionspin/kotlin/crypto/SRNGJsTest.kt b/multiplatform-crypto/src/jsTest/kotlin/com/ionspin/kotlin/crypto/SRNGJsTest.kt new file mode 100644 index 0000000..bfc7588 --- /dev/null +++ b/multiplatform-crypto/src/jsTest/kotlin/com/ionspin/kotlin/crypto/SRNGJsTest.kt @@ -0,0 +1,40 @@ +/* + * 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 + +import kotlin.test.Test +import kotlin.test.assertTrue + +/** + * Created by Ugljesa Jovanovic + * ugljesa.jovanovic@ionspin.com + * on 05-Jan-2020 + */ +class SRNGJsTest { + + @Test + fun testJsSrng() { + val bytes1 = SRNG.getRandomBytes(10) + val bytes2 = SRNG.getRandomBytes(10) + assertTrue { + !bytes1.contentEquals(bytes2) && + bytes1.size == 10 && + bytes2.size == 10 + } + + } +} \ No newline at end of file From 84799b4ede93341833ffb9dd8d73b3750cee28c1 Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Sun, 5 Jan 2020 20:41:58 +0100 Subject: [PATCH 02/29] A kludgy way to get the native to properly compile when in intellij IDE --- multiplatform-crypto/build.gradle.kts | 39 +++++++++++++++---- .../com/ionspin/kotlin/crypto/SRNGJsTest.kt | 1 + 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/multiplatform-crypto/build.gradle.kts b/multiplatform-crypto/build.gradle.kts index e7749e9..30c12ae 100644 --- a/multiplatform-crypto/build.gradle.kts +++ b/multiplatform-crypto/build.gradle.kts @@ -49,7 +49,12 @@ repositories { group = "com.ionspin.kotlin" version = "0.0.3-SNAPSHOT" +val ideaActive = System.getProperty("idea.active") == "true" + kotlin { + if (ideaActive) { + linuxX64("native") + } jvm() js { compilations { @@ -194,15 +199,35 @@ kotlin { implementation(kotlin("test-js")) } } - val nativeMain by creating { - dependsOn(commonMain) - } - val nativeTest by creating { - dependsOn(commonTest) - dependencies { - implementation(Deps.Native.coroutines) + val nativeMain = if (ideaActive) { + val nativeMain by getting { + dependsOn(commonMain) } + nativeMain + } else { + val nativeMain by creating { + dependsOn(commonMain) + } + nativeMain } + val nativeTest = if (ideaActive) { + val nativeTest by getting { + dependsOn(commonTest) + dependencies { + implementation(Deps.Native.coroutines) + } + } + nativeTest + } else { + val nativeTest by creating { + dependsOn(commonTest) + dependencies { + implementation(Deps.Native.coroutines) + } + } + nativeTest + } + val iosMain by getting { dependsOn(nativeMain) diff --git a/multiplatform-crypto/src/jsTest/kotlin/com/ionspin/kotlin/crypto/SRNGJsTest.kt b/multiplatform-crypto/src/jsTest/kotlin/com/ionspin/kotlin/crypto/SRNGJsTest.kt index bfc7588..55981de 100644 --- a/multiplatform-crypto/src/jsTest/kotlin/com/ionspin/kotlin/crypto/SRNGJsTest.kt +++ b/multiplatform-crypto/src/jsTest/kotlin/com/ionspin/kotlin/crypto/SRNGJsTest.kt @@ -24,6 +24,7 @@ import kotlin.test.assertTrue * ugljesa.jovanovic@ionspin.com * on 05-Jan-2020 */ +@ExperimentalUnsignedTypes class SRNGJsTest { @Test From 63df04a7b62988eee680e68d2ad35f7cb46f087a Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Mon, 6 Jan 2020 13:51:55 -0800 Subject: [PATCH 03/29] Implemented mingw srng, add some more gradle handling for idea, disabled mingwx86 for now as it's not supported by coroutines, and I want to use coroutines eventually --- multiplatform-crypto/build.gradle.kts | 62 +++++++++++++------ .../com/ionspin/kotlin/crypto/SRNGTest.kt | 6 +- .../kotlin/com/ionspin/kotlin/crypto/SRNG.kt | 43 +++++++++++++ .../kotlin/bignum/crypto/util/testBlocking.kt | 27 ++++++++ 4 files changed, 118 insertions(+), 20 deletions(-) create mode 100644 multiplatform-crypto/src/mingwX64Main/kotlin/com/ionspin/kotlin/crypto/SRNG.kt create mode 100644 multiplatform-crypto/src/mingwX64Test/kotlin/com/ionspin/kotlin/bignum/crypto/util/testBlocking.kt diff --git a/multiplatform-crypto/build.gradle.kts b/multiplatform-crypto/build.gradle.kts index 30c12ae..9564389 100644 --- a/multiplatform-crypto/build.gradle.kts +++ b/multiplatform-crypto/build.gradle.kts @@ -51,9 +51,22 @@ version = "0.0.3-SNAPSHOT" val ideaActive = System.getProperty("idea.active") == "true" +fun getHostOsName(): String { + val target = System.getProperty("os.name") + if (target == "Linux") return "linux" + if (target.startsWith("Windows")) return "windows" + if (target.startsWith("Mac")) return "macos" + return "unknown" +} + kotlin { + val hostOsName = getHostOsName() if (ideaActive) { - linuxX64("native") + when(hostOsName) { + "linux" -> linuxX64("native") + "macos" -> macosX64("native") + "windows" -> mingwX64("native") + } } jvm() js { @@ -131,13 +144,13 @@ kotlin { } } - mingwX86() { - binaries { - staticLib { - - } - } - } +// mingwX86() { +// binaries { +// staticLib { +// +// } +// } +// } linuxArm32Hfp() { binaries { @@ -168,7 +181,6 @@ kotlin { dependencies { implementation(kotlin(Deps.Common.test)) implementation(kotlin(Deps.Common.testAnnotation)) - implementation(Deps.Common.coroutines) } } val jvmMain by getting { @@ -263,20 +275,26 @@ kotlin { dependsOn(nativeTest) } - val mingwX86Main by getting { - dependsOn(nativeMain) - } - - val mingwX86Test by getting { - dependsOn(nativeTest) - } +// val mingwX86Main by getting { +// dependsOn(commonMain) +// dependencies { +// implementation(Deps.Native.coroutines) +// } +// } +// +// val mingwX86Test by getting { +// dependsOn(commonTest) +// } val mingwX64Main by getting { - dependsOn(nativeMain) + dependsOn(commonMain) + dependencies { + implementation(Deps.Native.coroutines) + } } val mingwX64Test by getting { - dependsOn(nativeTest) + dependsOn(commonTest) } val linuxArm32HfpMain by getting { @@ -347,6 +365,14 @@ tasks { } } + val mingwX64Test by getting(KotlinNativeTest::class) { + + testLogging { + events("PASSED", "FAILED", "SKIPPED") + showStandardStreams = true + } + } + val jsNodeTest by getting(KotlinJsTest::class) { testLogging { diff --git a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/SRNGTest.kt b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/SRNGTest.kt index 532da42..dba842a 100644 --- a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/SRNGTest.kt +++ b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/SRNGTest.kt @@ -27,9 +27,11 @@ import kotlin.test.assertTrue class SRNGTest { @Test fun testSrng() { + //Just a sanity test, need to add better srng tests. val randomBytes1 = SRNG.getRandomBytes(10) val randomBytes2 = SRNG.getRandomBytes(10) -// assertTrue { !randomBytes1.contentEquals(randomBytes2) } - //TODO implement SRNG for minGW + randomBytes1.forEach { println("RB1: $it")} + randomBytes2.forEach { println("RB2: $it")} + assertTrue { !randomBytes1.contentEquals(randomBytes2) } } } \ No newline at end of file diff --git a/multiplatform-crypto/src/mingwX64Main/kotlin/com/ionspin/kotlin/crypto/SRNG.kt b/multiplatform-crypto/src/mingwX64Main/kotlin/com/ionspin/kotlin/crypto/SRNG.kt new file mode 100644 index 0000000..f082a4b --- /dev/null +++ b/multiplatform-crypto/src/mingwX64Main/kotlin/com/ionspin/kotlin/crypto/SRNG.kt @@ -0,0 +1,43 @@ +/* + * 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 + +import kotlinx.cinterop.* +import platform.windows.* + +/** + * Created by Ugljesa Jovanovic + * ugljesa.jovanovic@ionspin.com + * on 21-Sep-2019 + */ +actual object SRNG { + private val advapi by lazy { LoadLibraryA("ADVAPI32.DLL")} + + private val advapiRandom by lazy { + GetProcAddress(advapi, "SystemFunction036")?.reinterpret, ULong, Int>>>() ?: error("Failed getting advapi random") + } + + @Suppress("EXPERIMENTAL_UNSIGNED_LITERALS") + actual fun getRandomBytes(amount: Int): Array { + memScoped { + val randArray = allocArray(amount) + val pointer = randArray.getPointer(this) + val status = advapiRandom(pointer.reinterpret(), amount.convert()) + return Array(amount) { pointer[it].toUByte() } + } + } +} \ No newline at end of file diff --git a/multiplatform-crypto/src/mingwX64Test/kotlin/com/ionspin/kotlin/bignum/crypto/util/testBlocking.kt b/multiplatform-crypto/src/mingwX64Test/kotlin/com/ionspin/kotlin/bignum/crypto/util/testBlocking.kt new file mode 100644 index 0000000..328fcc4 --- /dev/null +++ b/multiplatform-crypto/src/mingwX64Test/kotlin/com/ionspin/kotlin/bignum/crypto/util/testBlocking.kt @@ -0,0 +1,27 @@ +/* + * 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.util + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.runBlocking + +/** + * Created by Ugljesa Jovanovic + * ugljesa.jovanovic@ionspin.com + * on 20-Jul-2019 + */ +actual fun testBlocking(block: suspend (scope: CoroutineScope) -> Unit) = runBlocking { block(this) } \ No newline at end of file From decc1ab91ff4f033cbf0754dd176593d39892c4d Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Wed, 8 Jan 2020 21:13:41 +0100 Subject: [PATCH 04/29] Started work on argon2 implementation --- buildSrc/src/main/kotlin/Deps.kt | 2 +- .../kotlin/com/ionspin/kotlin/crypto/Util.kt | 19 +++++ .../kotlin/crypto/keyderivation/Argon2.kt | 74 +++++++++++++++++++ 3 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/Argon2.kt diff --git a/buildSrc/src/main/kotlin/Deps.kt b/buildSrc/src/main/kotlin/Deps.kt index 432d57a..a1d1631 100644 --- a/buildSrc/src/main/kotlin/Deps.kt +++ b/buildSrc/src/main/kotlin/Deps.kt @@ -21,7 +21,7 @@ object Versions { val nodePlugin = "1.3.0" val dokkaPlugin = "0.9.18" - val kotlinBigNumVersion = "0.1.5-SNAPSHOT" + val kotlinBigNumVersion = "0.1.6-SNAPSHOT" } diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/Util.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/Util.kt index 1b222ce..81c47b8 100644 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/Util.kt +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/Util.kt @@ -87,4 +87,23 @@ fun Array.toHexString() : String { it.toString(16) } } +} + +// UInt / Array utils +@ExperimentalUnsignedTypes +fun UInt.toBigEndianUByteArray() : Array { + return Array (4) { + ((this shr (24 - (it * 8))) and 0xFFU).toUByte() + } +} +@ExperimentalUnsignedTypes +fun UInt.toLittleEndianUByteArray() : Array { + return Array (4) { + ((this shr (it * 8)) and 0xFFU).toUByte() + } +} + +@ExperimentalUnsignedTypes +operator fun UInt.plus(other : Array) : Array { + return this.toLittleEndianUByteArray() + other } \ No newline at end of file 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 new file mode 100644 index 0000000..9cacd68 --- /dev/null +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/Argon2.kt @@ -0,0 +1,74 @@ +/* + * 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.keyderivation + +import com.ionspin.kotlin.crypto.chunked +import com.ionspin.kotlin.crypto.hash.blake2b.Blake2b +import com.ionspin.kotlin.crypto.plus + +/** + * Created by Ugljesa Jovanovic + * ugljesa.jovanovic@ionspin.com + * on 08-Jan-2020 + */ +@ExperimentalUnsignedTypes +class Argon2 internal constructor( + val password : Array, + val salt : Array, + val parallelism : UInt, + val tagLength : UInt, + val memorySize : UInt, + val numberOfIterations : UInt, + val versionNumber : UInt, + val key : Array, + val type : ArgonType +){ + enum class ArgonType { + Argon2i, Argon2d, Argon2id + } + companion object { + + + fun hash(input : Array, length : UInt) : Array { + if (length <= 64U) { + 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() } + v[0] = Blake2b.digest(length + input) + for (i in 1 until numberOfBlocks - 1) { + v[i] = Blake2b.digest(v[i-1]) + } + val remainingPartOfInput = input.copyOfRange(input.size - numberOfBlocks * 32, input.size) + v[numberOfBlocks] = Blake2b.digest(remainingPartOfInput, hashLength = remainingPartOfInput.size) + v.chunked(8) + + + + return emptyArray() + } + + } + + + + + + + +} \ No newline at end of file From d22f5b73f7cc2b368f7598fa708f4542f77af7ac Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Fri, 10 Jan 2020 23:58:28 +0100 Subject: [PATCH 05/29] Continued Argon2 implementation --- README.md | 6 + .../kotlin/crypto/keyderivation/Argon2.kt | 132 +++++++++++++++--- 2 files changed, 118 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 37e22d1..2f2be39 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,12 @@ It's not peer reviewed, not guaranteed to be bug free, and not guaranteed to be ## Symmetric cipher (Currently only available only in 0.0.3-SNAPSHOT) * AES * Modes: CBC, CTR + +## Key Derivation + +* Argon2 + +## AEAD More to come. 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 9cacd68..2cb0629 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 @@ -16,34 +16,43 @@ package com.ionspin.kotlin.crypto.keyderivation -import com.ionspin.kotlin.crypto.chunked import com.ionspin.kotlin.crypto.hash.blake2b.Blake2b import com.ionspin.kotlin.crypto.plus +import com.ionspin.kotlin.crypto.toLittleEndianUByteArray /** + * https://tools.ietf.org/html/draft-irtf-cfrg-argon2-03 + * https://en.wikipedia.org/wiki/Argon2 + * https://www.cryptolux.org/images/0/0d/Argon2.pdf + * * Created by Ugljesa Jovanovic * ugljesa.jovanovic@ionspin.com * on 08-Jan-2020 + * + * + * */ @ExperimentalUnsignedTypes class Argon2 internal constructor( - val password : Array, - val salt : Array, - val parallelism : UInt, - val tagLength : UInt, - val memorySize : UInt, - val numberOfIterations : UInt, - val versionNumber : UInt, - val key : Array, - val type : ArgonType -){ + val password: Array, + val salt: Array, + val parallelism: UInt, + val tagLength: UInt, + val memorySize: UInt, + val numberOfIterations: UInt, + val versionNumber: UInt, + val key: Array, + associatedData: Array + val type: ArgonType +) { enum class ArgonType { Argon2i, Argon2d, Argon2id } + companion object { - fun hash(input : Array, length : UInt) : Array { + fun argonHash(input: Array, length: UInt): Array { if (length <= 64U) { return Blake2b.digest(length + input) } @@ -52,11 +61,99 @@ class Argon2 internal constructor( val v = Array>(numberOfBlocks) { emptyArray() } v[0] = Blake2b.digest(length + input) for (i in 1 until numberOfBlocks - 1) { - v[i] = Blake2b.digest(v[i-1]) + v[i] = Blake2b.digest(v[i - 1]) } val remainingPartOfInput = input.copyOfRange(input.size - numberOfBlocks * 32, input.size) - v[numberOfBlocks] = Blake2b.digest(remainingPartOfInput, hashLength = remainingPartOfInput.size) - v.chunked(8) + val vLast = Blake2b.digest(remainingPartOfInput, hashLength = remainingPartOfInput.size) + val concat = + (v.map { it.copyOfRange(0, 32) }) + .plus(listOf(vLast)) + .foldRight(emptyArray()) { arrayOfUBytes, acc -> arrayOfUBytes + acc } + + + + return concat + } + + fun compressionFunctionG(x : Array, y : Array) : Array>> { + val r = Array(1024) { 0U } + x.forEachIndexed { index, it -> r[index] = it xor y[index] } + // mix rounds + return emptyArray() // TODO + } + + // --------- Unmodified blake2b mixing + /* + internal fun mixRound(input: Array, message: Array, round: Int): Array { + var v = input + val selectedSigma = sigma[round % 10] + v = mix(v, 0, 4, 8, 12, message[selectedSigma[0]], message[selectedSigma[1]]) + v = mix(v, 1, 5, 9, 13, message[selectedSigma[2]], message[selectedSigma[3]]) + v = mix(v, 2, 6, 10, 14, message[selectedSigma[4]], message[selectedSigma[5]]) + v = mix(v, 3, 7, 11, 15, message[selectedSigma[6]], message[selectedSigma[7]]) + v = mix(v, 0, 5, 10, 15, message[selectedSigma[8]], message[selectedSigma[9]]) + v = mix(v, 1, 6, 11, 12, message[selectedSigma[10]], message[selectedSigma[11]]) + v = mix(v, 2, 7, 8, 13, message[selectedSigma[12]], message[selectedSigma[13]]) + v = mix(v, 3, 4, 9, 14, message[selectedSigma[14]], message[selectedSigma[15]]) + return v + + } + + private fun mix(v: Array, a: Int, b: Int, c: Int, d: Int, x: ULong, y: ULong): Array { + v[a] = (v[a] + v[b] + x) + v[d] = (v[d] xor v[a]) rotateRight R1 + v[c] = (v[c] + v[d]) + v[b] = (v[b] xor v[c]) rotateRight R2 + v[a] = (v[a] + v[b] + y) + v[d] = (v[d] xor v[a]) rotateRight R3 + v[c] = (v[c] + v[d]) + v[b] = (v[b] xor v[c]) rotateRight R4 + return v + } + */ + + internal fun derive( + password: Array, + salt: Array, + parallelism: UInt, + tagLength: UInt, + memorySize: UInt, + numberOfIterations: UInt, + versionNumber: UInt, + key: Array, + associatedData: Array, + type: ArgonType + ): Array { + val h0 = Blake2b.digest( + parallelism.toLittleEndianUByteArray() + tagLength.toLittleEndianUByteArray() + memorySize.toLittleEndianUByteArray() + + numberOfIterations.toLittleEndianUByteArray() + versionNumber.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) // + val columnCount = blockCount / parallelism + + //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] = + argonHash(h0 + 0.toUInt().toLittleEndianUByteArray() + i.toUInt().toLittleEndianUByteArray(), 64U) + } + + //Compute B[i][1] + for (i in 0..parallelism.toInt()) { + matrix[i][0] = + argonHash(h0 + 1.toUInt().toLittleEndianUByteArray() + i.toUInt().toLittleEndianUByteArray(), 64U) + } @@ -66,9 +163,4 @@ class Argon2 internal constructor( } - - - - - } \ No newline at end of file From 7e8e48aa1d7bd914edd9176fadf1299e1352d1aa Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Sat, 11 Jan 2020 18:03:55 +0100 Subject: [PATCH 06/29] Moved util class to util package, added a couple of util tests, continued Argon implementation by completeing compression function --- .../kotlin/crypto/hash/blake2b/Blake2b.kt | 2 + .../ionspin/kotlin/crypto/hash/sha/Sha256.kt | 4 +- .../ionspin/kotlin/crypto/hash/sha/Sha512.kt | 4 +- .../kotlin/crypto/keyderivation/Argon2.kt | 116 +++++++++++++----- .../ionspin/kotlin/crypto/symmetric/AesCbc.kt | 4 +- .../ionspin/kotlin/crypto/symmetric/AesCtr.kt | 4 +- .../ionspin/kotlin/crypto/{ => util}/Util.kt | 39 +++++- .../com/ionspin/kotlin/crypto/UtilTest.kt | 38 ------ .../kotlin/crypto/symmetric/AesCbcTest.kt | 4 +- .../kotlin/crypto/symmetric/AesCtrTest.kt | 4 +- .../ionspin/kotlin/crypto/util/UtilTest.kt | 100 +++++++++++++++ 11 files changed, 239 insertions(+), 80 deletions(-) rename multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/{ => util}/Util.kt (75%) delete mode 100644 multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/UtilTest.kt create mode 100644 multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/util/UtilTest.kt diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/hash/blake2b/Blake2b.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/hash/blake2b/Blake2b.kt index b462faa..07f151d 100644 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/hash/blake2b/Blake2b.kt +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/hash/blake2b/Blake2b.kt @@ -21,6 +21,8 @@ import com.ionspin.kotlin.bignum.integer.toBigInteger import com.ionspin.kotlin.crypto.* import com.ionspin.kotlin.crypto.hash.StatelessHash import com.ionspin.kotlin.crypto.hash.UpdatableHash +import com.ionspin.kotlin.crypto.util.chunked +import com.ionspin.kotlin.crypto.util.rotateRight /** * Created by Ugljesa Jovanovic diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/hash/sha/Sha256.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/hash/sha/Sha256.kt index 34995a7..1a222ff 100644 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/hash/sha/Sha256.kt +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/hash/sha/Sha256.kt @@ -16,10 +16,10 @@ package com.ionspin.kotlin.crypto.hash.sha -import com.ionspin.kotlin.crypto.chunked +import com.ionspin.kotlin.crypto.util.chunked import com.ionspin.kotlin.crypto.hash.StatelessHash import com.ionspin.kotlin.crypto.hash.UpdatableHash -import com.ionspin.kotlin.crypto.rotateRight +import com.ionspin.kotlin.crypto.util.rotateRight /** diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/hash/sha/Sha512.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/hash/sha/Sha512.kt index 217175e..69618cf 100644 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/hash/sha/Sha512.kt +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/hash/sha/Sha512.kt @@ -16,10 +16,10 @@ package com.ionspin.kotlin.crypto.hash.sha -import com.ionspin.kotlin.crypto.chunked +import com.ionspin.kotlin.crypto.util.chunked import com.ionspin.kotlin.crypto.hash.StatelessHash import com.ionspin.kotlin.crypto.hash.UpdatableHash -import com.ionspin.kotlin.crypto.rotateRight +import com.ionspin.kotlin.crypto.util.rotateRight /** * Created by Ugljesa Jovanovic 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 2cb0629..300f47c 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 @@ -17,8 +17,7 @@ package com.ionspin.kotlin.crypto.keyderivation import com.ionspin.kotlin.crypto.hash.blake2b.Blake2b -import com.ionspin.kotlin.crypto.plus -import com.ionspin.kotlin.crypto.toLittleEndianUByteArray +import com.ionspin.kotlin.crypto.util.* /** * https://tools.ietf.org/html/draft-irtf-cfrg-argon2-03 @@ -42,13 +41,14 @@ class Argon2 internal constructor( val numberOfIterations: UInt, val versionNumber: UInt, val key: Array, - associatedData: Array + val associatedData: Array, val type: ArgonType ) { enum class ArgonType { Argon2i, Argon2d, Argon2id } + @ExperimentalStdlibApi companion object { @@ -70,47 +70,88 @@ class Argon2 internal constructor( .plus(listOf(vLast)) .foldRight(emptyArray()) { arrayOfUBytes, acc -> arrayOfUBytes + acc } - - return concat } - fun compressionFunctionG(x : Array, y : Array) : Array>> { - val r = Array(1024) { 0U } + fun compressionFunctionG(x: Array, y: Array): Array { + val r = Array(1024) { 0U } // view as 8x8 matrix of 16 byte registers x.forEachIndexed { index, it -> r[index] = it xor y[index] } - // mix rounds - return emptyArray() // TODO + val q = Array(1024) { 0U } + val z = Array(1024) { 0U } + // Do the argon/blake2b mixing on rows + for (i in 0..7) { + val startOfRow = (i * 8 * 16) + val endOfRow = startOfRow + (8 * 16) + mixRound(r.copyOfRange(startOfRow, endOfRow)) + .map { it.toLittleEndianUByteArray() } + .flatMap { it.asIterable() } + .toTypedArray() + .copyInto(q, startOfRow, endOfRow) + } + // Do the argon/blake2b mixing on columns + for (i in 0..7) { + copyIntoGBlockColumn( + z, + i, + mixRound(extractColumnFromGBlock(q, i)) + .map { it.toLittleEndianUByteArray() } + .flatMap { it.asIterable() } + .toTypedArray() + ) + } + // Z = Z xor R + r.forEachIndexed { index, it -> z[index] = it xor z[index] } + return z } - // --------- Unmodified blake2b mixing - /* - internal fun mixRound(input: Array, message: Array, round: Int): Array { - var v = input - val selectedSigma = sigma[round % 10] - v = mix(v, 0, 4, 8, 12, message[selectedSigma[0]], message[selectedSigma[1]]) - v = mix(v, 1, 5, 9, 13, message[selectedSigma[2]], message[selectedSigma[3]]) - v = mix(v, 2, 6, 10, 14, message[selectedSigma[4]], message[selectedSigma[5]]) - v = mix(v, 3, 7, 11, 15, message[selectedSigma[6]], message[selectedSigma[7]]) - v = mix(v, 0, 5, 10, 15, message[selectedSigma[8]], message[selectedSigma[9]]) - v = mix(v, 1, 6, 11, 12, message[selectedSigma[10]], message[selectedSigma[11]]) - v = mix(v, 2, 7, 8, 13, message[selectedSigma[12]], message[selectedSigma[13]]) - v = mix(v, 3, 4, 9, 14, message[selectedSigma[14]], message[selectedSigma[15]]) + private fun extractColumnFromGBlock(gBlock: Array, columnPosition: Int): Array { + val result = Array(128) { 0U } + for (i in 0..7) { + result[i] = gBlock[i * 8 + columnPosition] + } + return result + } + + private fun copyIntoGBlockColumn(gBlock: Array, columnPosition: Int, columnData: Array) { + for (i in 0..7) { + gBlock[i * 8 + columnPosition] = columnData[i] + } + } + + + //based on Blake2b mixRound + internal fun mixRound(input: Array): Array { + var v = input.chunked(4).map { it.fromLittleEndianArrayToULong() }.toTypedArray() + v = mix(v, 0, 4, 8, 12) + v = mix(v, 1, 5, 9, 13) + v = mix(v, 2, 6, 10, 14) + v = mix(v, 3, 7, 11, 15) + v = mix(v, 0, 5, 10, 15) + v = mix(v, 1, 6, 11, 12) + v = mix(v, 2, 7, 8, 13) + v = mix(v, 3, 4, 9, 14) return v } - private fun mix(v: Array, a: Int, b: Int, c: Int, d: Int, x: ULong, y: ULong): Array { - v[a] = (v[a] + v[b] + x) + const val R1 = 32 + const val R2 = 24 + const val R3 = 16 + const val R4 = 63 + + //Based on Blake2b mix + private fun mix(v: Array, a: Int, b: Int, c: Int, d: Int): Array { + v[a] = (v[a] + v[b] * 2U * a.toUInt() * b.toUInt()) v[d] = (v[d] xor v[a]) rotateRight R1 - v[c] = (v[c] + v[d]) + v[c] = (v[c] + v[d] * 2U * c.toUInt() * d.toUInt()) v[b] = (v[b] xor v[c]) rotateRight R2 - v[a] = (v[a] + v[b] + y) + v[a] = (v[a] + v[b] * 2U * a.toUInt() * b.toUInt()) v[d] = (v[d] xor v[a]) rotateRight R3 - v[c] = (v[c] + v[d]) + v[c] = (v[c] + v[d] * 2U * c.toUInt() * d.toUInt()) v[b] = (v[b] xor v[c]) rotateRight R4 return v } - */ + internal fun derive( password: Array, @@ -155,9 +196,26 @@ class Argon2 internal constructor( argonHash(h0 + 1.toUInt().toLittleEndianUByteArray() + i.toUInt().toLittleEndianUByteArray(), 64U) } + for (i in 0..parallelism.toInt()) { + for (j in 1..columnCount.toInt()) { + //TODO i,j choosing based on 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 -> + return if (acc.size == 0) { + acc + arrayOfArrays[columnCount.toInt() - 1] + } else { + acc.mapIndexed { index, it -> it xor arrayOfArrays[columnCount.toInt() - 1][index] }.toTypedArray() + } + } - return emptyArray() + + return result } } diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/AesCbc.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/AesCbc.kt index 4d7125e..3d6d7e0 100644 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/AesCbc.kt +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/AesCbc.kt @@ -17,8 +17,8 @@ package com.ionspin.kotlin.crypto.symmetric import com.ionspin.kotlin.crypto.SRNG -import com.ionspin.kotlin.crypto.chunked -import com.ionspin.kotlin.crypto.xor +import com.ionspin.kotlin.crypto.util.chunked +import com.ionspin.kotlin.crypto.util.xor /** * Advanced encryption standard with cipher block chaining and PKCS #5 diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/AesCtr.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/AesCtr.kt index 4d5373a..66b7423 100644 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/AesCtr.kt +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/AesCtr.kt @@ -20,9 +20,9 @@ import com.ionspin.kotlin.bignum.Endianness import com.ionspin.kotlin.bignum.integer.BigInteger import com.ionspin.kotlin.bignum.modular.ModularBigInteger import com.ionspin.kotlin.crypto.SRNG -import com.ionspin.kotlin.crypto.chunked +import com.ionspin.kotlin.crypto.util.chunked import com.ionspin.kotlin.crypto.symmetric.AesCtr.Companion.encrypt -import com.ionspin.kotlin.crypto.xor +import com.ionspin.kotlin.crypto.util.xor /** * diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/Util.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/util/Util.kt similarity index 75% rename from multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/Util.kt rename to multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/util/Util.kt index 81c47b8..8d8924b 100644 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/Util.kt +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/util/Util.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.ionspin.kotlin.crypto +package com.ionspin.kotlin.crypto.util /** * Created by Ugljesa Jovanovic @@ -103,6 +103,43 @@ fun UInt.toLittleEndianUByteArray() : Array { } } +// UInt / Array utils +@ExperimentalUnsignedTypes +fun ULong.toBigEndianUByteArray() : Array { + return Array (8) { + ((this shr (56 - (it * 8))) and 0xFFU).toUByte() + } +} +@ExperimentalUnsignedTypes +fun ULong.toLittleEndianUByteArray() : Array { + return Array (8) { + ((this shr (it * 8)) and 0xFFU).toUByte() + } +} +@ExperimentalUnsignedTypes +fun Array.fromLittleEndianArrayToULong() : ULong { + if (this.size > 8) { + throw RuntimeException("ore than 8 bytes in input, potential overflow") + } + var ulong = this.foldIndexed(0UL) { index, acc, uByte -> acc or (uByte.toULong() shl (index * 8))} + return ulong +} + + +@ExperimentalUnsignedTypes +fun Array.fromBigEndianArrayToULong() : ULong { + if (this.size > 8) { + throw RuntimeException("ore than 8 bytes in input, potential overflow") + } + var ulong = this.foldIndexed(0UL) { + index, acc, uByte -> + val res = acc or (uByte.toULong() shl (56 - (index * 8))) + res + + } + return ulong +} + @ExperimentalUnsignedTypes operator fun UInt.plus(other : Array) : Array { return this.toLittleEndianUByteArray() + other diff --git a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/UtilTest.kt b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/UtilTest.kt deleted file mode 100644 index 79a6bbd..0000000 --- a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/UtilTest.kt +++ /dev/null @@ -1,38 +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 - -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 - */ -class UtilTest { - - @Test - fun testSlicer() { - val array = arrayOf(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17) - val chunked = array.chunked(2) - assertTrue { - chunked.size == 9 && chunked[8][0] == 17 - } - } -} \ No newline at end of file diff --git a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesCbcTest.kt b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesCbcTest.kt index b55a715..f0bada0 100644 --- a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesCbcTest.kt +++ b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesCbcTest.kt @@ -16,8 +16,8 @@ package com.ionspin.kotlin.crypto.symmetric -import com.ionspin.kotlin.crypto.hexStringToUByteArray -import com.ionspin.kotlin.crypto.toHexString +import com.ionspin.kotlin.crypto.util.hexStringToUByteArray +import com.ionspin.kotlin.crypto.util.toHexString import kotlin.test.Test import kotlin.test.assertTrue diff --git a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesCtrTest.kt b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesCtrTest.kt index e80a995..4b140b5 100644 --- a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesCtrTest.kt +++ b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesCtrTest.kt @@ -16,8 +16,8 @@ package com.ionspin.kotlin.crypto.symmetric -import com.ionspin.kotlin.crypto.hexStringToUByteArray -import com.ionspin.kotlin.crypto.toHexString +import com.ionspin.kotlin.crypto.util.hexStringToUByteArray +import com.ionspin.kotlin.crypto.util.toHexString import kotlin.test.Test import kotlin.test.assertTrue diff --git a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/util/UtilTest.kt b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/util/UtilTest.kt new file mode 100644 index 0000000..74d11d6 --- /dev/null +++ b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/util/UtilTest.kt @@ -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.util + +import kotlin.test.Test +import kotlin.test.assertTrue + +/** + * Created by Ugljesa Jovanovic + * ugljesa.jovanovic@ionspin.com + * on 17-Jul-2019 + */ +@ExperimentalUnsignedTypes +class UtilTest { + + @Test + fun testSlicer() { + val array = arrayOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17) + val chunked = array.chunked(2) + assertTrue { + chunked.size == 9 && chunked[8][0] == 17 + } + } + + @Test + fun testUIntToBigEndianArray() { + assertTrue { + val original = 1U + val converted = original.toBigEndianUByteArray() + converted[0] = 1U + true + } + assertTrue { + val original = 0xAABBCCDDU + val converted = original.toBigEndianUByteArray() + converted[0] == 0xAAU.toUByte() && + converted[1] == 0xBBU.toUByte() && + converted[2] == 0xCCU.toUByte() && + converted[3] == 0xDDU.toUByte() + + } + } + + @Test + fun testUIntToLittleEndianArray() { + assertTrue { + val original = 1U + val converted = original.toBigEndianUByteArray() + converted[4] = 1U + true + } + assertTrue { + val original = 0xAABBCCDDU + val converted = original.toBigEndianUByteArray() + converted[0] == 0xDDU.toUByte() && + converted[1] == 0xCCU.toUByte() && + converted[2] == 0xBBU.toUByte() && + converted[3] == 0xAAU.toUByte() + + } + } + + @Test + fun testFromBigEndianByteArrayToLong() { + + assertTrue { + val ubyteArray = ubyteArrayOf(0xA1U, 0xA2U, 0xB1U, 0xB2U, 0xC1U, 0xC2U, 0xD1U, 0xD2U).toTypedArray() + val expected = 0xA1A2B1B2C1C2D1D2U + val reconstructed = ubyteArray.fromBigEndianArrayToULong(); + reconstructed == expected + } + + } + + @Test + fun testFromLittleEndianByteArrayToLong() { + + assertTrue { + val ubyteArray = ubyteArrayOf(0xA1U, 0xA2U, 0xB1U, 0xB2U, 0xC1U, 0xC2U, 0xD1U, 0xD2U).toTypedArray() + val expected = 0xD2D1C2C1B2B1A2A1UL + val reconstructed = ubyteArray.fromLittleEndianArrayToULong(); + reconstructed == expected + } + + } +} \ No newline at end of file From 803288cbc4a2461ce8114d4fd672afda0d461828 Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Wed, 15 Jan 2020 21:54:30 +0100 Subject: [PATCH 07/29] Further progress --- .../kotlin/crypto/keyderivation/Argon2.kt | 170 ++++++++++++++---- .../com/ionspin/kotlin/crypto/util/Util.kt | 19 ++ 2 files changed, 152 insertions(+), 37 deletions(-) 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 300f47c..fd1c522 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 @@ -20,16 +20,18 @@ import com.ionspin.kotlin.crypto.hash.blake2b.Blake2b import com.ionspin.kotlin.crypto.util.* /** + * + * Further resources and examples of implementation: * https://tools.ietf.org/html/draft-irtf-cfrg-argon2-03 * https://en.wikipedia.org/wiki/Argon2 * https://www.cryptolux.org/images/0/0d/Argon2.pdf + * https://github.com/LoupVaillant/Monocypher/blob/master/src/monocypher.c + * https://github.com/jedisct1/libsodium/blob/master/src/libsodium/crypto_pwhash/argon2/argon2.c * * Created by Ugljesa Jovanovic * ugljesa.jovanovic@ionspin.com * on 08-Jan-2020 * - * - * */ @ExperimentalUnsignedTypes class Argon2 internal constructor( @@ -44,15 +46,28 @@ class Argon2 internal constructor( val associatedData: Array, val type: ArgonType ) { - enum class ArgonType { - Argon2i, Argon2d, Argon2id + enum class ArgonType(val typeId: Int) { + Argon2d(0), Argon2i(1), Argon2id(2) } + data class Argon2StreamGContext( + val block: Array, + val passNumber: Int, + val sliceNumber: Int, + val blockCount: UInt, + val numberOfIterations: UInt, + val counter: UInt, + val type: ArgonType + ) { + + } + + @ExperimentalStdlibApi companion object { - fun argonHash(input: Array, length: UInt): Array { + fun argonBlake2bArbitraryLenghtHash(input: Array, length: UInt): Array { if (length <= 64U) { return Blake2b.digest(length + input) } @@ -121,7 +136,7 @@ class Argon2 internal constructor( //based on Blake2b mixRound internal fun mixRound(input: Array): Array { - var v = input.chunked(4).map { it.fromLittleEndianArrayToULong() }.toTypedArray() + var v = input.chunked(8).map { it.fromLittleEndianArrayToULong() }.toTypedArray() v = mix(v, 0, 4, 8, 12) v = mix(v, 1, 5, 9, 13) v = mix(v, 2, 6, 10, 14) @@ -152,6 +167,74 @@ class Argon2 internal constructor( return v } + private fun computeIndexes( + block: Array>>, + pass: Long, + lane: Int, + column: Int, + blockCount: UInt, + iterationCount: UInt, + type: ArgonType, + laneCounter : Int + + ): Pair { + var counter = laneCounter + val sliceNumber = column / 4 + val sliceLength = blockCount / 4U + val (j1, j2) = when (type) { + ArgonType.Argon2i -> { + val firstPass = compressionFunctionG( + Array(1024) { 0U }, + pass.toULong().toLittleEndianUByteArray() + + lane.toULong().toLittleEndianUByteArray() + + sliceNumber.toULong().toLittleEndianUByteArray() + + blockCount.toULong().toLittleEndianUByteArray() + + iterationCount.toULong().toLittleEndianUByteArray() + + type.typeId.toULong().toLittleEndianUByteArray() + + counter.toUInt().toLittleEndianUByteArray() + + Array(968) { 0U } + ) + val secondPass = compressionFunctionG( + firstPass, + pass.toULong().toLittleEndianUByteArray() + + lane.toULong().toLittleEndianUByteArray() + + sliceNumber.toULong().toLittleEndianUByteArray() + + blockCount.toULong().toLittleEndianUByteArray() + + iterationCount.toULong().toLittleEndianUByteArray() + + type.typeId.toULong().toLittleEndianUByteArray() + + counter.toUInt().toLittleEndianUByteArray() + + Array(968) { 0U } + ) + Pair(firstPass, secondPass) + } + ArgonType.Argon2d -> { + Pair( + (block[laneCounter][column - 1].sliceArray(0..3).fromLittleEndianArrayToUInt()), + (block[laneCounter][column - 1].sliceArray(4..7).fromLittleEndianArrayToUInt()) + ) + } + ArgonType.Argon2id -> { + Pair(emptyArray(), emptyArray()) + } + } + + return Pair(1, 1) + + } + + fun populateSegment( + matrix: Array>>, + pass: Long, + lane: Int, + column: Int, + blockCount: UInt, + iterationCount: UInt, + type: ArgonType, + laneCounter : Int + ) { + //TODO handle segment by segment + } + internal fun derive( password: Array, @@ -177,48 +260,61 @@ class Argon2 internal constructor( val blockCount = (memorySize / (4U * parallelism)) * (4U * parallelism) // val columnCount = blockCount / parallelism - //Allocate memory as Array of parallelism rows and columnCount colums - val matrix = Array(parallelism.toInt()) { - Array(columnCount.toInt()) { - Array(1024) { 0U } + //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] = - argonHash(h0 + 0.toUInt().toLittleEndianUByteArray() + i.toUInt().toLittleEndianUByteArray(), 64U) - } - - //Compute B[i][1] - for (i in 0..parallelism.toInt()) { - matrix[i][0] = - argonHash(h0 + 1.toUInt().toLittleEndianUByteArray() + i.toUInt().toLittleEndianUByteArray(), 64U) - } - - for (i in 0..parallelism.toInt()) { - for (j in 1..columnCount.toInt()) { - //TODO i,j choosing based on type - val iPrim = -1 - val jPrim = -1 - matrix[i][j] = compressionFunctionG(matrix[i][j - 1], matrix[iPrim][jPrim]) + //Compute B[i][0] + for (i in 0..parallelism.toInt()) { + matrix[i][0] = + argonBlake2bArbitraryLenghtHash( + h0 + 0.toUInt().toLittleEndianUByteArray() + i.toUInt().toLittleEndianUByteArray(), + 64U + ) } - } - val result = matrix.foldIndexed(emptyArray()) { index, acc, arrayOfArrays -> - return if (acc.size == 0) { - acc + arrayOfArrays[columnCount.toInt() - 1] - } else { - acc.mapIndexed { index, it -> it xor arrayOfArrays[columnCount.toInt() - 1][index] }.toTypedArray() + //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, 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 -> + return if (acc.size == 0) { + acc + arrayOfArrays[columnCount.toInt() - 1] + } else { + acc.mapIndexed { index, it -> it xor arrayOfArrays[columnCount.toInt() - 1][index] } + .toTypedArray() + } + } + result } - return result + return allPasses.foldRight(emptyArray()) { arrayOfUBytes, acc -> acc xor arrayOfUBytes } //TODO placeholder } } -} \ No newline at end of file +} 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 8d8924b..795b180 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 @@ -140,6 +140,25 @@ fun Array.fromBigEndianArrayToULong() : ULong { return ulong } +@ExperimentalUnsignedTypes +fun Array.fromLittleEndianArrayToUInt() : ULong { + if (this.size > 4) { + throw RuntimeException("ore than 8 bytes in input, potential overflow") + } + var ulong = this.foldIndexed(0UL) { index, acc, uByte -> acc or (uByte.toULong() shl (index * 8))} + return ulong +} + + +@ExperimentalUnsignedTypes +fun Array.fromBigEndianArrayToUInt() : ULong { + if (this.size > 4) { + throw RuntimeException("ore than 8 bytes in input, potential overflow") + } + var ulong = this.foldIndexed(0UL) { index, acc, uByte -> acc or (uByte.toULong() shl (24 - (index * 8))) } + return ulong +} + @ExperimentalUnsignedTypes operator fun UInt.plus(other : Array) : Array { return this.toLittleEndianUByteArray() + other From 1e0a5b516e17349f41f0695bf9390b8aa220d98e Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Sun, 10 May 2020 11:11:09 +0200 Subject: [PATCH 08/29] Continuing Argon 2 work --- multiplatform-crypto/build.gradle.kts | 3 + .../kotlin/crypto/keyderivation/Argon2.kt | 87 +++++++++++-------- .../com/ionspin/kotlin/crypto/util/Util.kt | 14 +-- 3 files changed, 63 insertions(+), 41 deletions(-) diff --git a/multiplatform-crypto/build.gradle.kts b/multiplatform-crypto/build.gradle.kts index 9564389..56756ab 100644 --- a/multiplatform-crypto/build.gradle.kts +++ b/multiplatform-crypto/build.gradle.kts @@ -312,6 +312,9 @@ kotlin { val linuxArm64Test by getting { dependsOn(nativeTest) } + all { + languageSettings.enableLanguageFeature("InlineClasses") + } } 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 fd1c522..1098a01 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 @@ -13,12 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.ionspin.kotlin.crypto.keyderivation import com.ionspin.kotlin.crypto.hash.blake2b.Blake2b import com.ionspin.kotlin.crypto.util.* - /** * * Further resources and examples of implementation: @@ -63,9 +61,17 @@ class Argon2 internal constructor( } + @ExperimentalStdlibApi companion object { + fun Array.xor(target : Array, other : Array) { + if (this.size != other.size || this.size != target.size) { + throw RuntimeException("Invalid array sizes, this ${this.size}, other ${other.size}") + } + target.mapIndexed { index, _ -> this[index] xor other[index]} + } + fun argonBlake2bArbitraryLenghtHash(input: Array, length: UInt): Array { if (length <= 64U) { @@ -88,9 +94,11 @@ class Argon2 internal constructor( return concat } + fun compressionFunctionG(x: Array, y: Array): Array { - val r = Array(1024) { 0U } // view as 8x8 matrix of 16 byte registers - x.forEachIndexed { index, it -> r[index] = it xor y[index] } + val r = x xor y +// val r = Array(1024) { 0U } // view as 8x8 matrix of 16 byte registers +// x.forEachIndexed { index, it -> r[index] = it xor y[index] } // R = X xor Y val q = Array(1024) { 0U } val z = Array(1024) { 0U } // Do the argon/blake2b mixing on rows @@ -115,7 +123,7 @@ class Argon2 internal constructor( ) } // Z = Z xor R - r.forEachIndexed { index, it -> z[index] = it xor z[index] } + z.xor(z, r) return z } @@ -168,19 +176,23 @@ class Argon2 internal constructor( } private fun computeIndexes( - block: Array>>, - pass: Long, - lane: Int, - column: Int, - blockCount: UInt, - iterationCount: UInt, - type: ArgonType, - laneCounter : Int - + indexContext: IndexContext, + matrix : Array>> ): Pair { + val block = indexContext.indexMatrix + val parallelism = indexContext.parallelism + val pass = indexContext.pass + val lane = indexContext.lane + val column = indexContext.column + val blockCount = indexContext.blockCount + val iterationCount = indexContext.iterationCount + val type = indexContext.type + val laneCounter = indexContext.laneCounter + var counter = laneCounter val sliceNumber = column / 4 val sliceLength = blockCount / 4U + val (j1, j2) = when (type) { ArgonType.Argon2i -> { val firstPass = compressionFunctionG( @@ -205,36 +217,43 @@ class Argon2 internal constructor( counter.toUInt().toLittleEndianUByteArray() + Array(968) { 0U } ) - Pair(firstPass, secondPass) + Pair(1U, 1U) } ArgonType.Argon2d -> { Pair( - (block[laneCounter][column - 1].sliceArray(0..3).fromLittleEndianArrayToUInt()), - (block[laneCounter][column - 1].sliceArray(4..7).fromLittleEndianArrayToUInt()) + (matrix[laneCounter][column - 1].sliceArray(0..3).fromLittleEndianArrayToUInt()), + (matrix[laneCounter][column - 1].sliceArray(4..7).fromLittleEndianArrayToUInt()) ) } ArgonType.Argon2id -> { - Pair(emptyArray(), emptyArray()) + Pair(1U, 1U) } } + val l = if (pass == 0L && sliceNumber == 0) { + 2U + } else { + j2 % parallelism + } + +// val availableIndices = if () + + return Pair(1, 1) } - fun populateSegment( - matrix: Array>>, - pass: Long, - lane: Int, - column: Int, - blockCount: UInt, - iterationCount: UInt, - type: ArgonType, - laneCounter : Int - ) { - //TODO handle segment by segment - } - + data class IndexContext( + val indexMatrix: Array, + val parallelism: UInt, + val pass: Long, + val lane: Int, + val column: Int, + val blockCount: UInt, + val iterationCount: UInt, + val type: ArgonType, + val laneCounter: Int + ) internal fun derive( password: Array, @@ -257,11 +276,11 @@ class Argon2 internal constructor( associatedData.size.toUInt().toLittleEndianUByteArray() + associatedData ) - val blockCount = (memorySize / (4U * parallelism)) * (4U * parallelism) // + val blockCount = (memorySize / (4U * parallelism)) * (4U * parallelism) // TODO hmmm val columnCount = blockCount / parallelism //TODO pass handling - val allPasses = (0 .. numberOfIterations.toLong()).map { pass -> + 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()) { @@ -291,7 +310,7 @@ class Argon2 internal constructor( for (j in 1..columnCount.toInt()) { val counter = 0 //TODO handle counter - computeIndexes(matrix, pass, i, j, blockCount, numberOfIterations, type) + 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]) 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 795b180..e936bc8 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 @@ -70,7 +70,7 @@ infix fun Array.xor(other : Array) : Array { if (this.size != other.size) { throw RuntimeException("Operands of different sizes are not supported yet") } - return this.copyOf().mapIndexed { index, it -> it xor other[index] }.toTypedArray() + return Array(this.size) { this[it] xor other [it]} } @ExperimentalUnsignedTypes @@ -141,22 +141,22 @@ fun Array.fromBigEndianArrayToULong() : ULong { } @ExperimentalUnsignedTypes -fun Array.fromLittleEndianArrayToUInt() : ULong { +fun Array.fromLittleEndianArrayToUInt() : UInt { if (this.size > 4) { throw RuntimeException("ore than 8 bytes in input, potential overflow") } - var ulong = this.foldIndexed(0UL) { index, acc, uByte -> acc or (uByte.toULong() shl (index * 8))} - return ulong + var uint = this.foldIndexed(0U) { index, acc, uByte -> acc or (uByte.toUInt() shl (index * 8))} + return uint } @ExperimentalUnsignedTypes -fun Array.fromBigEndianArrayToUInt() : ULong { +fun Array.fromBigEndianArrayToUInt() : UInt { if (this.size > 4) { throw RuntimeException("ore than 8 bytes in input, potential overflow") } - var ulong = this.foldIndexed(0UL) { index, acc, uByte -> acc or (uByte.toULong() shl (24 - (index * 8))) } - return ulong + var uint = this.foldIndexed(0U) { index, acc, uByte -> acc or (uByte.toUInt() shl (24 - (index * 8))) } + return uint } @ExperimentalUnsignedTypes From 4cc7c7e92af0185ba47ee2ec2e27b38e0d2611f8 Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Tue, 12 May 2020 21:09:05 +0200 Subject: [PATCH 09/29] 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 From 21685191e79bc9615c3bae4192f0ecb2d4771347 Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Tue, 12 May 2020 21:57:04 +0200 Subject: [PATCH 10/29] Fix arbitrary length hash function --- .../com/ionspin/kotlin/crypto/keyderivation/Argon2.kt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) 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 8f5d11d..143fac1 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 @@ -86,8 +86,8 @@ class Argon2 internal constructor( for (i in 1 until numberOf64ByteBlocks) { v[i] = Blake2b.digest(v[i - 1]) } - val remainingPartOfInput = input.copyOfRange(length.toInt() - numberOf64ByteBlocks * 32, input.size) - val vLast = Blake2b.digest(remainingPartOfInput, hashLength = remainingPartOfInput.size) + val remainingPartOfInput = length.toInt() - numberOf64ByteBlocks * 32 + val vLast = Blake2b.digest(v[numberOf64ByteBlocks - 1], hashLength = remainingPartOfInput) val concat = (v.map { it.copyOfRange(0, 32) }) .plus(listOf(vLast)) @@ -328,6 +328,7 @@ class Argon2 internal constructor( type: ArgonType ): Array { + println("H0 Input") val toDigest = parallelism.toLittleEndianUByteArray() + tagLength.toLittleEndianUByteArray() + memorySize.toLittleEndianUByteArray() + numberOfIterations.toLittleEndianUByteArray() + versionNumber.toLittleEndianUByteArray() + type.typeId.toUInt().toLittleEndianUByteArray() + password.size.toUInt().toLittleEndianUByteArray() + password + @@ -335,6 +336,7 @@ class Argon2 internal constructor( key.size.toUInt().toLittleEndianUByteArray() + key + associatedData.size.toUInt().toLittleEndianUByteArray() + associatedData toDigest.hexColumsPrint(16) + println("Marker H0 Input end") val h0 = Blake2b.digest( parallelism.toLittleEndianUByteArray() + tagLength.toLittleEndianUByteArray() + memorySize.toLittleEndianUByteArray() + numberOfIterations.toLittleEndianUByteArray() + versionNumber.toLittleEndianUByteArray() + type.typeId.toUInt().toLittleEndianUByteArray()+ @@ -345,6 +347,7 @@ class Argon2 internal constructor( ) h0.hexColumsPrint(8) + println("Marker H0") val blockCount = (memorySize / (4U * parallelism)) * (4U * parallelism) val columnCount = (blockCount / parallelism).toInt() @@ -367,6 +370,8 @@ class Argon2 internal constructor( h0 + 0.toUInt().toLittleEndianUByteArray() + i.toUInt().toLittleEndianUByteArray(), 1024U ) + matrix[i][0].hexColumsPrint(16) + println("Marker, matrix [$i][0]") } //Compute B[i][1] @@ -376,6 +381,8 @@ class Argon2 internal constructor( h0 + 1.toUInt().toLittleEndianUByteArray() + i.toUInt().toLittleEndianUByteArray(), 1024U ) + matrix[i][1].hexColumsPrint(16) + println("Marker, matrix [$i][1]") } //Compute B[i][j] From 297a2d496917e33ddf68bbb401922f717e9d8e6d Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Wed, 13 May 2020 22:00:41 +0200 Subject: [PATCH 11/29] Further step by step build --- .../kotlin/crypto/keyderivation/Argon2.kt | 287 ++++++++++++++---- .../com/ionspin/kotlin/crypto/util/Util.kt | 2 +- 2 files changed, 222 insertions(+), 67 deletions(-) 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 143fac1..f656ea0 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 @@ -18,6 +18,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.* + /** * * Further resources and examples of implementation: @@ -63,15 +64,14 @@ class Argon2 internal constructor( } - @ExperimentalStdlibApi companion object { - fun Array.xor(target : Array, other : Array) { + fun Array.xor(target: Array, other: Array) { if (this.size != other.size || this.size != target.size) { throw RuntimeException("Invalid array sizes, this ${this.size}, other ${other.size}") } - target.mapIndexed { index, _ -> this[index] xor other[index]} + target.mapIndexed { index, _ -> this[index] xor other[index] } } @@ -99,6 +99,7 @@ class Argon2 internal constructor( fun compressionFunctionG(x: Array, y: Array): Array { val r = x xor y + // Xor works in first pass! // val r = Array(1024) { 0U } // view as 8x8 matrix of 16 byte registers // x.forEachIndexed { index, it -> r[index] = it xor y[index] } // R = X xor Y val q = Array(1024) { 0U } @@ -111,7 +112,7 @@ class Argon2 internal constructor( .map { it.toLittleEndianUByteArray() } .flatMap { it.asIterable() } .toTypedArray() - .copyInto(q, startOfRow, endOfRow) + .copyInto(q, startOfRow) } // Do the argon/blake2b mixing on columns for (i in 0..7) { @@ -179,7 +180,7 @@ class Argon2 internal constructor( private fun computeIndexes( indexContext: IndexContext, - matrix : Array>> + matrix: Array>> ): Pair { val block = indexContext.indexMatrix val parallelism = indexContext.parallelism @@ -258,11 +259,20 @@ 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 { + 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] + matrix[lane][columnCount - 1] //Get last block in the SAME lane } else { matrix[lane][column - 1] } @@ -296,25 +306,79 @@ class Argon2 internal constructor( // 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) { + val referenceAreaSize = if (iteration == 0) { if (slice == 0) { //All indices except the previous - val from0Until = column - 1 + column - 1 } else { if (lane == l) { //Same lane - val from0Until = slice * (columnCount / 4) + column - 1 + column - 1 } else { - val from0Until = slice * (columnCount / 4) + if(column == 0) { -1 } else { 0 } + slice * (columnCount / 4) + if (column % (columnCount / 4) == 0) { // Check if column is first block of the SEGMENT + -1 + } else { + 0 + } + } + } + } else { + if (lane == l) { + columnCount - (columnCount / 4) + column - 1 + } else { + columnCount - (columnCount / 4) + if (column == 0) { + -1 + } else { + 0 } } } - val availableIndicesSet = + val x = (j1.toULong() * j1) shr 32 + val y = (referenceAreaSize.toULong() * x) shr 32 + val z = referenceAreaSize.toULong() - 1U - y - return Pair(l, j2.toInt()) + val startPosition = if (iteration == 0) { + 0 + } else { + if (slice == 3) { + 0 + } else { + (slice + 1) * (columnCount / 4) //TODO replace all of these with segment length when consolidating variables + } + } + + val absolutePosition = (startPosition + z.toInt()) % columnCount + + return Pair(l, absolutePosition) } + data class ArgonContext( + val password: Array, + val salt: Array, + val parallelism: UInt, + val tagLength: UInt, + val memorySize: UInt, + val numberOfIterations: UInt, + val versionNumber: UInt, + val key: Array, + val associatedData: Array, + val type: ArgonType + ) + + data class ArgonInternalContext( + val matrix: Array>>, + val blockCount : UInt, + val columnCount : Int, + val segmentLength: Int + ) + + data class SegmentPosition( + val iteration: Int, + val lane: Int, + val slice: Int + ) + internal fun derive( password: Array, salt: Array, @@ -327,19 +391,34 @@ class Argon2 internal constructor( associatedData: Array, type: ArgonType ): Array { + val argonContext = ArgonContext( + password = password, + salt = salt, + parallelism = parallelism, + tagLength = tagLength, + memorySize = memorySize, + numberOfIterations = numberOfIterations, + versionNumber = versionNumber, + key = key, + associatedData = associatedData, + type = type + ) println("H0 Input") - 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 + 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) println("Marker H0 Input end") val h0 = Blake2b.digest( parallelism.toLittleEndianUByteArray() + tagLength.toLittleEndianUByteArray() + memorySize.toLittleEndianUByteArray() + - numberOfIterations.toLittleEndianUByteArray() + versionNumber.toLittleEndianUByteArray() + type.typeId.toUInt().toLittleEndianUByteArray()+ + numberOfIterations.toLittleEndianUByteArray() + versionNumber.toLittleEndianUByteArray() + type.typeId.toUInt() + .toLittleEndianUByteArray() + password.size.toUInt().toLittleEndianUByteArray() + password + salt.size.toUInt().toLittleEndianUByteArray() + salt + key.size.toUInt().toLittleEndianUByteArray() + key + @@ -370,6 +449,7 @@ class Argon2 internal constructor( h0 + 0.toUInt().toLittleEndianUByteArray() + i.toUInt().toLittleEndianUByteArray(), 1024U ) + println("Start, matrix [$i][0]") matrix[i][0].hexColumsPrint(16) println("Marker, matrix [$i][0]") } @@ -381,69 +461,144 @@ class Argon2 internal constructor( h0 + 1.toUInt().toLittleEndianUByteArray() + i.toUInt().toLittleEndianUByteArray(), 1024U ) + println("Start, matrix [$i][1]") matrix[i][1].hexColumsPrint(16) println("Marker, matrix [$i][1]") } - //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]) + // ---- Good until here at least ---- + val argonInternalContext = ArgonInternalContext( + matrix, blockCount, columnCount, segmentLength + ) + singleThreaded(argonContext, argonInternalContext) + + return emptyArray() + } + + fun singleThreaded(argonContext: ArgonContext, argonInternalContext: ArgonInternalContext ) { + for (iteration in 0 until argonContext.numberOfIterations.toInt()) { + for (slice in 0 until 4) { + for (lane in 0 until argonContext.parallelism.toInt()) { + println("Processing segment I: $iteration, S: $slice, L: $lane") + val segmentPosition = SegmentPosition(iteration, lane, slice) + processSegment(argonContext, argonInternalContext, segmentPosition) + } } } - //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 + fun processSegment(argonContext: ArgonContext, argonInternalContext: ArgonInternalContext, segmentPosition: SegmentPosition) { + val password = argonContext.password + val salt = argonContext.salt + val parallelism = argonContext.parallelism + val tagLength = argonContext.tagLength + val memorySize = argonContext.memorySize + val numberOfIterations = argonContext.numberOfIterations + val versionNumber = argonContext.versionNumber + val key = argonContext.key + val associatedData = argonContext.associatedData + val type = argonContext.type + + val matrix = argonInternalContext.matrix + val blockCount = argonInternalContext.blockCount + val columnCount = argonInternalContext.columnCount + val segmentLength = argonInternalContext.segmentLength + + val iteration = segmentPosition.iteration + val lane = segmentPosition.lane + val slice = segmentPosition.slice + + + if (iteration == 0) { + //Compute B[i][j] + //Using B[i][j] = G(B[i][j], B[l][z]) where l and z are provided bu computeIndexes + //Because this is iteration 0 we have B[i][0] and B[i][1] already filled, so whenever we + //are processing first segment we skip these two blocks + if (slice == 0) { + for (column in 2..(slice * segmentLength)) { + val (l, z) = computeIndexNew(matrix, lane, column, columnCount, parallelism.toInt(), 0, 0, type) + println("Calling compress for I: $iteration S: $slice Lane: $lane Column: $column with l: $l z: $z") + matrix[lane][column] = compressionFunctionG(matrix[lane][column - 1], matrix[l][z]) + } + } else { + for (column in (slice * segmentLength)..((slice + 1) * segmentLength)) { + val (l, z) = computeIndexNew(matrix, lane, column, columnCount, parallelism.toInt(), iteration, slice, type) + println("Calling compress for I: $iteration S: $slice Lane: $lane Column: $column with l: $l z: $z") + matrix[lane][column] = compressionFunctionG(matrix[lane][column - 1], matrix[l][z]) + } + } + } else { + val (l, z) = computeIndexNew(matrix, lane, 0, columnCount, parallelism.toInt(), 0, 0, type) + matrix[lane][0] = compressionFunctionG(matrix[lane][columnCount - 1], matrix[l][z]) + for (column in 1..(slice * segmentLength)) { + val (l, z) = computeIndexNew(matrix, lane, column, columnCount, parallelism.toInt(), 0, 0, type) + println("Calling compress for I: $iteration S: $slice Lane: $lane Column: $column with l: $l z: $z") + matrix[lane][column] = compressionFunctionG(matrix[lane][column - 1], 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 (l, z) = computeIndexNew( +// matrix, +// i, +// j, +// columnCount, +// parallelism.toInt(), +// iteration, +// iteration / segmentLength, +// type // ) - - 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]) - } - - } - } +// 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]) +// } +// +// } +// } +// +// +// val result = matrix.foldIndexed(emptyArray()) { lane, acc, laneArray -> +// return if (acc.size == 0) { +// acc + laneArray[columnCount - 1] // add last element in first lane to the accumulator +// } else { +// // 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() +// } +// } +// result +// } - val result = matrix.foldIndexed(emptyArray()) { lane, acc, laneArray -> - return if (acc.size == 0) { - acc + laneArray[columnCount - 1] // add last element in first lane to the accumulator - } else { - // 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() - } - } - result - } - - - - return remainingIterations.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 + password, + salt, + parallelism, + tagLength, + memorySize, + numberOfIterations, + versionNumber, + key, + associatedData, + type ) } 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 ebc0085..5c148ef 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 @@ -70,7 +70,7 @@ infix fun Array.xor(other : Array) : Array { if (this.size != other.size) { throw RuntimeException("Operands of different sizes are not supported yet") } - return Array(this.size) { this[it] xor other [it]} + return Array(this.size) { this[it] xor other[it] } } @ExperimentalUnsignedTypes From 9b800e34bc0e9ef0c79fd986b878ad17f21cdd8d Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Thu, 14 May 2020 21:31:14 +0200 Subject: [PATCH 12/29] Fixed mixing columns --- .../kotlin/crypto/keyderivation/Argon2.kt | 88 ++++++++++++++----- 1 file changed, 64 insertions(+), 24 deletions(-) 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 f656ea0..a99a6cb 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 @@ -97,8 +97,15 @@ class Argon2 internal constructor( } - fun compressionFunctionG(x: Array, y: Array): Array { + fun compressionFunctionG( + x: Array, + y: Array, + currentBlock: Array, + xorWithCurrentBlock: Boolean + ): Array { val r = x xor y + println("R = X xor Y") + r.hexColumsPrint(16) // Xor works in first pass! // val r = Array(1024) { 0U } // view as 8x8 matrix of 16 byte registers // x.forEachIndexed { index, it -> r[index] = it xor y[index] } // R = X xor Y @@ -106,14 +113,21 @@ class Argon2 internal constructor( val z = Array(1024) { 0U } // Do the argon/blake2b mixing on rows for (i in 0..7) { + println("Q round $i") + q.hexColumsPrint(16) val startOfRow = (i * 8 * 16) val endOfRow = startOfRow + (8 * 16) - mixRound(r.copyOfRange(startOfRow, endOfRow)) + val rowToMix = r.copyOfRange(startOfRow, endOfRow) + println("Mixing row:") + rowToMix.hexColumsPrint(16) + mixRound(rowToMix) .map { it.toLittleEndianUByteArray() } .flatMap { it.asIterable() } .toTypedArray() .copyInto(q, startOfRow) } + println("---- Q -----") + q.hexColumsPrint(16) // Do the argon/blake2b mixing on columns for (i in 0..7) { copyIntoGBlockColumn( @@ -125,9 +139,18 @@ class Argon2 internal constructor( .toTypedArray() ) } - // Z = Z xor R - z.xor(z, r) - return z + println("---- Z -----") + z.hexColumsPrint(16) + val final = if (xorWithCurrentBlock) { + println("Z xor R xoe CURRENT") + (z xor r) xor ((x xor y) xor currentBlock) + } else { + println("Z xor R") + z xor r + } + + final.hexColumsPrint(16) + return final } private fun extractColumnFromGBlock(gBlock: Array, columnPosition: Int): Array { @@ -148,6 +171,7 @@ class Argon2 internal constructor( //based on Blake2b mixRound internal fun mixRound(input: Array): Array { var v = input.chunked(8).map { it.fromLittleEndianArrayToULong() }.toTypedArray() + v.forEach { println(it.toString(16)) } v = mix(v, 0, 4, 8, 12) v = mix(v, 1, 5, 9, 13) v = mix(v, 2, 6, 10, 14) @@ -167,13 +191,13 @@ class Argon2 internal constructor( //Based on Blake2b mix private fun mix(v: Array, a: Int, b: Int, c: Int, d: Int): Array { - v[a] = (v[a] + v[b] * 2U * a.toUInt() * b.toUInt()) + v[a] = (v[a] + v[b] + 2U * (v[a] and 0xFFFFFFFFUL) * (v[b] and 0xFFFFFFFFUL)) v[d] = (v[d] xor v[a]) rotateRight R1 - v[c] = (v[c] + v[d] * 2U * c.toUInt() * d.toUInt()) + v[c] = (v[c] + v[d] + 2U * (v[c] and 0xFFFFFFFFUL) * (v[d] and 0xFFFFFFFFUL)) v[b] = (v[b] xor v[c]) rotateRight R2 - v[a] = (v[a] + v[b] * 2U * a.toUInt() * b.toUInt()) + v[a] = (v[a] + v[b] + 2U * (v[a] and 0xFFFFFFFFUL) * (v[b] and 0xFFFFFFFFUL)) v[d] = (v[d] xor v[a]) rotateRight R3 - v[c] = (v[c] + v[d] * 2U * c.toUInt() * d.toUInt()) + v[c] = (v[c] + v[d] + 2U * (v[c] and 0xFFFFFFFFUL) * (v[d] and 0xFFFFFFFFUL)) v[b] = (v[b] xor v[c]) rotateRight R4 return v } @@ -207,7 +231,9 @@ class Argon2 internal constructor( iterationCount.toULong().toLittleEndianUByteArray() + type.typeId.toULong().toLittleEndianUByteArray() + counter.toUInt().toLittleEndianUByteArray() + - Array(968) { 0U } + Array(968) { 0U }, + emptyArray(), + false ) val secondPass = compressionFunctionG( firstPass, @@ -218,7 +244,9 @@ class Argon2 internal constructor( iterationCount.toULong().toLittleEndianUByteArray() + type.typeId.toULong().toLittleEndianUByteArray() + counter.toUInt().toLittleEndianUByteArray() + - Array(968) { 0U } + Array(968) { 0U }, + emptyArray(), + false ) secondPass.hexColumsPrint() Pair(1U, 1U) @@ -368,8 +396,8 @@ class Argon2 internal constructor( data class ArgonInternalContext( val matrix: Array>>, - val blockCount : UInt, - val columnCount : Int, + val blockCount: UInt, + val columnCount: Int, val segmentLength: Int ) @@ -475,7 +503,7 @@ class Argon2 internal constructor( return emptyArray() } - fun singleThreaded(argonContext: ArgonContext, argonInternalContext: ArgonInternalContext ) { + fun singleThreaded(argonContext: ArgonContext, argonInternalContext: ArgonInternalContext) { for (iteration in 0 until argonContext.numberOfIterations.toInt()) { for (slice in 0 until 4) { for (lane in 0 until argonContext.parallelism.toInt()) { @@ -487,7 +515,11 @@ class Argon2 internal constructor( } } - fun processSegment(argonContext: ArgonContext, argonInternalContext: ArgonInternalContext, segmentPosition: SegmentPosition) { + fun processSegment( + argonContext: ArgonContext, + argonInternalContext: ArgonInternalContext, + segmentPosition: SegmentPosition + ) { val password = argonContext.password val salt = argonContext.salt val parallelism = argonContext.parallelism @@ -518,29 +550,39 @@ class Argon2 internal constructor( for (column in 2..(slice * segmentLength)) { val (l, z) = computeIndexNew(matrix, lane, column, columnCount, parallelism.toInt(), 0, 0, type) println("Calling compress for I: $iteration S: $slice Lane: $lane Column: $column with l: $l z: $z") - matrix[lane][column] = compressionFunctionG(matrix[lane][column - 1], matrix[l][z]) + matrix[lane][column] = + compressionFunctionG(matrix[lane][column - 1], matrix[l][z], matrix[lane][column], false) } } else { for (column in (slice * segmentLength)..((slice + 1) * segmentLength)) { - val (l, z) = computeIndexNew(matrix, lane, column, columnCount, parallelism.toInt(), iteration, slice, type) + val (l, z) = computeIndexNew( + matrix, + lane, + column, + columnCount, + parallelism.toInt(), + iteration, + slice, + type + ) println("Calling compress for I: $iteration S: $slice Lane: $lane Column: $column with l: $l z: $z") - matrix[lane][column] = compressionFunctionG(matrix[lane][column - 1], matrix[l][z]) + matrix[lane][column] = + compressionFunctionG(matrix[lane][column - 1], matrix[l][z], matrix[lane][column], false) } } } else { val (l, z) = computeIndexNew(matrix, lane, 0, columnCount, parallelism.toInt(), 0, 0, type) - matrix[lane][0] = compressionFunctionG(matrix[lane][columnCount - 1], matrix[l][z]) + matrix[lane][0] = compressionFunctionG(matrix[lane][columnCount - 1], matrix[l][z], matrix[lane][columnCount], true) for (column in 1..(slice * segmentLength)) { val (l, z) = computeIndexNew(matrix, lane, column, columnCount, parallelism.toInt(), 0, 0, type) println("Calling compress for I: $iteration S: $slice Lane: $lane Column: $column with l: $l z: $z") - matrix[lane][column] = compressionFunctionG(matrix[lane][column - 1], matrix[l][z]) + matrix[lane][column] = + compressionFunctionG(matrix[lane][column - 1], matrix[l][z], matrix[lane][column], true) } } - - // //Remaining iteration // val remainingIterations = (1..numberOfIterations.toInt()).map { iteration -> // @@ -579,14 +621,12 @@ class Argon2 internal constructor( // } - // return remainingIterations.foldRight(emptyArray()) { arrayOfUBytes, acc -> acc xor arrayOfUBytes } //TODO placeholder } } - fun calculate(): Array { return derive( password, From 3519d2240fc01d26567ca021cb43be068a88befa Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Thu, 14 May 2020 22:48:19 +0200 Subject: [PATCH 13/29] Fixed row mixing --- .../kotlin/crypto/keyderivation/Argon2.kt | 16 +++++++++------- .../com/ionspin/kotlin/crypto/util/Util.kt | 4 ++-- 2 files changed, 11 insertions(+), 9 deletions(-) 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 a99a6cb..8c943f7 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 @@ -113,13 +113,9 @@ class Argon2 internal constructor( val z = Array(1024) { 0U } // Do the argon/blake2b mixing on rows for (i in 0..7) { - println("Q round $i") - q.hexColumsPrint(16) val startOfRow = (i * 8 * 16) val endOfRow = startOfRow + (8 * 16) val rowToMix = r.copyOfRange(startOfRow, endOfRow) - println("Mixing row:") - rowToMix.hexColumsPrint(16) mixRound(rowToMix) .map { it.toLittleEndianUByteArray() } .flatMap { it.asIterable() } @@ -130,6 +126,7 @@ class Argon2 internal constructor( q.hexColumsPrint(16) // Do the argon/blake2b mixing on columns for (i in 0..7) { + println("Z round ${i}") copyIntoGBlockColumn( z, i, @@ -142,7 +139,7 @@ class Argon2 internal constructor( println("---- Z -----") z.hexColumsPrint(16) val final = if (xorWithCurrentBlock) { - println("Z xor R xoe CURRENT") + println("Z xor R xor CURRENT") (z xor r) xor ((x xor y) xor currentBlock) } else { println("Z xor R") @@ -156,14 +153,18 @@ class Argon2 internal constructor( private fun extractColumnFromGBlock(gBlock: Array, columnPosition: Int): Array { val result = Array(128) { 0U } for (i in 0..7) { - result[i] = gBlock[i * 8 + columnPosition] + gBlock.copyOfRange(i * 128 + (columnPosition * 16), i * 128 + (columnPosition * 16) + 16).copyInto(result, i * 16) } return result } private fun copyIntoGBlockColumn(gBlock: Array, columnPosition: Int, columnData: Array) { for (i in 0..7) { - gBlock[i * 8 + columnPosition] = columnData[i] + println("Mixed column data ${i}") + val column = columnData.copyOfRange(i * 16, i * 16 + 16) + column.hexColumsPrint(16) + column.copyInto(gBlock, i * 128 + columnPosition * 16) +// gBlock[i * 8 + columnPosition] = columnData[i] } } @@ -180,6 +181,7 @@ class Argon2 internal constructor( v = mix(v, 1, 6, 11, 12) v = mix(v, 2, 7, 8, 13) v = mix(v, 3, 4, 9, 14) + v.hexColumsPrint(2) return v } 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 5c148ef..129eef0 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 @@ -31,8 +31,8 @@ fun Array.hexColumsPrint(chunk : Int = 16) { printout.forEach { println(it.joinToString(separator = " ") { it.toUpperCase() }) } } -fun Array.hexColumsPrint() { - val printout = this.map { it.toString(16) }.chunked(3) +fun Array.hexColumsPrint(chunk: Int = 3) { + val printout = this.map { it.toString(16) }.chunked(chunk) printout.forEach { println(it.joinToString(separator = " ") { it.toUpperCase() }) } } From 6c92936c5d3080372fab4c293cad2398d25d22d2 Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Thu, 14 May 2020 23:12:22 +0200 Subject: [PATCH 14/29] Bounds fixes --- .../kotlin/crypto/keyderivation/Argon2.kt | 53 +++++++++---------- 1 file changed, 24 insertions(+), 29 deletions(-) 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 8c943f7..0668596 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 @@ -104,9 +104,8 @@ class Argon2 internal constructor( xorWithCurrentBlock: Boolean ): Array { val r = x xor y - println("R = X xor Y") - r.hexColumsPrint(16) - // Xor works in first pass! +// println("R = X xor Y") +// r.hexColumsPrint(16) // val r = Array(1024) { 0U } // view as 8x8 matrix of 16 byte registers // x.forEachIndexed { index, it -> r[index] = it xor y[index] } // R = X xor Y val q = Array(1024) { 0U } @@ -122,11 +121,10 @@ class Argon2 internal constructor( .toTypedArray() .copyInto(q, startOfRow) } - println("---- Q -----") - q.hexColumsPrint(16) +// println("---- Q -----") +// q.hexColumsPrint(16) // Do the argon/blake2b mixing on columns for (i in 0..7) { - println("Z round ${i}") copyIntoGBlockColumn( z, i, @@ -136,17 +134,17 @@ class Argon2 internal constructor( .toTypedArray() ) } - println("---- Z -----") - z.hexColumsPrint(16) +// println("---- Z -----") +// z.hexColumsPrint(16) val final = if (xorWithCurrentBlock) { - println("Z xor R xor CURRENT") +// println("Z xor R xor CURRENT") (z xor r) xor ((x xor y) xor currentBlock) } else { - println("Z xor R") +// println("Z xor R") z xor r } - final.hexColumsPrint(16) +// final.hexColumsPrint(16) return final } @@ -160,11 +158,8 @@ class Argon2 internal constructor( private fun copyIntoGBlockColumn(gBlock: Array, columnPosition: Int, columnData: Array) { for (i in 0..7) { - println("Mixed column data ${i}") val column = columnData.copyOfRange(i * 16, i * 16 + 16) - column.hexColumsPrint(16) column.copyInto(gBlock, i * 128 + columnPosition * 16) -// gBlock[i * 8 + columnPosition] = columnData[i] } } @@ -172,7 +167,6 @@ class Argon2 internal constructor( //based on Blake2b mixRound internal fun mixRound(input: Array): Array { var v = input.chunked(8).map { it.fromLittleEndianArrayToULong() }.toTypedArray() - v.forEach { println(it.toString(16)) } v = mix(v, 0, 4, 8, 12) v = mix(v, 1, 5, 9, 13) v = mix(v, 2, 6, 10, 14) @@ -181,7 +175,6 @@ class Argon2 internal constructor( v = mix(v, 1, 6, 11, 12) v = mix(v, 2, 7, 8, 13) v = mix(v, 3, 4, 9, 14) - v.hexColumsPrint(2) return v } @@ -319,8 +312,8 @@ class Argon2 internal constructor( val l = if (iteration == 0 && slice == 0) { lane } else { - val lol = (j2.toBigInteger() % parallelism).intValue() - lol + (j2.toBigInteger() % parallelism).intValue() + } //From Argon 2 2020 draft @@ -377,7 +370,9 @@ class Argon2 internal constructor( (slice + 1) * (columnCount / 4) //TODO replace all of these with segment length when consolidating variables } } - + if ( (startPosition + z.toInt()) % columnCount == -1) { + println("Debug") + } val absolutePosition = (startPosition + z.toInt()) % columnCount return Pair(l, absolutePosition) @@ -479,9 +474,9 @@ class Argon2 internal constructor( h0 + 0.toUInt().toLittleEndianUByteArray() + i.toUInt().toLittleEndianUByteArray(), 1024U ) - println("Start, matrix [$i][0]") - matrix[i][0].hexColumsPrint(16) - println("Marker, matrix [$i][0]") +// println("Start, matrix [$i][0]") +// matrix[i][0].hexColumsPrint(16) +// println("Marker, matrix [$i][0]") } //Compute B[i][1] @@ -491,9 +486,9 @@ class Argon2 internal constructor( h0 + 1.toUInt().toLittleEndianUByteArray() + i.toUInt().toLittleEndianUByteArray(), 1024U ) - println("Start, matrix [$i][1]") - matrix[i][1].hexColumsPrint(16) - println("Marker, matrix [$i][1]") +// println("Start, matrix [$i][1]") +// matrix[i][1].hexColumsPrint(16) +// println("Marker, matrix [$i][1]") } // ---- Good until here at least ---- @@ -556,7 +551,7 @@ class Argon2 internal constructor( compressionFunctionG(matrix[lane][column - 1], matrix[l][z], matrix[lane][column], false) } } else { - for (column in (slice * segmentLength)..((slice + 1) * segmentLength)) { + for (column in (slice * segmentLength) until ((slice + 1) * segmentLength)) { val (l, z) = computeIndexNew( matrix, lane, @@ -573,10 +568,10 @@ class Argon2 internal constructor( } } } else { - val (l, z) = computeIndexNew(matrix, lane, 0, columnCount, parallelism.toInt(), 0, 0, type) - matrix[lane][0] = compressionFunctionG(matrix[lane][columnCount - 1], matrix[l][z], matrix[lane][columnCount], true) + val (l, z) = computeIndexNew(matrix, lane, 0, columnCount, parallelism.toInt(), iteration, slice, type) + matrix[lane][0] = compressionFunctionG(matrix[lane][columnCount - 1], matrix[l][z], matrix[lane][0], true) for (column in 1..(slice * segmentLength)) { - val (l, z) = computeIndexNew(matrix, lane, column, columnCount, parallelism.toInt(), 0, 0, type) + val (l, z) = computeIndexNew(matrix, lane, column, columnCount, parallelism.toInt(), iteration, slice, type) println("Calling compress for I: $iteration S: $slice Lane: $lane Column: $column with l: $l z: $z") matrix[lane][column] = compressionFunctionG(matrix[lane][column - 1], matrix[l][z], matrix[lane][column], true) From d92db320c30413f225511522f5e1917672f6d5ce Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Fri, 15 May 2020 22:24:09 +0200 Subject: [PATCH 15/29] Bumpp kotlin to 1.3.72, working first iteration --- buildSrc/src/main/kotlin/Deps.kt | 2 +- .../com/ionspin/kotlin/crypto/keyderivation/Argon2.kt | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/Deps.kt b/buildSrc/src/main/kotlin/Deps.kt index a1d1631..1e43a78 100644 --- a/buildSrc/src/main/kotlin/Deps.kt +++ b/buildSrc/src/main/kotlin/Deps.kt @@ -16,7 +16,7 @@ object Versions { val kotlinCoroutines = "1.3.3" - val kotlin = "1.3.61" + val kotlin = "1.3.72" val kotlinSerialization = "0.11.1" val nodePlugin = "1.3.0" val dokkaPlugin = "0.9.18" 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 0668596..e5a7948 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 @@ -509,6 +509,9 @@ class Argon2 internal constructor( processSegment(argonContext, argonInternalContext, segmentPosition) } } + println("Done with $iteration") + argonInternalContext.matrix[0][0].slice(0 .. 7).toTypedArray().hexColumsPrint(8) + argonInternalContext.matrix[argonContext.parallelism.toInt() - 1][argonInternalContext.columnCount - 1].slice(1016 .. 1023).toTypedArray().hexColumsPrint(8) } } @@ -549,7 +552,9 @@ class Argon2 internal constructor( println("Calling compress for I: $iteration S: $slice Lane: $lane Column: $column with l: $l z: $z") matrix[lane][column] = compressionFunctionG(matrix[lane][column - 1], matrix[l][z], matrix[lane][column], false) +// matrix[lane][column].hexColumsPrint(16) } + } else { for (column in (slice * segmentLength) until ((slice + 1) * segmentLength)) { val (l, z) = computeIndexNew( @@ -565,6 +570,8 @@ class Argon2 internal constructor( println("Calling compress for I: $iteration S: $slice Lane: $lane Column: $column with l: $l z: $z") matrix[lane][column] = compressionFunctionG(matrix[lane][column - 1], matrix[l][z], matrix[lane][column], false) +// matrix[lane][column].hexColumsPrint(16) + println("debug") } } } else { @@ -575,11 +582,14 @@ class Argon2 internal constructor( println("Calling compress for I: $iteration S: $slice Lane: $lane Column: $column with l: $l z: $z") matrix[lane][column] = compressionFunctionG(matrix[lane][column - 1], matrix[l][z], matrix[lane][column], true) +// matrix[lane][column].hexColumsPrint(16) } } + + // //Remaining iteration // val remainingIterations = (1..numberOfIterations.toInt()).map { iteration -> // From 1904e2b9f2c8667f4f627db84e83d4624bcaf998 Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Fri, 15 May 2020 23:50:18 +0200 Subject: [PATCH 16/29] Working for all segments 2d variant --- .../kotlin/crypto/keyderivation/Argon2.kt | 40 ++++++++++++------- 1 file changed, 26 insertions(+), 14 deletions(-) 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 e5a7948..d20f246 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 @@ -98,12 +98,12 @@ class Argon2 internal constructor( fun compressionFunctionG( - x: Array, - y: Array, + previousBlock: Array, + referenceBlock: Array, currentBlock: Array, xorWithCurrentBlock: Boolean ): Array { - val r = x xor y + val r = referenceBlock xor previousBlock // println("R = X xor Y") // r.hexColumsPrint(16) // val r = Array(1024) { 0U } // view as 8x8 matrix of 16 byte registers @@ -138,7 +138,7 @@ class Argon2 internal constructor( // z.hexColumsPrint(16) val final = if (xorWithCurrentBlock) { // println("Z xor R xor CURRENT") - (z xor r) xor ((x xor y) xor currentBlock) + (z xor r) xor currentBlock } else { // println("Z xor R") z xor r @@ -329,10 +329,11 @@ class Argon2 internal constructor( // 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. + val segmentIndex = column - (slice * (columnCount / 4)) val referenceAreaSize = if (iteration == 0) { if (slice == 0) { //All indices except the previous - column - 1 + (column % (columnCount / 4)) - 1 } else { if (lane == l) { //Same lane @@ -347,9 +348,9 @@ class Argon2 internal constructor( } } else { if (lane == l) { - columnCount - (columnCount / 4) + column - 1 + columnCount - (columnCount / 4) + (column % (columnCount / 4) - 1) } else { - columnCount - (columnCount / 4) + if (column == 0) { + columnCount - (columnCount / 4) + if (column % (columnCount / 4) == 0) { -1 } else { 0 @@ -575,16 +576,27 @@ class Argon2 internal constructor( } } } else { - val (l, z) = computeIndexNew(matrix, lane, 0, columnCount, parallelism.toInt(), iteration, slice, type) - matrix[lane][0] = compressionFunctionG(matrix[lane][columnCount - 1], matrix[l][z], matrix[lane][0], true) - for (column in 1..(slice * segmentLength)) { - val (l, z) = computeIndexNew(matrix, lane, column, columnCount, parallelism.toInt(), iteration, slice, type) - println("Calling compress for I: $iteration S: $slice Lane: $lane Column: $column with l: $l z: $z") - matrix[lane][column] = - compressionFunctionG(matrix[lane][column - 1], matrix[l][z], matrix[lane][column], true) + if (slice == 0) { + val (l, z) = computeIndexNew(matrix, lane, 0, columnCount, parallelism.toInt(), iteration, slice, type) + matrix[lane][0] = compressionFunctionG(matrix[lane][columnCount - 1], matrix[l][z], matrix[lane][0], true) + for (column in 1 until segmentLength) { + val (l, z) = computeIndexNew(matrix, lane, column, columnCount, parallelism.toInt(), iteration, slice, type) + println("Calling compress for I: $iteration S: $slice Lane: $lane Column: $column with l: $l z: $z") + matrix[lane][column] = + compressionFunctionG(matrix[lane][column - 1], matrix[l][z], matrix[lane][column], true) // matrix[lane][column].hexColumsPrint(16) + } + } else { + for (column in slice * segmentLength until (slice + 1) * segmentLength) { + val (l, z) = computeIndexNew(matrix, lane, column, columnCount, parallelism.toInt(), iteration, slice, type) + println("Calling compress for I: $iteration S: $slice Lane: $lane Column: $column with l: $l z: $z") + matrix[lane][column] = + compressionFunctionG(matrix[lane][column - 1], matrix[l][z], matrix[lane][column], true) +// matrix[lane][column].hexColumsPrint(16) + } } + } From 6f51a0ec66b9fc6270be9ade0866eb4c852f8a06 Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Sat, 16 May 2020 10:34:21 +0200 Subject: [PATCH 17/29] Fixed blake2b not using hash length parameter, fully working 2d variant with correct kat tag --- .../kotlin/crypto/hash/blake2b/Blake2b.kt | 7 +- .../kotlin/crypto/keyderivation/Argon2.kt | 69 +++++++++++++++---- .../kotlin/crypto/hash/blake2b/Blake2BTest.kt | 10 +++ .../crypto/hash/keyderivation/Argon2Test.kt | 2 + 4 files changed, 72 insertions(+), 16 deletions(-) diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/hash/blake2b/Blake2b.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/hash/blake2b/Blake2b.kt index 07f151d..afcc519 100644 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/hash/blake2b/Blake2b.kt +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/hash/blake2b/Blake2b.kt @@ -150,7 +150,7 @@ class Blake2b(val key: Array? = null, val hashLength: Int = 64) : Updatab val keyBytes = key?.run { encodeToByteArray().map { it.toUByte() }.toTypedArray() } ?: emptyArray() - return digest(inputMessage = array, key = keyBytes) + return digest(inputMessage = array, key = keyBytes, hashLength = hashLength) } @@ -159,6 +159,9 @@ class Blake2b(val key: Array? = null, val hashLength: Int = 64) : Updatab key: Array, hashLength: Int ): Array { + if (hashLength > MAX_HASH_BYTES) { + throw RuntimeException("Invalid hash length. Requested length more than maximum length. Requested length $hashLength") + } val chunkedMessage = inputMessage.chunked(BLOCK_BYTES) val h = iv.copyOf() @@ -202,7 +205,7 @@ class Blake2b(val key: Array? = null, val hashLength: Int = 64) : Updatab compress(h, lastBlockPadded, lastSize.toBigInteger(), true).copyInto(h) - return formatResult(h) + return formatResult(h).copyOfRange(0, hashLength) } private fun formatResult(h: Array): Array { 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 d20f246..c4e1490 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 @@ -77,7 +77,7 @@ class Argon2 internal constructor( fun argonBlake2bArbitraryLenghtHash(input: Array, length: UInt): Array { if (length <= 64U) { - return Blake2b.digest(length + input) + return Blake2b.digest(inputMessage = length + input, hashLength = length.toInt()) } //We can cast to int because UInt even if MAX_VALUE divided by 32 is guaranteed not to overflow val numberOf64ByteBlocks = (1U + ((length - 1U) / 32U) - 2U).toInt() // equivalent to ceil(length/32) - 2 @@ -151,7 +151,8 @@ class Argon2 internal constructor( private fun extractColumnFromGBlock(gBlock: Array, columnPosition: Int): Array { val result = Array(128) { 0U } for (i in 0..7) { - gBlock.copyOfRange(i * 128 + (columnPosition * 16), i * 128 + (columnPosition * 16) + 16).copyInto(result, i * 16) + gBlock.copyOfRange(i * 128 + (columnPosition * 16), i * 128 + (columnPosition * 16) + 16) + .copyInto(result, i * 16) } return result } @@ -350,7 +351,7 @@ class Argon2 internal constructor( if (lane == l) { columnCount - (columnCount / 4) + (column % (columnCount / 4) - 1) } else { - columnCount - (columnCount / 4) + if (column % (columnCount / 4) == 0) { + columnCount - (columnCount / 4) + if (column % (columnCount / 4) == 0) { -1 } else { 0 @@ -371,7 +372,7 @@ class Argon2 internal constructor( (slice + 1) * (columnCount / 4) //TODO replace all of these with segment length when consolidating variables } } - if ( (startPosition + z.toInt()) % columnCount == -1) { + if ((startPosition + z.toInt()) % columnCount == -1) { println("Debug") } val absolutePosition = (startPosition + z.toInt()) % columnCount @@ -492,13 +493,24 @@ class Argon2 internal constructor( // println("Marker, matrix [$i][1]") } - // ---- Good until here at least ---- val argonInternalContext = ArgonInternalContext( matrix, blockCount, columnCount, segmentLength ) singleThreaded(argonContext, argonInternalContext) - return emptyArray() + val result = matrix.foldIndexed(emptyArray()) { lane, acc, laneArray -> + if (acc.size == 0) { + acc + laneArray[columnCount - 1] // add last element in first lane to the accumulator + } else { + // 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() + } + } + //Hash the xored last blocks + println("Tag:") + val hash = argonBlake2bArbitraryLenghtHash(result, tagLength) + return hash } fun singleThreaded(argonContext: ArgonContext, argonInternalContext: ArgonInternalContext) { @@ -511,8 +523,11 @@ class Argon2 internal constructor( } } println("Done with $iteration") - argonInternalContext.matrix[0][0].slice(0 .. 7).toTypedArray().hexColumsPrint(8) - argonInternalContext.matrix[argonContext.parallelism.toInt() - 1][argonInternalContext.columnCount - 1].slice(1016 .. 1023).toTypedArray().hexColumsPrint(8) + argonInternalContext.matrix[0][0].slice(0..7).toTypedArray().hexColumsPrint(8) + argonInternalContext.matrix[argonContext.parallelism.toInt() - 1][argonInternalContext.columnCount - 1].slice( + 1016..1023 + ).toTypedArray().hexColumsPrint(8) + } } @@ -577,10 +592,29 @@ class Argon2 internal constructor( } } else { if (slice == 0) { - val (l, z) = computeIndexNew(matrix, lane, 0, columnCount, parallelism.toInt(), iteration, slice, type) - matrix[lane][0] = compressionFunctionG(matrix[lane][columnCount - 1], matrix[l][z], matrix[lane][0], true) + val (l, z) = computeIndexNew( + matrix, + lane, + 0, + columnCount, + parallelism.toInt(), + iteration, + slice, + type + ) + matrix[lane][0] = + compressionFunctionG(matrix[lane][columnCount - 1], matrix[l][z], matrix[lane][0], true) for (column in 1 until segmentLength) { - val (l, z) = computeIndexNew(matrix, lane, column, columnCount, parallelism.toInt(), iteration, slice, type) + val (l, z) = computeIndexNew( + matrix, + lane, + column, + columnCount, + parallelism.toInt(), + iteration, + slice, + type + ) println("Calling compress for I: $iteration S: $slice Lane: $lane Column: $column with l: $l z: $z") matrix[lane][column] = compressionFunctionG(matrix[lane][column - 1], matrix[l][z], matrix[lane][column], true) @@ -588,7 +622,16 @@ class Argon2 internal constructor( } } else { for (column in slice * segmentLength until (slice + 1) * segmentLength) { - val (l, z) = computeIndexNew(matrix, lane, column, columnCount, parallelism.toInt(), iteration, slice, type) + val (l, z) = computeIndexNew( + matrix, + lane, + column, + columnCount, + parallelism.toInt(), + iteration, + slice, + type + ) println("Calling compress for I: $iteration S: $slice Lane: $lane Column: $column with l: $l z: $z") matrix[lane][column] = compressionFunctionG(matrix[lane][column - 1], matrix[l][z], matrix[lane][column], true) @@ -600,8 +643,6 @@ class Argon2 internal constructor( } - - // //Remaining iteration // val remainingIterations = (1..numberOfIterations.toInt()).map { iteration -> // diff --git a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/hash/blake2b/Blake2BTest.kt b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/hash/blake2b/Blake2BTest.kt index d4bdd51..852e7dc 100644 --- a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/hash/blake2b/Blake2BTest.kt +++ b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/hash/blake2b/Blake2BTest.kt @@ -17,6 +17,7 @@ package com.ionspin.kotlin.crypto.hash.blake2b import kotlin.test.Test +import kotlin.test.assertFailsWith import kotlin.test.assertTrue /** @@ -280,4 +281,13 @@ class Blake2BTest { } + + @Test + fun testInvalidHashLength() { + val test = "1234567890" + assertFailsWith(RuntimeException::class) { + val result = Blake2b.digest(inputString = test, hashLength = 65) + } + } + } \ No newline at end of file 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 index 6a59e61..4a33cd9 100644 --- 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 @@ -19,6 +19,7 @@ package com.ionspin.kotlin.crypto.hash.keyderivation import com.ionspin.kotlin.crypto.keyderivation.Argon2 +import com.ionspin.kotlin.crypto.util.hexColumsPrint import kotlin.test.Test /** @@ -58,6 +59,7 @@ class Argon2Test { type = Argon2.ArgonType.Argon2d ) val result = digest.calculate() + result.hexColumsPrint(8) } } \ No newline at end of file From 6af623eef6bd619b20a901dbc49cc1b501c6b70f Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Sat, 16 May 2020 16:39:14 +0200 Subject: [PATCH 18/29] Cleanup, implementing 2i --- .../{Argon2.kt => Argon2Template.kt} | 2 +- .../crypto/keyderivation/argon2/Argon2.kt | 326 ++++++++++++++++++ .../keyderivation/argon2/Argon2Utils.kt | 139 ++++++++ .../crypto/hash/keyderivation/Argon2Test.kt | 92 ++++- 4 files changed, 555 insertions(+), 4 deletions(-) rename multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/{Argon2.kt => Argon2Template.kt} (99%) create mode 100644 multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2.kt create mode 100644 multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2Utils.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/Argon2Template.kt similarity index 99% rename from multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/Argon2.kt rename to multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/Argon2Template.kt index c4e1490..9b0c1e5 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/Argon2Template.kt @@ -35,7 +35,7 @@ import com.ionspin.kotlin.crypto.util.* */ @ExperimentalStdlibApi @ExperimentalUnsignedTypes -class Argon2 internal constructor( +class Argon2Template internal constructor( val password: Array, val salt: Array, val parallelism: UInt, diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2.kt new file mode 100644 index 0000000..330fe3c --- /dev/null +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2.kt @@ -0,0 +1,326 @@ +/* + * 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_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") + +package com.ionspin.kotlin.crypto.keyderivation.argon2 + +import com.ionspin.kotlin.bignum.integer.toBigInteger +import com.ionspin.kotlin.crypto.hash.blake2b.Blake2b +import com.ionspin.kotlin.crypto.keyderivation.argon2.Argon2Utils.argonBlake2bArbitraryLenghtHash +import com.ionspin.kotlin.crypto.keyderivation.argon2.Argon2Utils.compressionFunctionG +import com.ionspin.kotlin.crypto.util.fromLittleEndianArrayToUInt +import com.ionspin.kotlin.crypto.util.hexColumsPrint +import com.ionspin.kotlin.crypto.util.toLittleEndianUByteArray + +/** + * Created by Ugljesa Jovanovic + * ugljesa.jovanovic@ionspin.com + * on 16-May-2020 + */ + +enum class ArgonType(val typeId: Int) { + Argon2d(0), Argon2i(1), Argon2id(2) +} + +data class SegmentPosition( + val iteration: Int, + val lane: Int, + val slice: Int +) + +class Argon2( + val password: Array, + val salt: Array = emptyArray(), + val parallelism: Int = 1, + val tagLength: UInt = 64U, + requestedMemorySize: UInt = 0U, + val numberOfIterations: UInt = 1U, + val key: Array = emptyArray(), + val associatedData: Array = emptyArray(), + val argonType: ArgonType = ArgonType.Argon2id +) { + //We support only the latest version + val versionNumber: UInt = 0x13U + + //Use either requested memory size, or default, or throw exception if the requested amount is less than 8*parallelism + val memorySize = if (requestedMemorySize == 0U) { + ((8 * parallelism) * 2).toUInt() + } else { + if (requestedMemorySize < (8 * parallelism).toUInt()) { + throw RuntimeException("Requested memory size must be larger than 8 * parallelism. Requested size: $requestedMemorySize") + } + requestedMemorySize + } + val blockCount = (memorySize / (4U * parallelism.toUInt())) * (4U * parallelism.toUInt()) + val columnCount = (blockCount / parallelism.toUInt()).toInt() + val segmentLength = columnCount / 4 + + val useIndependentAddressing = argonType == ArgonType.Argon2id || argonType == ArgonType.Argon2i + + + // State + val matrix = Array(parallelism) { + Array(columnCount) { + Array(1024) { 0U } + } + } + + private fun clearMatrix() { + matrix.forEachIndexed { laneIndex, lane -> + lane.forEachIndexed { columnIndex, block -> + block.forEachIndexed { byteIndex, byte -> + matrix[laneIndex][columnIndex][byteIndex] = 0U + } + } + } + } + + private fun populateAddressBlock( + iteration: Int, + slice: Int, + lane: Int, + addressBlock: Array, + addressCounter: ULong + ): Array { + //Calculate first pass + val firstPass = compressionFunctionG( + Array(1024) { 0U }, + iteration.toULong().toLittleEndianUByteArray() + + lane.toULong().toLittleEndianUByteArray() + + slice.toULong().toLittleEndianUByteArray() + + blockCount.toULong().toLittleEndianUByteArray() + + numberOfIterations.toULong().toLittleEndianUByteArray() + + argonType.typeId.toULong().toLittleEndianUByteArray() + + addressCounter.toLittleEndianUByteArray() + + Array(968) { 0U }, + addressBlock, + false + ) + //Calculate second pass + val secondPass = compressionFunctionG( + firstPass, + addressBlock, + addressBlock, + false + ) + // Put into address block + return secondPass + } + + + private fun computeReferenceBlockIndexes(iteration: Int, slice: Int, lane: Int, column: Int, addressBlock: Array?): Pair { + val (j1, j2) = when (argonType) { + ArgonType.Argon2d -> { + val previousBlock = if (column == 0) { + matrix[lane][columnCount - 1] //Get last block in the SAME lane + } 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 -> { + val selectedAddressBlock = addressBlock!!.sliceArray((column * 8) until (column * 8) + 8) + val first32Bit = selectedAddressBlock.sliceArray(0 until 4).fromLittleEndianArrayToUInt() + val second32Bit = selectedAddressBlock.sliceArray(4 until 8).fromLittleEndianArrayToUInt() + Pair(first32Bit, second32Bit) + } + 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 { + (j2.toBigInteger() % parallelism).intValue() + + } + + val segmentIndex = (column % (columnCount / 4)) + val referenceAreaSize = if (iteration == 0) { + if (slice == 0) { + //All indices except the previous + segmentIndex - 1 + } else { + if (lane == l) { + //Same lane + column - 1 + } else { + slice * (columnCount / 4) + if (segmentIndex == 0) { // Check if column is first block of the SEGMENT + -1 + } else { + 0 + } + } + } + } else { + if (lane == l) { + columnCount - (columnCount / 4) + (segmentIndex - 1) + } else { + columnCount - (columnCount / 4) + if (segmentIndex == 0) { + -1 + } else { + 0 + } + } + } + + val x = (j1.toULong() * j1) shr 32 + val y = (referenceAreaSize.toULong() * x) shr 32 + val z = referenceAreaSize.toULong() - 1U - y + + val startPosition = if (iteration == 0) { + 0 + } else { + if (slice == 3) { + 0 + } else { + (slice + 1) * segmentLength + } + } + val absolutePosition = (startPosition + z.toInt()) % columnCount + + return Pair(l, absolutePosition) + } + + fun derive(): Array { + val h0 = Blake2b.digest( + parallelism.toUInt() + .toLittleEndianUByteArray() + tagLength.toLittleEndianUByteArray() + memorySize.toLittleEndianUByteArray() + + numberOfIterations.toLittleEndianUByteArray() + versionNumber.toLittleEndianUByteArray() + argonType.typeId.toUInt() + .toLittleEndianUByteArray() + + password.size.toUInt().toLittleEndianUByteArray() + password + + salt.size.toUInt().toLittleEndianUByteArray() + salt + + key.size.toUInt().toLittleEndianUByteArray() + key + + associatedData.size.toUInt().toLittleEndianUByteArray() + associatedData + ) + + //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 + ) + } + executeArgonWithSingleThread() + + val result = matrix.foldIndexed(emptyArray()) { lane, acc, laneArray -> + if (acc.size == 0) { + acc + laneArray[columnCount - 1] // add last element in first lane to the accumulator + } else { + // 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() + } + } + //Hash the xored last blocks + println("Tag:") + val hash = argonBlake2bArbitraryLenghtHash(result, tagLength) + return hash + + + } + + fun executeArgonWithSingleThread() { + for (iteration in 0 until numberOfIterations.toInt()) { + for (slice in 0 until 4) { + for (lane in 0 until parallelism.toInt()) { + println("Processing segment I: $iteration, S: $slice, L: $lane") + val segmentPosition = SegmentPosition(iteration, lane, slice) + processSegment(segmentPosition) + } + } + //Debug prints + println("Done with $iteration") + matrix[0][0].slice(0..7).toTypedArray().hexColumsPrint(8) + matrix[parallelism.toInt() - 1][columnCount - 1].slice( + 1016..1023 + ).toTypedArray().hexColumsPrint(8) + + } + } + + fun processSegment(segmentPosition: SegmentPosition) { + val iteration = segmentPosition.iteration + val slice = segmentPosition.slice + val lane = segmentPosition.lane + + var addressBlock : Array? = null + var addressCounter = 1UL //Starts from 1 in each segment as defined by the spec + + //Generate initial segment address block + if (useIndependentAddressing) { + addressBlock = Array(1024) { + 0U + } + addressBlock = populateAddressBlock(iteration, slice, lane, addressBlock, addressCounter) + addressCounter++ + + addressBlock.hexColumsPrint(16) + } + + val startColumn = if (iteration == 0 && slice == 0) { + 2 + } else { + slice * segmentLength + } + + for (column in startColumn until (slice + 1) * segmentLength) { + //Each address block contains 128 addresses, and we use one per iteration, + //so once we do 128 iterations we need to calculate a new address block + if (useIndependentAddressing && column % 128 == 0) { + addressBlock = populateAddressBlock(iteration, slice, lane, addressBlock!!, addressCounter) + addressCounter++ + } + val previousColumn = if (column == 0) { + columnCount - 1 + } else { + column - 1 + } + val (l, z) = computeReferenceBlockIndexes( + iteration, + slice, + lane, + column, + addressBlock + ) + println("Calling compress for I: $iteration S: $slice Lane: $lane Column: $column with l: $l z: $z") + matrix[lane][column] = + compressionFunctionG( + matrix[lane][previousColumn], + matrix[l][z], + matrix[lane][column], + true + ) + } + + } + + +} diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2Utils.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2Utils.kt new file mode 100644 index 0000000..e24692d --- /dev/null +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2Utils.kt @@ -0,0 +1,139 @@ +/* + * 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_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") + +package com.ionspin.kotlin.crypto.keyderivation.argon2 + +import com.ionspin.kotlin.crypto.hash.blake2b.Blake2b +import com.ionspin.kotlin.crypto.keyderivation.Argon2Template +import com.ionspin.kotlin.crypto.util.* + +/** + * Created by Ugljesa Jovanovic + * ugljesa.jovanovic@ionspin.com + * on 16-May-2020 + */ +object Argon2Utils { + + const val R1 = 32 + const val R2 = 24 + const val R3 = 16 + const val R4 = 63 + + //based on Blake2b mixRound + internal inline fun mixRound(input: Array): Array { + var v = input.chunked(8).map { it.fromLittleEndianArrayToULong() }.toTypedArray() + v = mix(v, 0, 4, 8, 12) + v = mix(v, 1, 5, 9, 13) + v = mix(v, 2, 6, 10, 14) + v = mix(v, 3, 7, 11, 15) + v = mix(v, 0, 5, 10, 15) + v = mix(v, 1, 6, 11, 12) + v = mix(v, 2, 7, 8, 13) + v = mix(v, 3, 4, 9, 14) + return v + } + + //Based on Blake2b mix + private inline fun mix(v: Array, a: Int, b: Int, c: Int, d: Int): Array { + v[a] = (v[a] + v[b] + 2U * (v[a] and 0xFFFFFFFFUL) * (v[b] and 0xFFFFFFFFUL)) + v[d] = (v[d] xor v[a]) rotateRight R1 + v[c] = (v[c] + v[d] + 2U * (v[c] and 0xFFFFFFFFUL) * (v[d] and 0xFFFFFFFFUL)) + v[b] = (v[b] xor v[c]) rotateRight R2 + v[a] = (v[a] + v[b] + 2U * (v[a] and 0xFFFFFFFFUL) * (v[b] and 0xFFFFFFFFUL)) + v[d] = (v[d] xor v[a]) rotateRight R3 + v[c] = (v[c] + v[d] + 2U * (v[c] and 0xFFFFFFFFUL) * (v[d] and 0xFFFFFFFFUL)) + v[b] = (v[b] xor v[c]) rotateRight R4 + return v + } + + private fun extractColumnFromGBlock(gBlock: Array, columnPosition: Int): Array { + val result = Array(128) { 0U } + for (i in 0..7) { + gBlock.copyOfRange(i * 128 + (columnPosition * 16), i * 128 + (columnPosition * 16) + 16) + .copyInto(result, i * 16) + } + return result + } + + private fun copyIntoGBlockColumn(gBlock: Array, columnPosition: Int, columnData: Array) { + for (i in 0..7) { + val column = columnData.copyOfRange(i * 16, i * 16 + 16) + column.copyInto(gBlock, i * 128 + columnPosition * 16) + } + } + + fun compressionFunctionG( + previousBlock: Array, + referenceBlock: Array, + currentBlock: Array, + xorWithCurrentBlock: Boolean + ): Array { + val r = referenceBlock xor previousBlock + val q = Array(1024) { 0U } + val z = Array(1024) { 0U } + // Do the argon/blake2b mixing on rows + for (i in 0..7) { + val startOfRow = (i * 8 * 16) + val endOfRow = startOfRow + (8 * 16) + val rowToMix = r.copyOfRange(startOfRow, endOfRow) + mixRound(rowToMix) + .map { it.toLittleEndianUByteArray() } + .flatMap { it.asIterable() } + .toTypedArray() + .copyInto(q, startOfRow) + } + // Do the argon/blake2b mixing on columns + for (i in 0..7) { + copyIntoGBlockColumn( + z, + i, + mixRound(extractColumnFromGBlock(q, i)) + .map { it.toLittleEndianUByteArray() } + .flatMap { it.asIterable() } + .toTypedArray() + ) + } + val final = if (xorWithCurrentBlock) { + (z xor r) xor currentBlock + } else { + z xor r + } + return final + } + + fun argonBlake2bArbitraryLenghtHash(input: Array, length: UInt): Array { + if (length <= 64U) { + return Blake2b.digest(inputMessage = length + input, hashLength = length.toInt()) + } + //We can cast to int because UInt even if MAX_VALUE divided by 32 is guaranteed not to overflow + 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 numberOf64ByteBlocks) { + v[i] = Blake2b.digest(v[i - 1]) + } + val remainingPartOfInput = length.toInt() - numberOf64ByteBlocks * 32 + val vLast = Blake2b.digest(v[numberOf64ByteBlocks - 1], hashLength = remainingPartOfInput) + val concat = + (v.map { it.copyOfRange(0, 32) }) + .plus(listOf(vLast)) + .foldRight(emptyArray()) { arrayOfUBytes, acc -> arrayOfUBytes + acc } + + return concat + } +} \ No newline at end of file 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 index 4a33cd9..8dcdafc 100644 --- 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 @@ -18,9 +18,13 @@ package com.ionspin.kotlin.crypto.hash.keyderivation -import com.ionspin.kotlin.crypto.keyderivation.Argon2 +import com.ionspin.kotlin.crypto.keyderivation.Argon2Template +import com.ionspin.kotlin.crypto.keyderivation.argon2.Argon2 +import com.ionspin.kotlin.crypto.keyderivation.argon2.ArgonType import com.ionspin.kotlin.crypto.util.hexColumsPrint +import kotlin.math.exp import kotlin.test.Test +import kotlin.test.assertTrue /** * Created by Ugljesa Jovanovic @@ -46,7 +50,7 @@ class Argon2Test { 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( + val digest = Argon2Template( password, salt, parallelism, @@ -56,10 +60,92 @@ class Argon2Test { 0x13U, secret, associatedData, - type = Argon2.ArgonType.Argon2d + type = Argon2Template.ArgonType.Argon2d ) val result = digest.calculate() result.hexColumsPrint(8) } + + @Test + fun argon2dKATTest() { + val expected : Array = arrayOf( + 0x51U, 0x2BU, 0x39U, 0x1BU, 0x6FU, 0x11U, 0x62U, 0x97U, + 0x53U, 0x71U, 0xD3U, 0x09U, 0x19U, 0x73U, 0x42U, 0x94U, + 0xF8U, 0x68U, 0xE3U, 0xBEU, 0x39U, 0x84U, 0xF3U, 0xC1U, + 0xA1U, 0x3AU, 0x4DU, 0xB9U, 0xFAU, 0xBEU, 0x4AU, 0xCBU + ) + + + 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.toInt(), + tagLength, + memory, + iterations, + secret, + associatedData, + ArgonType.Argon2d + ) + val result = digest.derive() + result.hexColumsPrint(8) + assertTrue { expected.contentEquals(result) } + + } + + @Test + fun argon2iKATTest() { + val expected : Array = arrayOf( + 0xc8U, 0x14U, 0xd9U, 0xd1U, 0xdcU, 0x7fU, 0x37U, 0xaaU, + 0x13U, 0xf0U, 0xd7U, 0x7fU, 0x24U, 0x94U, 0xbdU, 0xa1U, + 0xc8U, 0xdeU, 0x6bU, 0x01U, 0x6dU, 0xd3U, 0x88U, 0xd2U, + 0x99U, 0x52U, 0xa4U, 0xc4U, 0x67U, 0x2bU, 0x6cU, 0xe8U + ) + + + 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.toInt(), + tagLength, + memory, + iterations, + secret, + associatedData, + ArgonType.Argon2i + ) + val result = digest.derive() + result.hexColumsPrint(8) + assertTrue { expected.contentEquals(result) } + + } } \ No newline at end of file From 55ac0a87132acb5590c3adb8efa6013a8451c4de Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Sat, 16 May 2020 17:23:31 +0200 Subject: [PATCH 19/29] Working Argon 2i when tested against KAT --- .../crypto/keyderivation/argon2/Argon2.kt | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2.kt index 330fe3c..da37aef 100644 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2.kt +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2.kt @@ -110,19 +110,18 @@ class Argon2( addressBlock, false ) - //Calculate second pass val secondPass = compressionFunctionG( + Array(1024) { 0U }, + firstPass, firstPass, - addressBlock, - addressBlock, false ) - // Put into address block return secondPass } private fun computeReferenceBlockIndexes(iteration: Int, slice: Int, lane: Int, column: Int, addressBlock: Array?): Pair { + val segmentIndex = (column % segmentLength) val (j1, j2) = when (argonType) { ArgonType.Argon2d -> { val previousBlock = if (column == 0) { @@ -135,7 +134,7 @@ class Argon2( Pair(first32Bit, second32Bit) } ArgonType.Argon2i -> { - val selectedAddressBlock = addressBlock!!.sliceArray((column * 8) until (column * 8) + 8) + val selectedAddressBlock = addressBlock!!.sliceArray((segmentIndex * 8) until (segmentIndex * 8) + 8) val first32Bit = selectedAddressBlock.sliceArray(0 until 4).fromLittleEndianArrayToUInt() val second32Bit = selectedAddressBlock.sliceArray(4 until 8).fromLittleEndianArrayToUInt() Pair(first32Bit, second32Bit) @@ -143,7 +142,6 @@ class Argon2( 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 @@ -152,7 +150,7 @@ class Argon2( } - val segmentIndex = (column % (columnCount / 4)) + val referenceAreaSize = if (iteration == 0) { if (slice == 0) { //All indices except the previous @@ -257,11 +255,11 @@ class Argon2( } } //Debug prints - println("Done with $iteration") - matrix[0][0].slice(0..7).toTypedArray().hexColumsPrint(8) - matrix[parallelism.toInt() - 1][columnCount - 1].slice( - 1016..1023 - ).toTypedArray().hexColumsPrint(8) +// println("Done with $iteration") +// matrix[0][0].slice(0..7).toTypedArray().hexColumsPrint(8) +// matrix[parallelism.toInt() - 1][columnCount - 1].slice( +// 1016..1023 +// ).toTypedArray().hexColumsPrint(8) } } @@ -281,8 +279,6 @@ class Argon2( } addressBlock = populateAddressBlock(iteration, slice, lane, addressBlock, addressCounter) addressCounter++ - - addressBlock.hexColumsPrint(16) } val startColumn = if (iteration == 0 && slice == 0) { @@ -294,7 +290,7 @@ class Argon2( for (column in startColumn until (slice + 1) * segmentLength) { //Each address block contains 128 addresses, and we use one per iteration, //so once we do 128 iterations we need to calculate a new address block - if (useIndependentAddressing && column % 128 == 0) { + if (useIndependentAddressing && column != 0 && column % 128 == 0) { addressBlock = populateAddressBlock(iteration, slice, lane, addressBlock!!, addressCounter) addressCounter++ } @@ -303,6 +299,9 @@ class Argon2( } else { column - 1 } + if (iteration == 1) { + println("Breakpoint") + } val (l, z) = computeReferenceBlockIndexes( iteration, slice, @@ -310,7 +309,7 @@ class Argon2( column, addressBlock ) - println("Calling compress for I: $iteration S: $slice Lane: $lane Column: $column with l: $l z: $z") +// println("Calling compress for I: $iteration S: $slice Lane: $lane Column: $column with l: $l z: $z") matrix[lane][column] = compressionFunctionG( matrix[lane][previousColumn], From e08f69f643230ec31cd9ef19d5c6b5b7f30b4d17 Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Sat, 16 May 2020 17:30:54 +0200 Subject: [PATCH 20/29] Fixed versions all tests passing --- buildSrc/src/main/kotlin/Deps.kt | 4 ++-- .../kotlin/com/ionspin/kotlin/crypto/util/UtilTest.kt | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/buildSrc/src/main/kotlin/Deps.kt b/buildSrc/src/main/kotlin/Deps.kt index 1e43a78..70adb05 100644 --- a/buildSrc/src/main/kotlin/Deps.kt +++ b/buildSrc/src/main/kotlin/Deps.kt @@ -15,9 +15,9 @@ */ object Versions { - val kotlinCoroutines = "1.3.3" + val kotlinCoroutines = "1.3.6" val kotlin = "1.3.72" - val kotlinSerialization = "0.11.1" + val kotlinSerialization = "0.20.0" val nodePlugin = "1.3.0" val dokkaPlugin = "0.9.18" diff --git a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/util/UtilTest.kt b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/util/UtilTest.kt index 74d11d6..c7f0c8e 100644 --- a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/util/UtilTest.kt +++ b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/util/UtilTest.kt @@ -59,13 +59,13 @@ class UtilTest { fun testUIntToLittleEndianArray() { assertTrue { val original = 1U - val converted = original.toBigEndianUByteArray() - converted[4] = 1U + val converted = original.toLittleEndianUByteArray() + converted[3] = 1U true } assertTrue { val original = 0xAABBCCDDU - val converted = original.toBigEndianUByteArray() + val converted = original.toLittleEndianUByteArray() converted[0] == 0xDDU.toUByte() && converted[1] == 0xCCU.toUByte() && converted[2] == 0xBBU.toUByte() && From 1208d0549ccbff02f106d70e11d177a42702c342 Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Sat, 16 May 2020 17:58:19 +0200 Subject: [PATCH 21/29] Cleanup, working kat for Argon2id --- .../crypto/keyderivation/Argon2Template.kt | 717 ------------------ .../crypto/keyderivation/argon2/Argon2.kt | 19 +- .../keyderivation/argon2/Argon2Utils.kt | 1 - .../crypto/hash/keyderivation/Argon2Test.kt | 76 +- 4 files changed, 59 insertions(+), 754 deletions(-) delete mode 100644 multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/Argon2Template.kt diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/Argon2Template.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/Argon2Template.kt deleted file mode 100644 index 9b0c1e5..0000000 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/Argon2Template.kt +++ /dev/null @@ -1,717 +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.keyderivation - -import com.ionspin.kotlin.bignum.integer.toBigInteger -import com.ionspin.kotlin.crypto.hash.blake2b.Blake2b -import com.ionspin.kotlin.crypto.util.* - -/** - * - * Further resources and examples of implementation: - * https://tools.ietf.org/html/draft-irtf-cfrg-argon2-03 - * https://en.wikipedia.org/wiki/Argon2 - * https://www.cryptolux.org/images/0/0d/Argon2.pdf - * https://github.com/LoupVaillant/Monocypher/blob/master/src/monocypher.c - * https://github.com/jedisct1/libsodium/blob/master/src/libsodium/crypto_pwhash/argon2/argon2.c - * - * Created by Ugljesa Jovanovic - * ugljesa.jovanovic@ionspin.com - * on 08-Jan-2020 - * - */ -@ExperimentalStdlibApi -@ExperimentalUnsignedTypes -class Argon2Template internal constructor( - val password: Array, - val salt: Array, - val parallelism: UInt, - val tagLength: UInt, - val memorySize: UInt, - val numberOfIterations: UInt, - val versionNumber: UInt, - val key: Array, - val associatedData: Array, - val type: ArgonType -) { - enum class ArgonType(val typeId: Int) { - Argon2d(0), Argon2i(1), Argon2id(2) - } - - data class Argon2StreamGContext( - val block: Array, - val passNumber: Int, - val sliceNumber: Int, - val blockCount: UInt, - val numberOfIterations: UInt, - val counter: UInt, - val type: ArgonType - ) { - - } - - - @ExperimentalStdlibApi - companion object { - - fun Array.xor(target: Array, other: Array) { - if (this.size != other.size || this.size != target.size) { - throw RuntimeException("Invalid array sizes, this ${this.size}, other ${other.size}") - } - target.mapIndexed { index, _ -> this[index] xor other[index] } - } - - - fun argonBlake2bArbitraryLenghtHash(input: Array, length: UInt): Array { - if (length <= 64U) { - return Blake2b.digest(inputMessage = length + input, hashLength = length.toInt()) - } - //We can cast to int because UInt even if MAX_VALUE divided by 32 is guaranteed not to overflow - 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 numberOf64ByteBlocks) { - v[i] = Blake2b.digest(v[i - 1]) - } - val remainingPartOfInput = length.toInt() - numberOf64ByteBlocks * 32 - val vLast = Blake2b.digest(v[numberOf64ByteBlocks - 1], hashLength = remainingPartOfInput) - val concat = - (v.map { it.copyOfRange(0, 32) }) - .plus(listOf(vLast)) - .foldRight(emptyArray()) { arrayOfUBytes, acc -> arrayOfUBytes + acc } - - return concat - } - - - fun compressionFunctionG( - previousBlock: Array, - referenceBlock: Array, - currentBlock: Array, - xorWithCurrentBlock: Boolean - ): Array { - val r = referenceBlock xor previousBlock -// println("R = X xor Y") -// r.hexColumsPrint(16) -// val r = Array(1024) { 0U } // view as 8x8 matrix of 16 byte registers -// x.forEachIndexed { index, it -> r[index] = it xor y[index] } // R = X xor Y - val q = Array(1024) { 0U } - val z = Array(1024) { 0U } - // Do the argon/blake2b mixing on rows - for (i in 0..7) { - val startOfRow = (i * 8 * 16) - val endOfRow = startOfRow + (8 * 16) - val rowToMix = r.copyOfRange(startOfRow, endOfRow) - mixRound(rowToMix) - .map { it.toLittleEndianUByteArray() } - .flatMap { it.asIterable() } - .toTypedArray() - .copyInto(q, startOfRow) - } -// println("---- Q -----") -// q.hexColumsPrint(16) - // Do the argon/blake2b mixing on columns - for (i in 0..7) { - copyIntoGBlockColumn( - z, - i, - mixRound(extractColumnFromGBlock(q, i)) - .map { it.toLittleEndianUByteArray() } - .flatMap { it.asIterable() } - .toTypedArray() - ) - } -// println("---- Z -----") -// z.hexColumsPrint(16) - val final = if (xorWithCurrentBlock) { -// println("Z xor R xor CURRENT") - (z xor r) xor currentBlock - } else { -// println("Z xor R") - z xor r - } - -// final.hexColumsPrint(16) - return final - } - - private fun extractColumnFromGBlock(gBlock: Array, columnPosition: Int): Array { - val result = Array(128) { 0U } - for (i in 0..7) { - gBlock.copyOfRange(i * 128 + (columnPosition * 16), i * 128 + (columnPosition * 16) + 16) - .copyInto(result, i * 16) - } - return result - } - - private fun copyIntoGBlockColumn(gBlock: Array, columnPosition: Int, columnData: Array) { - for (i in 0..7) { - val column = columnData.copyOfRange(i * 16, i * 16 + 16) - column.copyInto(gBlock, i * 128 + columnPosition * 16) - } - } - - - //based on Blake2b mixRound - internal fun mixRound(input: Array): Array { - var v = input.chunked(8).map { it.fromLittleEndianArrayToULong() }.toTypedArray() - v = mix(v, 0, 4, 8, 12) - v = mix(v, 1, 5, 9, 13) - v = mix(v, 2, 6, 10, 14) - v = mix(v, 3, 7, 11, 15) - v = mix(v, 0, 5, 10, 15) - v = mix(v, 1, 6, 11, 12) - v = mix(v, 2, 7, 8, 13) - v = mix(v, 3, 4, 9, 14) - return v - - } - - const val R1 = 32 - const val R2 = 24 - const val R3 = 16 - const val R4 = 63 - - //Based on Blake2b mix - private fun mix(v: Array, a: Int, b: Int, c: Int, d: Int): Array { - v[a] = (v[a] + v[b] + 2U * (v[a] and 0xFFFFFFFFUL) * (v[b] and 0xFFFFFFFFUL)) - v[d] = (v[d] xor v[a]) rotateRight R1 - v[c] = (v[c] + v[d] + 2U * (v[c] and 0xFFFFFFFFUL) * (v[d] and 0xFFFFFFFFUL)) - v[b] = (v[b] xor v[c]) rotateRight R2 - v[a] = (v[a] + v[b] + 2U * (v[a] and 0xFFFFFFFFUL) * (v[b] and 0xFFFFFFFFUL)) - v[d] = (v[d] xor v[a]) rotateRight R3 - v[c] = (v[c] + v[d] + 2U * (v[c] and 0xFFFFFFFFUL) * (v[d] and 0xFFFFFFFFUL)) - v[b] = (v[b] xor v[c]) rotateRight R4 - return v - } - - private fun computeIndexes( - indexContext: IndexContext, - matrix: Array>> - ): Pair { - val block = indexContext.indexMatrix - val parallelism = indexContext.parallelism - val pass = indexContext.pass - val lane = indexContext.lane - val column = indexContext.column - val blockCount = indexContext.blockCount - val iterationCount = indexContext.iterationCount - val type = indexContext.type - val laneCounter = indexContext.laneCounter - - var counter = laneCounter - val sliceNumber = column / 4 - val sliceLength = blockCount / 4U - - val (j1, j2) = when (type) { - ArgonType.Argon2i -> { - val firstPass = compressionFunctionG( - Array(1024) { 0U }, - pass.toULong().toLittleEndianUByteArray() + - lane.toULong().toLittleEndianUByteArray() + - sliceNumber.toULong().toLittleEndianUByteArray() + - blockCount.toULong().toLittleEndianUByteArray() + - iterationCount.toULong().toLittleEndianUByteArray() + - type.typeId.toULong().toLittleEndianUByteArray() + - counter.toUInt().toLittleEndianUByteArray() + - Array(968) { 0U }, - emptyArray(), - false - ) - val secondPass = compressionFunctionG( - firstPass, - pass.toULong().toLittleEndianUByteArray() + - lane.toULong().toLittleEndianUByteArray() + - sliceNumber.toULong().toLittleEndianUByteArray() + - blockCount.toULong().toLittleEndianUByteArray() + - iterationCount.toULong().toLittleEndianUByteArray() + - type.typeId.toULong().toLittleEndianUByteArray() + - counter.toUInt().toLittleEndianUByteArray() + - Array(968) { 0U }, - emptyArray(), - false - ) - secondPass.hexColumsPrint() - Pair(1U, 1U) - } - ArgonType.Argon2d -> { - Pair( - (matrix[laneCounter][column - 1].sliceArray(0..3).fromLittleEndianArrayToUInt()), - (matrix[laneCounter][column - 1].sliceArray(4..7).fromLittleEndianArrayToUInt()) - ) - } - ArgonType.Argon2id -> { - Pair(1U, 1U) - } - } - - val l = if (pass == 0L && sliceNumber == 0) { - 2U - } else { - j2 % parallelism - } - -// val availableIndices = if () - - - return Pair(1, 1) - - } - - data class IndexContext( - val indexMatrix: Array, - val parallelism: UInt, - val pass: Long, - val lane: Int, - val column: Int, - val blockCount: UInt, - val iterationCount: UInt, - val type: ArgonType, - 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][columnCount - 1] //Get last block in the SAME lane - } 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 { - (j2.toBigInteger() % parallelism).intValue() - - } - - //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. - val segmentIndex = column - (slice * (columnCount / 4)) - val referenceAreaSize = if (iteration == 0) { - if (slice == 0) { - //All indices except the previous - (column % (columnCount / 4)) - 1 - } else { - if (lane == l) { - //Same lane - column - 1 - } else { - slice * (columnCount / 4) + if (column % (columnCount / 4) == 0) { // Check if column is first block of the SEGMENT - -1 - } else { - 0 - } - } - } - } else { - if (lane == l) { - columnCount - (columnCount / 4) + (column % (columnCount / 4) - 1) - } else { - columnCount - (columnCount / 4) + if (column % (columnCount / 4) == 0) { - -1 - } else { - 0 - } - } - } - - val x = (j1.toULong() * j1) shr 32 - val y = (referenceAreaSize.toULong() * x) shr 32 - val z = referenceAreaSize.toULong() - 1U - y - - val startPosition = if (iteration == 0) { - 0 - } else { - if (slice == 3) { - 0 - } else { - (slice + 1) * (columnCount / 4) //TODO replace all of these with segment length when consolidating variables - } - } - if ((startPosition + z.toInt()) % columnCount == -1) { - println("Debug") - } - val absolutePosition = (startPosition + z.toInt()) % columnCount - - return Pair(l, absolutePosition) - } - - data class ArgonContext( - val password: Array, - val salt: Array, - val parallelism: UInt, - val tagLength: UInt, - val memorySize: UInt, - val numberOfIterations: UInt, - val versionNumber: UInt, - val key: Array, - val associatedData: Array, - val type: ArgonType - ) - - data class ArgonInternalContext( - val matrix: Array>>, - val blockCount: UInt, - val columnCount: Int, - val segmentLength: Int - ) - - data class SegmentPosition( - val iteration: Int, - val lane: Int, - val slice: Int - ) - - internal fun derive( - password: Array, - salt: Array, - parallelism: UInt, - tagLength: UInt, - memorySize: UInt, - numberOfIterations: UInt, - versionNumber: UInt, - key: Array, - associatedData: Array, - type: ArgonType - ): Array { - val argonContext = ArgonContext( - password = password, - salt = salt, - parallelism = parallelism, - tagLength = tagLength, - memorySize = memorySize, - numberOfIterations = numberOfIterations, - versionNumber = versionNumber, - key = key, - associatedData = associatedData, - type = type - ) - - println("H0 Input") - 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) - println("Marker H0 Input end") - val h0 = Blake2b.digest( - 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 - ) - - h0.hexColumsPrint(8) - println("Marker H0") - - 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 - ) -// println("Start, matrix [$i][0]") -// matrix[i][0].hexColumsPrint(16) -// println("Marker, matrix [$i][0]") - } - - //Compute B[i][1] - for (i in 0 until parallelism.toInt()) { - matrix[i][1] = - argonBlake2bArbitraryLenghtHash( - h0 + 1.toUInt().toLittleEndianUByteArray() + i.toUInt().toLittleEndianUByteArray(), - 1024U - ) -// println("Start, matrix [$i][1]") -// matrix[i][1].hexColumsPrint(16) -// println("Marker, matrix [$i][1]") - } - - val argonInternalContext = ArgonInternalContext( - matrix, blockCount, columnCount, segmentLength - ) - singleThreaded(argonContext, argonInternalContext) - - val result = matrix.foldIndexed(emptyArray()) { lane, acc, laneArray -> - if (acc.size == 0) { - acc + laneArray[columnCount - 1] // add last element in first lane to the accumulator - } else { - // 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() - } - } - //Hash the xored last blocks - println("Tag:") - val hash = argonBlake2bArbitraryLenghtHash(result, tagLength) - return hash - } - - fun singleThreaded(argonContext: ArgonContext, argonInternalContext: ArgonInternalContext) { - for (iteration in 0 until argonContext.numberOfIterations.toInt()) { - for (slice in 0 until 4) { - for (lane in 0 until argonContext.parallelism.toInt()) { - println("Processing segment I: $iteration, S: $slice, L: $lane") - val segmentPosition = SegmentPosition(iteration, lane, slice) - processSegment(argonContext, argonInternalContext, segmentPosition) - } - } - println("Done with $iteration") - argonInternalContext.matrix[0][0].slice(0..7).toTypedArray().hexColumsPrint(8) - argonInternalContext.matrix[argonContext.parallelism.toInt() - 1][argonInternalContext.columnCount - 1].slice( - 1016..1023 - ).toTypedArray().hexColumsPrint(8) - - } - } - - fun processSegment( - argonContext: ArgonContext, - argonInternalContext: ArgonInternalContext, - segmentPosition: SegmentPosition - ) { - val password = argonContext.password - val salt = argonContext.salt - val parallelism = argonContext.parallelism - val tagLength = argonContext.tagLength - val memorySize = argonContext.memorySize - val numberOfIterations = argonContext.numberOfIterations - val versionNumber = argonContext.versionNumber - val key = argonContext.key - val associatedData = argonContext.associatedData - val type = argonContext.type - - val matrix = argonInternalContext.matrix - val blockCount = argonInternalContext.blockCount - val columnCount = argonInternalContext.columnCount - val segmentLength = argonInternalContext.segmentLength - - val iteration = segmentPosition.iteration - val lane = segmentPosition.lane - val slice = segmentPosition.slice - - - if (iteration == 0) { - //Compute B[i][j] - //Using B[i][j] = G(B[i][j], B[l][z]) where l and z are provided bu computeIndexes - //Because this is iteration 0 we have B[i][0] and B[i][1] already filled, so whenever we - //are processing first segment we skip these two blocks - if (slice == 0) { - for (column in 2..(slice * segmentLength)) { - val (l, z) = computeIndexNew(matrix, lane, column, columnCount, parallelism.toInt(), 0, 0, type) - println("Calling compress for I: $iteration S: $slice Lane: $lane Column: $column with l: $l z: $z") - matrix[lane][column] = - compressionFunctionG(matrix[lane][column - 1], matrix[l][z], matrix[lane][column], false) -// matrix[lane][column].hexColumsPrint(16) - } - - } else { - for (column in (slice * segmentLength) until ((slice + 1) * segmentLength)) { - val (l, z) = computeIndexNew( - matrix, - lane, - column, - columnCount, - parallelism.toInt(), - iteration, - slice, - type - ) - println("Calling compress for I: $iteration S: $slice Lane: $lane Column: $column with l: $l z: $z") - matrix[lane][column] = - compressionFunctionG(matrix[lane][column - 1], matrix[l][z], matrix[lane][column], false) -// matrix[lane][column].hexColumsPrint(16) - println("debug") - } - } - } else { - if (slice == 0) { - val (l, z) = computeIndexNew( - matrix, - lane, - 0, - columnCount, - parallelism.toInt(), - iteration, - slice, - type - ) - matrix[lane][0] = - compressionFunctionG(matrix[lane][columnCount - 1], matrix[l][z], matrix[lane][0], true) - for (column in 1 until segmentLength) { - val (l, z) = computeIndexNew( - matrix, - lane, - column, - columnCount, - parallelism.toInt(), - iteration, - slice, - type - ) - println("Calling compress for I: $iteration S: $slice Lane: $lane Column: $column with l: $l z: $z") - matrix[lane][column] = - compressionFunctionG(matrix[lane][column - 1], matrix[l][z], matrix[lane][column], true) -// matrix[lane][column].hexColumsPrint(16) - } - } else { - for (column in slice * segmentLength until (slice + 1) * segmentLength) { - val (l, z) = computeIndexNew( - matrix, - lane, - column, - columnCount, - parallelism.toInt(), - iteration, - slice, - type - ) - println("Calling compress for I: $iteration S: $slice Lane: $lane Column: $column with l: $l z: $z") - matrix[lane][column] = - compressionFunctionG(matrix[lane][column - 1], matrix[l][z], matrix[lane][column], true) -// matrix[lane][column].hexColumsPrint(16) - } - } - - - } - - -// //Remaining iteration -// val remainingIterations = (1..numberOfIterations.toInt()).map { iteration -> -// -// for (i in 0 until parallelism.toInt()) { -// for (j in 0 until columnCount) { -// 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]) -// } -// -// } -// } -// -// -// val result = matrix.foldIndexed(emptyArray()) { lane, acc, laneArray -> -// return if (acc.size == 0) { -// acc + laneArray[columnCount - 1] // add last element in first lane to the accumulator -// } else { -// // 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() -// } -// } -// result -// } - - -// 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/keyderivation/argon2/Argon2.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2.kt index da37aef..4ea297b 100644 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2.kt +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2.kt @@ -139,7 +139,24 @@ class Argon2( val second32Bit = selectedAddressBlock.sliceArray(4 until 8).fromLittleEndianArrayToUInt() Pair(first32Bit, second32Bit) } - ArgonType.Argon2id -> TODO() + ArgonType.Argon2id -> { + if (iteration == 0 && (slice == 0 || slice == 1)) { + val selectedAddressBlock = addressBlock!!.sliceArray((segmentIndex * 8) until (segmentIndex * 8) + 8) + val first32Bit = selectedAddressBlock.sliceArray(0 until 4).fromLittleEndianArrayToUInt() + val second32Bit = selectedAddressBlock.sliceArray(4 until 8).fromLittleEndianArrayToUInt() + Pair(first32Bit, second32Bit) + } else { + val previousBlock = if (column == 0) { + matrix[lane][columnCount - 1] //Get last block in the SAME lane + } else { + matrix[lane][column - 1] + } + val first32Bit = previousBlock.sliceArray(0 until 4).fromLittleEndianArrayToUInt() + val second32Bit = previousBlock.sliceArray(4 until 8).fromLittleEndianArrayToUInt() + Pair(first32Bit, second32Bit) + } + + } } //If this is first iteration and first slice, block is taken from the current lane diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2Utils.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2Utils.kt index e24692d..a867a8b 100644 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2Utils.kt +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2Utils.kt @@ -19,7 +19,6 @@ package com.ionspin.kotlin.crypto.keyderivation.argon2 import com.ionspin.kotlin.crypto.hash.blake2b.Blake2b -import com.ionspin.kotlin.crypto.keyderivation.Argon2Template import com.ionspin.kotlin.crypto.util.* /** 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 index 8dcdafc..3cd6e49 100644 --- 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 @@ -18,11 +18,9 @@ package com.ionspin.kotlin.crypto.hash.keyderivation -import com.ionspin.kotlin.crypto.keyderivation.Argon2Template import com.ionspin.kotlin.crypto.keyderivation.argon2.Argon2 import com.ionspin.kotlin.crypto.keyderivation.argon2.ArgonType import com.ionspin.kotlin.crypto.util.hexColumsPrint -import kotlin.math.exp import kotlin.test.Test import kotlin.test.assertTrue @@ -34,39 +32,6 @@ import kotlin.test.assertTrue @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 = Argon2Template( - password, - salt, - parallelism, - tagLength, - memory, - iterations, - 0x13U, - secret, - associatedData, - type = Argon2Template.ArgonType.Argon2d - ) - val result = digest.calculate() - result.hexColumsPrint(8) - - } - @Test fun argon2dKATTest() { val expected : Array = arrayOf( @@ -148,4 +113,45 @@ class Argon2Test { assertTrue { expected.contentEquals(result) } } + + @Test + fun argon2idKATTest() { + val expected : Array = arrayOf( + 0x0dU, 0x64U, 0x0dU, 0xf5U, 0x8dU, 0x78U, 0x76U, 0x6cU, + 0x08U, 0xc0U, 0x37U, 0xa3U, 0x4aU, 0x8bU, 0x53U, 0xc9U, + 0xd0U, 0x1eU, 0xf0U, 0x45U, 0x2dU, 0x75U, 0xb6U, 0x5eU, + 0xb5U, 0x25U, 0x20U, 0xe9U, 0x6bU, 0x01U, 0xe6U, 0x59U + ) + + + 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.toInt(), + tagLength, + memory, + iterations, + secret, + associatedData, + ArgonType.Argon2id + ) + val result = digest.derive() + result.hexColumsPrint(8) + assertTrue { expected.contentEquals(result) } + + } } \ No newline at end of file From 2bc30517485ccfeaf7408171ba648b80734d4198 Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Sat, 16 May 2020 18:48:23 +0200 Subject: [PATCH 22/29] Enable windows build in builg.gradle, add argon2 nput validation --- multiplatform-crypto/build.gradle.kts | 34 ++++++++-------- .../crypto/keyderivation/argon2/Argon2.kt | 17 ++++++-- .../keyderivation/argon2/Argon2Exceptions.kt | 31 ++++++++++++++ .../keyderivation/argon2/Argon2Utils.kt | 40 +++++++++++++++++++ 4 files changed, 102 insertions(+), 20 deletions(-) create mode 100644 multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2Exceptions.kt diff --git a/multiplatform-crypto/build.gradle.kts b/multiplatform-crypto/build.gradle.kts index 56756ab..805243a 100644 --- a/multiplatform-crypto/build.gradle.kts +++ b/multiplatform-crypto/build.gradle.kts @@ -144,13 +144,13 @@ kotlin { } } -// mingwX86() { -// binaries { -// staticLib { -// -// } -// } -// } + mingwX86() { + binaries { + staticLib { + + } + } + } linuxArm32Hfp() { binaries { @@ -275,16 +275,16 @@ kotlin { dependsOn(nativeTest) } -// val mingwX86Main by getting { -// dependsOn(commonMain) -// dependencies { -// implementation(Deps.Native.coroutines) -// } -// } -// -// val mingwX86Test by getting { -// dependsOn(commonTest) -// } + val mingwX86Main by getting { + dependsOn(commonMain) + dependencies { + implementation(Deps.Native.coroutines) + } + } + + val mingwX86Test by getting { + dependsOn(commonTest) + } val mingwX64Main by getting { dependsOn(commonMain) diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2.kt index 4ea297b..1d732bc 100644 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2.kt +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2.kt @@ -22,6 +22,7 @@ import com.ionspin.kotlin.bignum.integer.toBigInteger import com.ionspin.kotlin.crypto.hash.blake2b.Blake2b import com.ionspin.kotlin.crypto.keyderivation.argon2.Argon2Utils.argonBlake2bArbitraryLenghtHash import com.ionspin.kotlin.crypto.keyderivation.argon2.Argon2Utils.compressionFunctionG +import com.ionspin.kotlin.crypto.keyderivation.argon2.Argon2Utils.validateArgonParameters import com.ionspin.kotlin.crypto.util.fromLittleEndianArrayToUInt import com.ionspin.kotlin.crypto.util.hexColumsPrint import com.ionspin.kotlin.crypto.util.toLittleEndianUByteArray @@ -53,6 +54,19 @@ class Argon2( val associatedData: Array = emptyArray(), val argonType: ArgonType = ArgonType.Argon2id ) { + init { + validateArgonParameters( + password, + salt, + parallelism, + tagLength, + requestedMemorySize, + numberOfIterations, + key, + associatedData, + argonType + ) + } //We support only the latest version val versionNumber: UInt = 0x13U @@ -60,9 +74,6 @@ class Argon2( val memorySize = if (requestedMemorySize == 0U) { ((8 * parallelism) * 2).toUInt() } else { - if (requestedMemorySize < (8 * parallelism).toUInt()) { - throw RuntimeException("Requested memory size must be larger than 8 * parallelism. Requested size: $requestedMemorySize") - } requestedMemorySize } val blockCount = (memorySize / (4U * parallelism.toUInt())) * (4U * parallelism.toUInt()) diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2Exceptions.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2Exceptions.kt new file mode 100644 index 0000000..b6080a1 --- /dev/null +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2Exceptions.kt @@ -0,0 +1,31 @@ +/* + * 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.keyderivation.argon2 + +/** + * Created by Ugljesa Jovanovic + * ugljesa.jovanovic@ionspin.com + * on 16-May-2020 + */ +class Argon2TagTooShort(tagLength: UInt) : RuntimeException("Too short tag (output) requested. Requested: $tagLength") +class Argon2TagTooLong(tagLength: UInt) : RuntimeException("Too long tag (output) requested. Requested: $tagLength") +class Argon2TimeTooShort(iterations: UInt) : RuntimeException("Too short time parameter (Too few iterations). Requested iterations: $iterations") +class Argon2TimeTooLong(iterations: UInt) : RuntimeException("Too long time parameter (Too many iterations). Requested iterations: $iterations") +class Argon2MemoryTooLitlle(requestedMemorySize: UInt) : RuntimeException("Requested memory size must be larger than 8 * parallelism. Requested size: $requestedMemorySize") +class Argon2MemoryTooMuch(requestedMemorySize: UInt) : RuntimeException("Requested memory size too large. Requested size: $requestedMemorySize") +class Argon2LanesTooFew(parallelism: Int) : RuntimeException("Too few, or invalid number of threads requested $parallelism") +class Argon2LanesTooMany(parallelism: Int) : RuntimeException("Too many threads requested (parallelism). Requested: $parallelism") \ No newline at end of file diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2Utils.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2Utils.kt index a867a8b..171168c 100644 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2Utils.kt +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2Utils.kt @@ -135,4 +135,44 @@ object Argon2Utils { return concat } + + /** + * Validates the argon 2 parameters. + * Since Kotlin arrays that we are currently using cannot have more than 2^31 bytes, we don't need to check + * sizes for password, salt, key and associated data. Also since UInt is 32bit we cant set more than 2^32-1 of + * tagLength, requested memory size and number of iterations, so no need to check for upper bound, just lower. + */ + fun validateArgonParameters( + password: Array, + salt: Array, + parallelism: Int , + tagLength: UInt, + requestedMemorySize: UInt , + numberOfIterations: UInt , + key: Array, + associatedData: Array, + argonType: ArgonType + ) { + + //Parallelism + if (parallelism > 0xFFFFFF) { + throw Argon2LanesTooMany(parallelism) + } + if (parallelism <= 0) { + throw Argon2LanesTooFew(parallelism) + } + //Tag length + if (tagLength <= 0U) { + throw Argon2TagTooShort(tagLength) + } + //Requested memory + if (requestedMemorySize < 8U || requestedMemorySize < (8 * parallelism).toUInt()) { + throw Argon2MemoryTooLitlle(requestedMemorySize) + } + //Number of iterations + if (numberOfIterations <= 0U) { + throw Argon2TimeTooShort(numberOfIterations) + } + + } } \ No newline at end of file From 13ebfa8be977ec3dca4c133ed3c2bbbd3245baf0 Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Sat, 16 May 2020 19:02:08 +0200 Subject: [PATCH 23/29] Add key derivation interface --- .../keyderivation/KeyDerivationFunction.kt | 26 +++++++++ .../crypto/keyderivation/argon2/Argon2.kt | 56 ++++++++----------- .../keyderivation/argon2/Argon2Utils.kt | 10 ++-- 3 files changed, 53 insertions(+), 39 deletions(-) create mode 100644 multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/KeyDerivationFunction.kt diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/KeyDerivationFunction.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/KeyDerivationFunction.kt new file mode 100644 index 0000000..bf8b6ed --- /dev/null +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/KeyDerivationFunction.kt @@ -0,0 +1,26 @@ +/* + * 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.keyderivation + +/** + * Created by Ugljesa Jovanovic + * ugljesa.jovanovic@ionspin.com + * on 16-May-2020 + */ +interface KeyDerivationFunction { + fun derive() : Array +} \ No newline at end of file diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2.kt index 1d732bc..15a3c9c 100644 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2.kt +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2.kt @@ -20,6 +20,7 @@ package com.ionspin.kotlin.crypto.keyderivation.argon2 import com.ionspin.kotlin.bignum.integer.toBigInteger import com.ionspin.kotlin.crypto.hash.blake2b.Blake2b +import com.ionspin.kotlin.crypto.keyderivation.KeyDerivationFunction import com.ionspin.kotlin.crypto.keyderivation.argon2.Argon2Utils.argonBlake2bArbitraryLenghtHash import com.ionspin.kotlin.crypto.keyderivation.argon2.Argon2Utils.compressionFunctionG import com.ionspin.kotlin.crypto.keyderivation.argon2.Argon2Utils.validateArgonParameters @@ -44,16 +45,16 @@ data class SegmentPosition( ) class Argon2( - val password: Array, - val salt: Array = emptyArray(), - val parallelism: Int = 1, - val tagLength: UInt = 64U, + private val password: Array, + private val salt: Array = emptyArray(), + private val parallelism: Int = 1, + private val tagLength: UInt = 64U, requestedMemorySize: UInt = 0U, - val numberOfIterations: UInt = 1U, - val key: Array = emptyArray(), - val associatedData: Array = emptyArray(), - val argonType: ArgonType = ArgonType.Argon2id -) { + private val numberOfIterations: UInt = 1U, + private val key: Array = emptyArray(), + private val associatedData: Array = emptyArray(), + private val argonType: ArgonType = ArgonType.Argon2id +) : KeyDerivationFunction { init { validateArgonParameters( password, @@ -68,23 +69,23 @@ class Argon2( ) } //We support only the latest version - val versionNumber: UInt = 0x13U + private val versionNumber: UInt = 0x13U //Use either requested memory size, or default, or throw exception if the requested amount is less than 8*parallelism - val memorySize = if (requestedMemorySize == 0U) { + private val memorySize = if (requestedMemorySize == 0U) { ((8 * parallelism) * 2).toUInt() } else { requestedMemorySize } - val blockCount = (memorySize / (4U * parallelism.toUInt())) * (4U * parallelism.toUInt()) - val columnCount = (blockCount / parallelism.toUInt()).toInt() - val segmentLength = columnCount / 4 + private val blockCount = (memorySize / (4U * parallelism.toUInt())) * (4U * parallelism.toUInt()) + private val columnCount = (blockCount / parallelism.toUInt()).toInt() + private val segmentLength = columnCount / 4 - val useIndependentAddressing = argonType == ArgonType.Argon2id || argonType == ArgonType.Argon2i + private val useIndependentAddressing = argonType == ArgonType.Argon2id || argonType == ArgonType.Argon2i // State - val matrix = Array(parallelism) { + private val matrix = Array(parallelism) { Array(columnCount) { Array(1024) { 0U } } @@ -225,7 +226,7 @@ class Argon2( return Pair(l, absolutePosition) } - fun derive(): Array { + override fun derive(): Array { val h0 = Blake2b.digest( parallelism.toUInt() .toLittleEndianUByteArray() + tagLength.toLittleEndianUByteArray() + memorySize.toLittleEndianUByteArray() + @@ -266,33 +267,25 @@ class Argon2( } } //Hash the xored last blocks - println("Tag:") val hash = argonBlake2bArbitraryLenghtHash(result, tagLength) + clearMatrix() return hash } - fun executeArgonWithSingleThread() { + private fun executeArgonWithSingleThread() { for (iteration in 0 until numberOfIterations.toInt()) { for (slice in 0 until 4) { - for (lane in 0 until parallelism.toInt()) { - println("Processing segment I: $iteration, S: $slice, L: $lane") + for (lane in 0 until parallelism) { val segmentPosition = SegmentPosition(iteration, lane, slice) processSegment(segmentPosition) } } - //Debug prints -// println("Done with $iteration") -// matrix[0][0].slice(0..7).toTypedArray().hexColumsPrint(8) -// matrix[parallelism.toInt() - 1][columnCount - 1].slice( -// 1016..1023 -// ).toTypedArray().hexColumsPrint(8) - } } - fun processSegment(segmentPosition: SegmentPosition) { + private fun processSegment(segmentPosition: SegmentPosition) { val iteration = segmentPosition.iteration val slice = segmentPosition.slice val lane = segmentPosition.lane @@ -308,7 +301,6 @@ class Argon2( addressBlock = populateAddressBlock(iteration, slice, lane, addressBlock, addressCounter) addressCounter++ } - val startColumn = if (iteration == 0 && slice == 0) { 2 } else { @@ -327,9 +319,6 @@ class Argon2( } else { column - 1 } - if (iteration == 1) { - println("Breakpoint") - } val (l, z) = computeReferenceBlockIndexes( iteration, slice, @@ -337,7 +326,6 @@ class Argon2( column, addressBlock ) -// println("Calling compress for I: $iteration S: $slice Lane: $lane Column: $column with l: $l z: $z") matrix[lane][column] = compressionFunctionG( matrix[lane][previousColumn], diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2Utils.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2Utils.kt index 171168c..b27b0d0 100644 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2Utils.kt +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2Utils.kt @@ -34,7 +34,7 @@ object Argon2Utils { const val R4 = 63 //based on Blake2b mixRound - internal inline fun mixRound(input: Array): Array { + private fun mixRound(input: Array): Array { var v = input.chunked(8).map { it.fromLittleEndianArrayToULong() }.toTypedArray() v = mix(v, 0, 4, 8, 12) v = mix(v, 1, 5, 9, 13) @@ -48,7 +48,7 @@ object Argon2Utils { } //Based on Blake2b mix - private inline fun mix(v: Array, a: Int, b: Int, c: Int, d: Int): Array { + private fun mix(v: Array, a: Int, b: Int, c: Int, d: Int): Array { v[a] = (v[a] + v[b] + 2U * (v[a] and 0xFFFFFFFFUL) * (v[b] and 0xFFFFFFFFUL)) v[d] = (v[d] xor v[a]) rotateRight R1 v[c] = (v[c] + v[d] + 2U * (v[c] and 0xFFFFFFFFUL) * (v[d] and 0xFFFFFFFFUL)) @@ -76,7 +76,7 @@ object Argon2Utils { } } - fun compressionFunctionG( + internal fun compressionFunctionG( previousBlock: Array, referenceBlock: Array, currentBlock: Array, @@ -115,7 +115,7 @@ object Argon2Utils { return final } - fun argonBlake2bArbitraryLenghtHash(input: Array, length: UInt): Array { + internal fun argonBlake2bArbitraryLenghtHash(input: Array, length: UInt): Array { if (length <= 64U) { return Blake2b.digest(inputMessage = length + input, hashLength = length.toInt()) } @@ -142,7 +142,7 @@ object Argon2Utils { * sizes for password, salt, key and associated data. Also since UInt is 32bit we cant set more than 2^32-1 of * tagLength, requested memory size and number of iterations, so no need to check for upper bound, just lower. */ - fun validateArgonParameters( + internal fun validateArgonParameters( password: Array, salt: Array, parallelism: Int , From c62727e3fb9a74ecff8dd6745a77d321ecb206a8 Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Sat, 16 May 2020 19:06:32 +0200 Subject: [PATCH 24/29] Add string constructor overload --- .../crypto/keyderivation/argon2/Argon2.kt | 39 +++++++++++++++++-- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2.kt index 15a3c9c..07119b4 100644 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2.kt +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2.kt @@ -25,7 +25,6 @@ import com.ionspin.kotlin.crypto.keyderivation.argon2.Argon2Utils.argonBlake2bAr import com.ionspin.kotlin.crypto.keyderivation.argon2.Argon2Utils.compressionFunctionG import com.ionspin.kotlin.crypto.keyderivation.argon2.Argon2Utils.validateArgonParameters import com.ionspin.kotlin.crypto.util.fromLittleEndianArrayToUInt -import com.ionspin.kotlin.crypto.util.hexColumsPrint import com.ionspin.kotlin.crypto.util.toLittleEndianUByteArray /** @@ -44,6 +43,7 @@ data class SegmentPosition( val slice: Int ) +@ExperimentalStdlibApi class Argon2( private val password: Array, private val salt: Array = emptyArray(), @@ -55,6 +55,29 @@ class Argon2( private val associatedData: Array = emptyArray(), private val argonType: ArgonType = ArgonType.Argon2id ) : KeyDerivationFunction { + + constructor( + password: String, + salt: String = "", + parallelism: Int = 1, + tagLength: UInt = 64U, + requestedMemorySize: UInt = 0U, + numberOfIterations: UInt = 10U, + key: String = "", + associatedData: String = "", + argonType: ArgonType = ArgonType.Argon2id + ) : this( + password.encodeToByteArray().map { it.toUByte() }.toList().toTypedArray(), + salt.encodeToByteArray().map { it.toUByte() }.toList().toTypedArray(), + parallelism, + tagLength, + requestedMemorySize, + numberOfIterations, + key.encodeToByteArray().map { it.toUByte() }.toList().toTypedArray(), + associatedData.encodeToByteArray().map { it.toUByte() }.toList().toTypedArray(), + argonType + ) + init { validateArgonParameters( password, @@ -68,6 +91,7 @@ class Argon2( argonType ) } + //We support only the latest version private val versionNumber: UInt = 0x13U @@ -132,7 +156,13 @@ class Argon2( } - private fun computeReferenceBlockIndexes(iteration: Int, slice: Int, lane: Int, column: Int, addressBlock: Array?): Pair { + private fun computeReferenceBlockIndexes( + iteration: Int, + slice: Int, + lane: Int, + column: Int, + addressBlock: Array? + ): Pair { val segmentIndex = (column % segmentLength) val (j1, j2) = when (argonType) { ArgonType.Argon2d -> { @@ -153,7 +183,8 @@ class Argon2( } ArgonType.Argon2id -> { if (iteration == 0 && (slice == 0 || slice == 1)) { - val selectedAddressBlock = addressBlock!!.sliceArray((segmentIndex * 8) until (segmentIndex * 8) + 8) + val selectedAddressBlock = + addressBlock!!.sliceArray((segmentIndex * 8) until (segmentIndex * 8) + 8) val first32Bit = selectedAddressBlock.sliceArray(0 until 4).fromLittleEndianArrayToUInt() val second32Bit = selectedAddressBlock.sliceArray(4 until 8).fromLittleEndianArrayToUInt() Pair(first32Bit, second32Bit) @@ -290,7 +321,7 @@ class Argon2( val slice = segmentPosition.slice val lane = segmentPosition.lane - var addressBlock : Array? = null + var addressBlock: Array? = null var addressCounter = 1UL //Starts from 1 in each segment as defined by the spec //Generate initial segment address block From 04955effe742d94fe0c6928012c7bd4049389c5f Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Sat, 16 May 2020 21:12:25 +0200 Subject: [PATCH 25/29] Fix errors when calculating address blocks, add readme --- README.md | 33 +++++++++++++++++-- .../crypto/keyderivation/argon2/Argon2.kt | 11 +++++-- .../com/ionspin/kotlin/crypto/ReadmeTest.kt | 32 +++++++++++++++--- .../ionspin/kotlin/crypto/util/UtilTest.kt | 9 +++++ 4 files changed, 76 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 2f2be39..e2a562a 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ It's not peer reviewed, not guaranteed to be bug free, and not guaranteed to be * SHA512 * SHA256 -## Symmetric cipher (Currently only available only in 0.0.3-SNAPSHOT) +## Symmetric cipher * AES * Modes: CBC, CTR @@ -50,7 +50,7 @@ It's not peer reviewed, not guaranteed to be bug free, and not guaranteed to be ## AEAD -More to come. +TODO() ## Integration @@ -187,6 +187,35 @@ plainText == decrypted.toHexString() ``` +### Key derivation + +#### Argon2 + +NOTE: This implementation is tested against KAT generated by reference Argon2 implementation, which does not follow +specification completely. See this issue https://github.com/P-H-C/phc-winner-argon2/issues/183 + +```kotlin +val argon2Instance = Argon2( + password = "Password", + salt = "RandomSalt", + parallelism = 8, + tagLength = 64U, + requestedMemorySize = 256U, //4GB + numberOfIterations = 4U, + key = "", + associatedData = "", + argonType = ArgonType.Argon2id + ) +val tag = argon2Instance.derive() +val tagString = tag.map { it.toString(16).padStart(2, '0') }.joinToString(separator = "") +val expectedTagString = "c255e3e94305817d5e09a7c771e574e3a81cc78fef5da4a9644b6df0" + + "0ba1c9b424e3dd0ce7e600b1269b14c84430708186a8a60403e1bfbda935991592b9ff37" +println("Tag: ${tagString}") +assertEquals(tagString, expectedTagString) +``` + + + diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2.kt index 07119b4..b294f51 100644 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2.kt +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/keyderivation/argon2/Argon2.kt @@ -25,6 +25,7 @@ import com.ionspin.kotlin.crypto.keyderivation.argon2.Argon2Utils.argonBlake2bAr import com.ionspin.kotlin.crypto.keyderivation.argon2.Argon2Utils.compressionFunctionG import com.ionspin.kotlin.crypto.keyderivation.argon2.Argon2Utils.validateArgonParameters import com.ionspin.kotlin.crypto.util.fromLittleEndianArrayToUInt +import com.ionspin.kotlin.crypto.util.hexColumsPrint import com.ionspin.kotlin.crypto.util.toLittleEndianUByteArray /** @@ -164,6 +165,7 @@ class Argon2( addressBlock: Array? ): Pair { val segmentIndex = (column % segmentLength) + val independentIndex = segmentIndex % 128 // 128 is the number of addresses in address block val (j1, j2) = when (argonType) { ArgonType.Argon2d -> { val previousBlock = if (column == 0) { @@ -176,7 +178,7 @@ class Argon2( Pair(first32Bit, second32Bit) } ArgonType.Argon2i -> { - val selectedAddressBlock = addressBlock!!.sliceArray((segmentIndex * 8) until (segmentIndex * 8) + 8) + val selectedAddressBlock = addressBlock!!.sliceArray((independentIndex * 8) until (independentIndex * 8) + 8) val first32Bit = selectedAddressBlock.sliceArray(0 until 4).fromLittleEndianArrayToUInt() val second32Bit = selectedAddressBlock.sliceArray(4 until 8).fromLittleEndianArrayToUInt() Pair(first32Bit, second32Bit) @@ -184,7 +186,7 @@ class Argon2( ArgonType.Argon2id -> { if (iteration == 0 && (slice == 0 || slice == 1)) { val selectedAddressBlock = - addressBlock!!.sliceArray((segmentIndex * 8) until (segmentIndex * 8) + 8) + addressBlock!!.sliceArray((independentIndex * 8) until (independentIndex * 8) + 8) val first32Bit = selectedAddressBlock.sliceArray(0 until 4).fromLittleEndianArrayToUInt() val second32Bit = selectedAddressBlock.sliceArray(4 until 8).fromLittleEndianArrayToUInt() Pair(first32Bit, second32Bit) @@ -338,12 +340,15 @@ class Argon2( slice * segmentLength } + for (column in startColumn until (slice + 1) * segmentLength) { + val segmentIndex = column - (slice * segmentLength) //Each address block contains 128 addresses, and we use one per iteration, //so once we do 128 iterations we need to calculate a new address block - if (useIndependentAddressing && column != 0 && column % 128 == 0) { + if (useIndependentAddressing && segmentIndex != 0 && segmentIndex % 128 == 0) { addressBlock = populateAddressBlock(iteration, slice, lane, addressBlock!!, addressCounter) addressCounter++ + addressBlock.hexColumsPrint(16) } val previousColumn = if (column == 0) { columnCount - 1 diff --git a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/ReadmeTest.kt b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/ReadmeTest.kt index ee449cb..f18f78d 100644 --- a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/ReadmeTest.kt +++ b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/ReadmeTest.kt @@ -19,8 +19,10 @@ package com.ionspin.kotlin.crypto import com.ionspin.kotlin.crypto.hash.blake2b.Blake2b import com.ionspin.kotlin.crypto.hash.sha.Sha256 import com.ionspin.kotlin.crypto.hash.sha.Sha512 +import com.ionspin.kotlin.crypto.keyderivation.argon2.Argon2 +import com.ionspin.kotlin.crypto.keyderivation.argon2.ArgonType import kotlin.test.Test - +import kotlin.test.assertEquals import kotlin.test.assertTrue /** @@ -73,7 +75,7 @@ class ReadmeTest { @ExperimentalStdlibApi @Test fun sha256Example() { - val input ="abc" + val input = "abc" val result = Sha256.digest(inputString = input) val expectedResult = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" assertTrue { @@ -86,9 +88,9 @@ class ReadmeTest { @ExperimentalStdlibApi @Test fun sha512Example() { - val input ="abc" + val input = "abc" 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" + "2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f" assertTrue { @@ -124,4 +126,26 @@ class ReadmeTest { } + + @Test + fun argon2StringExample() { + val argon2Instance = Argon2( + password = "Password", + salt = "RandomSalt", + parallelism = 8, + tagLength = 64U, + requestedMemorySize = 256U, //4GB + numberOfIterations = 4U, + key = "", + associatedData = "", + argonType = ArgonType.Argon2id + ) + val tag = argon2Instance.derive() + val tagString = tag.map { it.toString(16).padStart(2, '0') }.joinToString(separator = "") + val expectedTagString = "c255e3e94305817d5e09a7c771e574e3a81cc78fef5da4a9644b6df0" + + "0ba1c9b424e3dd0ce7e600b1269b14c84430708186a8a60403e1bfbda935991592b9ff37" + println("Tag: ${tagString}") + assertEquals(tagString, expectedTagString) + + } } \ No newline at end of file diff --git a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/util/UtilTest.kt b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/util/UtilTest.kt index c7f0c8e..407368a 100644 --- a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/util/UtilTest.kt +++ b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/util/UtilTest.kt @@ -72,6 +72,15 @@ class UtilTest { converted[3] == 0xAAU.toUByte() } + assertTrue { + val original = 123456U + val converted = original.toLittleEndianUByteArray() + converted[0] == 0x40U.toUByte() && + converted[1] == 0xE2U.toUByte() && + converted[2] == 0x01U.toUByte() && + converted[3] == 0x00U.toUByte() + + } } @Test From 744254aa88483d549d009a504c0c81f49ed29ea7 Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Sat, 16 May 2020 21:49:16 +0200 Subject: [PATCH 26/29] Skip browser tests, and remove mingwX86 --- multiplatform-crypto/build.gradle.kts | 49 ++++++++++++++------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/multiplatform-crypto/build.gradle.kts b/multiplatform-crypto/build.gradle.kts index 805243a..2dbad27 100644 --- a/multiplatform-crypto/build.gradle.kts +++ b/multiplatform-crypto/build.gradle.kts @@ -84,11 +84,12 @@ kotlin { } } browser { - testTask { - useKarma { - useChrome() - } - } + //Until I figure out how to run headless chrome on travis +// testTask { +// useKarma { +// useChrome() +// } +// } } nodejs { testTask { @@ -143,14 +144,14 @@ kotlin { } } } - - mingwX86() { - binaries { - staticLib { - - } - } - } +// No coroutines support for mingwX86 +// mingwX86() { +// binaries { +// staticLib { +// +// } +// } +// } linuxArm32Hfp() { binaries { @@ -274,18 +275,18 @@ kotlin { val linuxTest by getting { dependsOn(nativeTest) } +// Coroutines don't support mingwx86 yet +// val mingwX86Main by getting { +// dependsOn(commonMain) +// dependencies { +// implementation(Deps.Native.coroutines) +// } +// } - val mingwX86Main by getting { - dependsOn(commonMain) - dependencies { - implementation(Deps.Native.coroutines) - } - } - - val mingwX86Test by getting { - dependsOn(commonTest) - } - +// val mingwX86Test by getting { +// dependsOn(commonTest) +// } +// val mingwX64Main by getting { dependsOn(commonMain) dependencies { From bf5e6b3b1a41bd52bfacaa743783c5c1ce80e478 Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Sat, 16 May 2020 22:27:55 +0200 Subject: [PATCH 27/29] travis osx version bump to trz and resolve gradle failing --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a4288b2..eaab6c3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ matrix: - 'if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then bash ./linuxBuild.sh; fi' - 'if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then bash ./linuxBuildAndPublish.sh; fi' - os: osx - osx_image: xcode11.2 + osx_image: xcode11.4 language: java jdk: openjdk12 # before_script: From ba90eae3cbe54f10700ef8e57551b6d28c27daee Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Sat, 16 May 2020 22:38:18 +0200 Subject: [PATCH 28/29] Bump gradle to 6.4 to fix mac build problems --- gradle/wrapper/gradle-wrapper.properties | 2 +- multiplatform-crypto/build.gradle.kts | 21 +++++++++++---------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3c28bf7..65fa2e2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -16,6 +16,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.4.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/multiplatform-crypto/build.gradle.kts b/multiplatform-crypto/build.gradle.kts index 2dbad27..b4e42dd 100644 --- a/multiplatform-crypto/build.gradle.kts +++ b/multiplatform-crypto/build.gradle.kts @@ -83,14 +83,15 @@ kotlin { println("Destination dir ${it.compileKotlinTask.destinationDir}") } } - browser { - //Until I figure out how to run headless chrome on travis + //Until I figure out how to run headless chrome on travis +// browser { +// // testTask { // useKarma { // useChrome() // } // } - } +// } nodejs { testTask { useMocha() { @@ -385,13 +386,13 @@ tasks { } } - val jsBrowserTest by getting(KotlinJsTest::class) { - - testLogging { - events("PASSED", "FAILED", "SKIPPED") - showStandardStreams = true - } - } +// val jsBrowserTest by getting(KotlinJsTest::class) { +// +// testLogging { +// events("PASSED", "FAILED", "SKIPPED") +// showStandardStreams = true +// } +// } } From a59ae994f2050f7f4d6705cc8a167acf08d96731 Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Sat, 16 May 2020 23:05:00 +0200 Subject: [PATCH 29/29] Reduce memory required for this tests, as travis mac build seems to fail because of it --- .../kotlin/com/ionspin/kotlin/crypto/ReadmeTest.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/ReadmeTest.kt b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/ReadmeTest.kt index f18f78d..4cea3e0 100644 --- a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/ReadmeTest.kt +++ b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/ReadmeTest.kt @@ -132,9 +132,9 @@ class ReadmeTest { val argon2Instance = Argon2( password = "Password", salt = "RandomSalt", - parallelism = 8, + parallelism = 4, tagLength = 64U, - requestedMemorySize = 256U, //4GB + requestedMemorySize = 32U, //Travis build on mac fails with higher values numberOfIterations = 4U, key = "", associatedData = "", @@ -142,8 +142,8 @@ class ReadmeTest { ) val tag = argon2Instance.derive() val tagString = tag.map { it.toString(16).padStart(2, '0') }.joinToString(separator = "") - val expectedTagString = "c255e3e94305817d5e09a7c771e574e3a81cc78fef5da4a9644b6df0" + - "0ba1c9b424e3dd0ce7e600b1269b14c84430708186a8a60403e1bfbda935991592b9ff37" + val expectedTagString = "ca134003c9f9f76ca8869359c1d9065603ec54ac30f5158f06af647cacaef2c1c3e" + + "c71e81960278c0596febc64125acbbe5959146db1c128199a1b7cb38982a9" println("Tag: ${tagString}") assertEquals(tagString, expectedTagString)