From 241df8d4a56be464d48df7ea9a948a33a310da90 Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Tue, 10 Sep 2019 19:35:32 +0200 Subject: [PATCH 01/16] Adding aes step by step from FIPS-197 --- buildSrc/src/main/kotlin/Deps.kt | 6 +- gradle/wrapper/gradle-wrapper.properties | 2 +- .../ionspin/kotlin/crypto/symmetric/Aes.kt | 66 +++++++++++++++++++ .../kotlin/crypto/symmetric/AesTest.kt | 30 +++++++++ 4 files changed, 100 insertions(+), 4 deletions(-) create mode 100644 multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt create mode 100644 multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesTest.kt diff --git a/buildSrc/src/main/kotlin/Deps.kt b/buildSrc/src/main/kotlin/Deps.kt index 42e6ed7..1104855 100644 --- a/buildSrc/src/main/kotlin/Deps.kt +++ b/buildSrc/src/main/kotlin/Deps.kt @@ -16,16 +16,16 @@ object Versions { val klock = "1.1.1" - val kotlinCoroutines = "1.3.0-M2" + val kotlinCoroutines = "1.3.0" val timber = "5.0.0-SNAPSHOT" val oshi = "3.12.0" - val kotlin = "1.3.40" + val kotlin = "1.3.50" val ktor = "1.1.1" val kotlinSerialization = "0.11.1" val nodePlugin = "1.3.0" val dokkaPlugin = "0.9.18" - val kotlinBigNumVersion = "0.1.0-SNAPSHOT" + val kotlinBigNumVersion = "0.1.1-SNAPSHOT" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d83a619..4e8cbd5 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-5.1.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt new file mode 100644 index 0000000..c11ff7a --- /dev/null +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt @@ -0,0 +1,66 @@ +package com.ionspin.kotlin.crypto.symmetric + +/** + * Created by Ugljesa Jovanovic (jovanovic.ugljesa@gmail.com) on 07/Sep/2019 + */ +@ExperimentalUnsignedTypes +class Aes { + companion object { + val sBox : UByteArray = + ubyteArrayOf( + 0x63U, 0x7cU, 0x77U, 0x7bU, 0xf2U, 0x6bU, 0x6fU, 0xc5U, 0x30U, 0x01U, 0x67U, 0x2bU, 0xfeU, 0xd7U, 0xabU, 0x76U, + 0xcaU, 0x82U, 0xc9U, 0x7dU, 0xfaU, 0x59U, 0x47U, 0xf0U, 0xadU, 0xd4U, 0xa2U, 0xafU, 0x9cU, 0xa4U, 0x72U, 0xc0U, + 0xb7U, 0xfdU, 0x93U, 0x26U, 0x36U, 0x3fU, 0xf7U, 0xccU, 0x34U, 0xa5U, 0xe5U, 0xf1U, 0x71U, 0xd8U, 0x31U, 0x15U, + 0x04U, 0xc7U, 0x23U, 0xc3U, 0x18U, 0x96U, 0x05U, 0x9aU, 0x07U, 0x12U, 0x80U, 0xe2U, 0xebU, 0x27U, 0xb2U, 0x75U, + 0x09U, 0x83U, 0x2cU, 0x1aU, 0x1bU, 0x6eU, 0x5aU, 0xa0U, 0x52U, 0x3bU, 0xd6U, 0xb3U, 0x29U, 0xe3U, 0x2fU, 0x84U, + 0x53U, 0xd1U, 0x00U, 0xedU, 0x20U, 0xfcU, 0xb1U, 0x5bU, 0x6aU, 0xcbU, 0xbeU, 0x39U, 0x4aU, 0x4cU, 0x58U, 0xcfU, + 0xd0U, 0xefU, 0xaaU, 0xfbU, 0x43U, 0x4dU, 0x33U, 0x85U, 0x45U, 0xf9U, 0x02U, 0x7fU, 0x50U, 0x3cU, 0x9fU, 0xa8U, + 0x51U, 0xa3U, 0x40U, 0x8fU, 0x92U, 0x9dU, 0x38U, 0xf5U, 0xbcU, 0xb6U, 0xdaU, 0x21U, 0x10U, 0xffU, 0xf3U, 0xd2U, + 0xcdU, 0x0cU, 0x13U, 0xecU, 0x5fU, 0x97U, 0x44U, 0x17U, 0xc4U, 0xa7U, 0x7eU, 0x3dU, 0x64U, 0x5dU, 0x19U, 0x73U, + 0x60U, 0x81U, 0x4fU, 0xdcU, 0x22U, 0x2aU, 0x90U, 0x88U, 0x46U, 0xeeU, 0xb8U, 0x14U, 0xdeU, 0x5eU, 0x0bU, 0xdbU, + 0xe0U, 0x32U, 0x3aU, 0x0aU, 0x49U, 0x06U, 0x24U, 0x5cU, 0xc2U, 0xd3U, 0xacU, 0x62U, 0x91U, 0x95U, 0xe4U, 0x79U, + 0xe7U, 0xc8U, 0x37U, 0x6dU, 0x8dU, 0xd5U, 0x4eU, 0xa9U, 0x6cU, 0x56U, 0xf4U, 0xeaU, 0x65U, 0x7aU, 0xaeU, 0x08U, + 0xbaU, 0x78U, 0x25U, 0x2eU, 0x1cU, 0xa6U, 0xb4U, 0xc6U, 0xe8U, 0xddU, 0x74U, 0x1fU, 0x4bU, 0xbdU, 0x8bU, 0x8aU, + 0x70U, 0x3eU, 0xb5U, 0x66U, 0x48U, 0x03U, 0xf6U, 0x0eU, 0x61U, 0x35U, 0x57U, 0xb9U, 0x86U, 0xc1U, 0x1dU, 0x9eU, + 0xe1U, 0xf8U, 0x98U, 0x11U, 0x69U, 0xd9U, 0x8eU, 0x94U, 0x9bU, 0x1eU, 0x87U, 0xe9U, 0xceU, 0x55U, 0x28U, 0xdfU, + 0x8cU, 0xa1U, 0x89U, 0x0dU, 0xbfU, 0xe6U, 0x42U, 0x68U, 0x41U, 0x99U, 0x2dU, 0x0fU, 0xb0U, 0x54U, 0xbbU, 0x16U + ) + } + + sealed class AesKey(val key: String, val keyLength: Int) { + class Aes128Key(key: String) : AesKey(key, 128) + class Aes192Key(key: String) : AesKey(key, 192) + class Aes256Key(key: String) : AesKey(key, 256) + + init { + checkKeyLength(key, keyLength) + } + + fun checkKeyLength(key: String, expectedLength: Int) { + if ((key.length / 2) != expectedLength) { + throw RuntimeException("Invalid key length") + } + } + } + + val state : UByteArray = UByteArray(16) { 0U } + + val stateMatrix : Array> = (0 until 4).map { + Array(4) { 0U } + }.toTypedArray() + + fun subBytes() { + stateMatrix.forEachIndexed { indexRow, row -> + row.forEachIndexed { indexColumn, element -> + val firstDigit = (element / 16U).toInt() + val secondDigit = (element % 16U).toInt() + val substitutionValue = sBox[firstDigit * 16 + secondDigit] + stateMatrix[indexRow][indexColumn] = substitutionValue + } + } + } + + fun expandKey(key: AesKey) { + + } +} diff --git a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesTest.kt b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesTest.kt new file mode 100644 index 0000000..8e6a06c --- /dev/null +++ b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesTest.kt @@ -0,0 +1,30 @@ +package com.ionspin.kotlin.crypto.symmetric + +import kotlin.test.Test +import kotlin.test.assertTrue + +/** + * Created by Ugljesa Jovanovic (jovanovic.ugljesa@gmail.com) on 10/Sep/2019 + */ +@ExperimentalUnsignedTypes +class AesTest { + + @Test + fun testSubBytes() { + val fakeState = arrayOf( + ubyteArrayOf(0x53U, 0U, 0U, 0U).toTypedArray(), + ubyteArrayOf(0U, 0U, 0U, 0U).toTypedArray(), + ubyteArrayOf(0U, 0U, 0U, 0U).toTypedArray(), + ubyteArrayOf(0U, 0U, 0U, 0U).toTypedArray() + ) + val aes = Aes() + fakeState.copyInto(aes.stateMatrix) + aes.subBytes() + aes.stateMatrix.forEach{ + println(it.joinToString { it.toString(16) }) + } + assertTrue { + aes.stateMatrix[0][0] == 0xEDU.toUByte() + } + } +} \ No newline at end of file From c86f60135a69a3aa84b59a90cd824670a9e832a5 Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Tue, 10 Sep 2019 20:48:57 +0200 Subject: [PATCH 02/16] Add shiftRows --- .../ionspin/kotlin/crypto/symmetric/Aes.kt | 7 ++++++ .../kotlin/crypto/symmetric/AesTest.kt | 25 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt index c11ff7a..883efda 100644 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt @@ -60,6 +60,13 @@ class Aes { } } + fun shiftRows() { + stateMatrix[0] = arrayOf(stateMatrix[0][0], stateMatrix[0][1], stateMatrix[0][2], stateMatrix[0][3]) + stateMatrix[1] = arrayOf(stateMatrix[1][1], stateMatrix[1][2], stateMatrix[1][3], stateMatrix[1][0]) + stateMatrix[2] = arrayOf(stateMatrix[2][2], stateMatrix[2][3], stateMatrix[2][0], stateMatrix[2][1]) + stateMatrix[3] = arrayOf(stateMatrix[3][3], stateMatrix[3][0], stateMatrix[3][1], stateMatrix[3][2]) + } + fun expandKey(key: AesKey) { } diff --git a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesTest.kt b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesTest.kt index 8e6a06c..54bab3b 100644 --- a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesTest.kt +++ b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesTest.kt @@ -27,4 +27,29 @@ class AesTest { aes.stateMatrix[0][0] == 0xEDU.toUByte() } } + + @Test + fun testShiftRows() { + val fakeState = arrayOf( + ubyteArrayOf(0U, 1U, 2U, 3U).toTypedArray(), + ubyteArrayOf(0U, 1U, 2U, 3U).toTypedArray(), + ubyteArrayOf(0U, 1U, 2U, 3U).toTypedArray(), + ubyteArrayOf(0U, 1U, 2U, 3U).toTypedArray() + ) + val expectedState = arrayOf( + ubyteArrayOf(0U, 1U, 2U, 3U).toTypedArray(), + ubyteArrayOf(1U, 2U, 3U, 0U).toTypedArray(), + ubyteArrayOf(2U, 3U, 0U, 1U).toTypedArray(), + ubyteArrayOf(3U, 0U, 1U, 2U).toTypedArray() + ) + val aes = Aes() + fakeState.copyInto(aes.stateMatrix) + aes.shiftRows() + aes.stateMatrix.forEach{ + println(it.joinToString { it.toString(16) }) + } + assertTrue { + aes.stateMatrix.contentDeepEquals(expectedState) + } + } } \ No newline at end of file From 15799f33c01e589a3caff7782fcf83f6966f3c80 Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Sun, 15 Sep 2019 23:38:30 +0200 Subject: [PATCH 03/16] Add reverse sbox, add galois multiply --- .../ionspin/kotlin/crypto/symmetric/Aes.kt | 87 ++++++++++++++----- .../kotlin/crypto/symmetric/AesTest.kt | 11 +++ 2 files changed, 77 insertions(+), 21 deletions(-) diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt index 883efda..79b4a48 100644 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt @@ -6,25 +6,50 @@ package com.ionspin.kotlin.crypto.symmetric @ExperimentalUnsignedTypes class Aes { companion object { - val sBox : UByteArray = - ubyteArrayOf( - 0x63U, 0x7cU, 0x77U, 0x7bU, 0xf2U, 0x6bU, 0x6fU, 0xc5U, 0x30U, 0x01U, 0x67U, 0x2bU, 0xfeU, 0xd7U, 0xabU, 0x76U, - 0xcaU, 0x82U, 0xc9U, 0x7dU, 0xfaU, 0x59U, 0x47U, 0xf0U, 0xadU, 0xd4U, 0xa2U, 0xafU, 0x9cU, 0xa4U, 0x72U, 0xc0U, - 0xb7U, 0xfdU, 0x93U, 0x26U, 0x36U, 0x3fU, 0xf7U, 0xccU, 0x34U, 0xa5U, 0xe5U, 0xf1U, 0x71U, 0xd8U, 0x31U, 0x15U, - 0x04U, 0xc7U, 0x23U, 0xc3U, 0x18U, 0x96U, 0x05U, 0x9aU, 0x07U, 0x12U, 0x80U, 0xe2U, 0xebU, 0x27U, 0xb2U, 0x75U, - 0x09U, 0x83U, 0x2cU, 0x1aU, 0x1bU, 0x6eU, 0x5aU, 0xa0U, 0x52U, 0x3bU, 0xd6U, 0xb3U, 0x29U, 0xe3U, 0x2fU, 0x84U, - 0x53U, 0xd1U, 0x00U, 0xedU, 0x20U, 0xfcU, 0xb1U, 0x5bU, 0x6aU, 0xcbU, 0xbeU, 0x39U, 0x4aU, 0x4cU, 0x58U, 0xcfU, - 0xd0U, 0xefU, 0xaaU, 0xfbU, 0x43U, 0x4dU, 0x33U, 0x85U, 0x45U, 0xf9U, 0x02U, 0x7fU, 0x50U, 0x3cU, 0x9fU, 0xa8U, - 0x51U, 0xa3U, 0x40U, 0x8fU, 0x92U, 0x9dU, 0x38U, 0xf5U, 0xbcU, 0xb6U, 0xdaU, 0x21U, 0x10U, 0xffU, 0xf3U, 0xd2U, - 0xcdU, 0x0cU, 0x13U, 0xecU, 0x5fU, 0x97U, 0x44U, 0x17U, 0xc4U, 0xa7U, 0x7eU, 0x3dU, 0x64U, 0x5dU, 0x19U, 0x73U, - 0x60U, 0x81U, 0x4fU, 0xdcU, 0x22U, 0x2aU, 0x90U, 0x88U, 0x46U, 0xeeU, 0xb8U, 0x14U, 0xdeU, 0x5eU, 0x0bU, 0xdbU, - 0xe0U, 0x32U, 0x3aU, 0x0aU, 0x49U, 0x06U, 0x24U, 0x5cU, 0xc2U, 0xd3U, 0xacU, 0x62U, 0x91U, 0x95U, 0xe4U, 0x79U, - 0xe7U, 0xc8U, 0x37U, 0x6dU, 0x8dU, 0xd5U, 0x4eU, 0xa9U, 0x6cU, 0x56U, 0xf4U, 0xeaU, 0x65U, 0x7aU, 0xaeU, 0x08U, - 0xbaU, 0x78U, 0x25U, 0x2eU, 0x1cU, 0xa6U, 0xb4U, 0xc6U, 0xe8U, 0xddU, 0x74U, 0x1fU, 0x4bU, 0xbdU, 0x8bU, 0x8aU, - 0x70U, 0x3eU, 0xb5U, 0x66U, 0x48U, 0x03U, 0xf6U, 0x0eU, 0x61U, 0x35U, 0x57U, 0xb9U, 0x86U, 0xc1U, 0x1dU, 0x9eU, - 0xe1U, 0xf8U, 0x98U, 0x11U, 0x69U, 0xd9U, 0x8eU, 0x94U, 0x9bU, 0x1eU, 0x87U, 0xe9U, 0xceU, 0x55U, 0x28U, 0xdfU, - 0x8cU, 0xa1U, 0x89U, 0x0dU, 0xbfU, 0xe6U, 0x42U, 0x68U, 0x41U, 0x99U, 0x2dU, 0x0fU, 0xb0U, 0x54U, 0xbbU, 0x16U - ) + val sBox: UByteArray = + ubyteArrayOf( + // @formatter:off + 0x63U, 0x7cU, 0x77U, 0x7bU, 0xf2U, 0x6bU, 0x6fU, 0xc5U, 0x30U, 0x01U, 0x67U, 0x2bU, 0xfeU, 0xd7U, 0xabU, 0x76U, + 0xcaU, 0x82U, 0xc9U, 0x7dU, 0xfaU, 0x59U, 0x47U, 0xf0U, 0xadU, 0xd4U, 0xa2U, 0xafU, 0x9cU, 0xa4U, 0x72U, 0xc0U, + 0xb7U, 0xfdU, 0x93U, 0x26U, 0x36U, 0x3fU, 0xf7U, 0xccU, 0x34U, 0xa5U, 0xe5U, 0xf1U, 0x71U, 0xd8U, 0x31U, 0x15U, + 0x04U, 0xc7U, 0x23U, 0xc3U, 0x18U, 0x96U, 0x05U, 0x9aU, 0x07U, 0x12U, 0x80U, 0xe2U, 0xebU, 0x27U, 0xb2U, 0x75U, + 0x09U, 0x83U, 0x2cU, 0x1aU, 0x1bU, 0x6eU, 0x5aU, 0xa0U, 0x52U, 0x3bU, 0xd6U, 0xb3U, 0x29U, 0xe3U, 0x2fU, 0x84U, + 0x53U, 0xd1U, 0x00U, 0xedU, 0x20U, 0xfcU, 0xb1U, 0x5bU, 0x6aU, 0xcbU, 0xbeU, 0x39U, 0x4aU, 0x4cU, 0x58U, 0xcfU, + 0xd0U, 0xefU, 0xaaU, 0xfbU, 0x43U, 0x4dU, 0x33U, 0x85U, 0x45U, 0xf9U, 0x02U, 0x7fU, 0x50U, 0x3cU, 0x9fU, 0xa8U, + 0x51U, 0xa3U, 0x40U, 0x8fU, 0x92U, 0x9dU, 0x38U, 0xf5U, 0xbcU, 0xb6U, 0xdaU, 0x21U, 0x10U, 0xffU, 0xf3U, 0xd2U, + 0xcdU, 0x0cU, 0x13U, 0xecU, 0x5fU, 0x97U, 0x44U, 0x17U, 0xc4U, 0xa7U, 0x7eU, 0x3dU, 0x64U, 0x5dU, 0x19U, 0x73U, + 0x60U, 0x81U, 0x4fU, 0xdcU, 0x22U, 0x2aU, 0x90U, 0x88U, 0x46U, 0xeeU, 0xb8U, 0x14U, 0xdeU, 0x5eU, 0x0bU, 0xdbU, + 0xe0U, 0x32U, 0x3aU, 0x0aU, 0x49U, 0x06U, 0x24U, 0x5cU, 0xc2U, 0xd3U, 0xacU, 0x62U, 0x91U, 0x95U, 0xe4U, 0x79U, + 0xe7U, 0xc8U, 0x37U, 0x6dU, 0x8dU, 0xd5U, 0x4eU, 0xa9U, 0x6cU, 0x56U, 0xf4U, 0xeaU, 0x65U, 0x7aU, 0xaeU, 0x08U, + 0xbaU, 0x78U, 0x25U, 0x2eU, 0x1cU, 0xa6U, 0xb4U, 0xc6U, 0xe8U, 0xddU, 0x74U, 0x1fU, 0x4bU, 0xbdU, 0x8bU, 0x8aU, + 0x70U, 0x3eU, 0xb5U, 0x66U, 0x48U, 0x03U, 0xf6U, 0x0eU, 0x61U, 0x35U, 0x57U, 0xb9U, 0x86U, 0xc1U, 0x1dU, 0x9eU, + 0xe1U, 0xf8U, 0x98U, 0x11U, 0x69U, 0xd9U, 0x8eU, 0x94U, 0x9bU, 0x1eU, 0x87U, 0xe9U, 0xceU, 0x55U, 0x28U, 0xdfU, + 0x8cU, 0xa1U, 0x89U, 0x0dU, 0xbfU, 0xe6U, 0x42U, 0x68U, 0x41U, 0x99U, 0x2dU, 0x0fU, 0xb0U, 0x54U, 0xbbU, 0x16U + // @formatter:on + ) + + val inverseSBox: UByteArray = + ubyteArrayOf( + // @formatter:off + 0x52U, 0x09U, 0x6aU, 0xd5U, 0x30U, 0x36U, 0xa5U, 0x38U, 0xbfU, 0x40U, 0xa3U, 0x9eU, 0x81U, 0xf3U, 0xd7U, 0xfbU, + 0x7cU, 0xe3U, 0x39U, 0x82U, 0x9bU, 0x2fU, 0xffU, 0x87U, 0x34U, 0x8eU, 0x43U, 0x44U, 0xc4U, 0xdeU, 0xe9U, 0xcbU, + 0x54U, 0x7bU, 0x94U, 0x32U, 0xa6U, 0xc2U, 0x23U, 0x3dU, 0xeeU, 0x4cU, 0x95U, 0x0bU, 0x42U, 0xfaU, 0xc3U, 0x4eU, + 0x08U, 0x2eU, 0xa1U, 0x66U, 0x28U, 0xd9U, 0x24U, 0xb2U, 0x76U, 0x5bU, 0xa2U, 0x49U, 0x6dU, 0x8bU, 0xd1U, 0x25U, + 0x72U, 0xf8U, 0xf6U, 0x64U, 0x86U, 0x68U, 0x98U, 0x16U, 0xd4U, 0xa4U, 0x5cU, 0xccU, 0x5dU, 0x65U, 0xb6U, 0x92U, + 0x6cU, 0x70U, 0x48U, 0x50U, 0xfdU, 0xedU, 0xb9U, 0xdaU, 0x5eU, 0x15U, 0x46U, 0x57U, 0xa7U, 0x8dU, 0x9dU, 0x84U, + 0x90U, 0xd8U, 0xabU, 0x00U, 0x8cU, 0xbcU, 0xd3U, 0x0aU, 0xf7U, 0xe4U, 0x58U, 0x05U, 0xb8U, 0xb3U, 0x45U, 0x06U, + 0xd0U, 0x2cU, 0x1eU, 0x8fU, 0xcaU, 0x3fU, 0x0fU, 0x02U, 0xc1U, 0xafU, 0xbdU, 0x03U, 0x01U, 0x13U, 0x8aU, 0x6bU, + 0x3aU, 0x91U, 0x11U, 0x41U, 0x4fU, 0x67U, 0xdcU, 0xeaU, 0x97U, 0xf2U, 0xcfU, 0xceU, 0xf0U, 0xb4U, 0xe6U, 0x73U, + 0x96U, 0xacU, 0x74U, 0x22U, 0xe7U, 0xadU, 0x35U, 0x85U, 0xe2U, 0xf9U, 0x37U, 0xe8U, 0x1cU, 0x75U, 0xdfU, 0x6eU, + 0x47U, 0xf1U, 0x1aU, 0x71U, 0x1dU, 0x29U, 0xc5U, 0x89U, 0x6fU, 0xb7U, 0x62U, 0x0eU, 0xaaU, 0x18U, 0xbeU, 0x1bU, + 0xfcU, 0x56U, 0x3eU, 0x4bU, 0xc6U, 0xd2U, 0x79U, 0x20U, 0x9aU, 0xdbU, 0xc0U, 0xfeU, 0x78U, 0xcdU, 0x5aU, 0xf4U, + 0x1fU, 0xddU, 0xa8U, 0x33U, 0x88U, 0x07U, 0xc7U, 0x31U, 0xb1U, 0x12U, 0x10U, 0x59U, 0x27U, 0x80U, 0xecU, 0x5fU, + 0x60U, 0x51U, 0x7fU, 0xa9U, 0x19U, 0xb5U, 0x4aU, 0x0dU, 0x2dU, 0xe5U, 0x7aU, 0x9fU, 0x93U, 0xc9U, 0x9cU, 0xefU, + 0xa0U, 0xe0U, 0x3bU, 0x4dU, 0xaeU, 0x2aU, 0xf5U, 0xb0U, 0xc8U, 0xebU, 0xbbU, 0x3cU, 0x83U, 0x53U, 0x99U, 0x61U, + 0x17U, 0x2bU, 0x04U, 0x7eU, 0xbaU, 0x77U, 0xd6U, 0x26U, 0xe1U, 0x69U, 0x14U, 0x63U, 0x55U, 0x21U, 0x0cU, 0x7dU + // @formatter:on + ) + } sealed class AesKey(val key: String, val keyLength: Int) { @@ -43,9 +68,9 @@ class Aes { } } - val state : UByteArray = UByteArray(16) { 0U } + val state: UByteArray = UByteArray(16) { 0U } - val stateMatrix : Array> = (0 until 4).map { + val stateMatrix: Array> = (0 until 4).map { Array(4) { 0U } }.toTypedArray() @@ -67,6 +92,26 @@ class Aes { stateMatrix[3] = arrayOf(stateMatrix[3][3], stateMatrix[3][0], stateMatrix[3][1], stateMatrix[3][2]) } + fun galoisFieldMultiply(first : UByte, second : UByte) : UByte { + var result : UInt = 0U + var firstInt = first.toUInt() + var secondInt = second.toUInt() + var carry : UInt = 0U + for (i in 0 .. 7) { + if (secondInt and 0x01U == 1U) { + result = result xor firstInt + } + carry = firstInt and 0x80U + firstInt = firstInt shl 1 + if (carry == 0x80U) { + firstInt = firstInt xor 0x001BU + } + secondInt = secondInt shr 1 + firstInt = firstInt and 0xFFU + } + return result.toUByte() + } + fun expandKey(key: AesKey) { } diff --git a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesTest.kt b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesTest.kt index 54bab3b..a6dd137 100644 --- a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesTest.kt +++ b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesTest.kt @@ -52,4 +52,15 @@ class AesTest { aes.stateMatrix.contentDeepEquals(expectedState) } } + + @Test + fun testGaloisMultiply() { + val a = 0x57U + val b = 0x83U + val aes = Aes() + val c = aes.galoisFieldMultiply(a.toUByte(), b.toUByte()) + assertTrue { + c == 0xC1U.toUByte() + } + } } \ No newline at end of file From d72d55ef71810a455555fd543727e9b5d37bd3fb Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Mon, 16 Sep 2019 23:56:08 +0200 Subject: [PATCH 04/16] Add mix columns --- .../ionspin/kotlin/crypto/symmetric/Aes.kt | 22 ++++++ .../kotlin/crypto/symmetric/AesTest.kt | 78 ++++++++++++++----- 2 files changed, 81 insertions(+), 19 deletions(-) diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt index 79b4a48..ecc0a8a 100644 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt @@ -92,6 +92,24 @@ class Aes { stateMatrix[3] = arrayOf(stateMatrix[3][3], stateMatrix[3][0], stateMatrix[3][1], stateMatrix[3][2]) } + fun mixColumns() { + val stateMixed : Array> = (0 until 4).map { + Array(4) { 0U } + }.toTypedArray() + for (c in 0 .. 3) { + + stateMixed[0][c] = (2U gfm stateMatrix[0][c]) xor galoisFieldMultiply(3U, stateMatrix[1][c]) xor stateMatrix[2][c] xor stateMatrix[3][c] + stateMixed[1][c] = stateMatrix[0][c] xor (2U gfm stateMatrix[1][c]) xor (3U gfm stateMatrix[2][c]) xor stateMatrix[3][c] + stateMixed[2][c] = stateMatrix[0][c] xor stateMatrix[1][c] xor (2U gfm stateMatrix[2][c]) xor (3U gfm stateMatrix[3][c]) + stateMixed[3][c] = 3U gfm stateMatrix[0][c] xor stateMatrix[1][c] xor stateMatrix[2][c] xor (2U gfm stateMatrix[3][c]) + } + stateMixed.copyInto(stateMatrix) + } + + fun galoisFieldAdd(first : UByte, second : UByte) : UByte { + return first xor second + } + fun galoisFieldMultiply(first : UByte, second : UByte) : UByte { var result : UInt = 0U var firstInt = first.toUInt() @@ -112,6 +130,10 @@ class Aes { return result.toUByte() } + infix fun UInt.gfm(second : UByte) : UByte { + return galoisFieldMultiply(this.toUByte(), second) + } + fun expandKey(key: AesKey) { } diff --git a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesTest.kt b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesTest.kt index a6dd137..381c9b6 100644 --- a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesTest.kt +++ b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesTest.kt @@ -12,15 +12,15 @@ class AesTest { @Test fun testSubBytes() { val fakeState = arrayOf( - ubyteArrayOf(0x53U, 0U, 0U, 0U).toTypedArray(), - ubyteArrayOf(0U, 0U, 0U, 0U).toTypedArray(), - ubyteArrayOf(0U, 0U, 0U, 0U).toTypedArray(), - ubyteArrayOf(0U, 0U, 0U, 0U).toTypedArray() - ) + ubyteArrayOf(0x53U, 0U, 0U, 0U).toTypedArray(), + ubyteArrayOf(0U, 0U, 0U, 0U).toTypedArray(), + ubyteArrayOf(0U, 0U, 0U, 0U).toTypedArray(), + ubyteArrayOf(0U, 0U, 0U, 0U).toTypedArray() + ) val aes = Aes() fakeState.copyInto(aes.stateMatrix) aes.subBytes() - aes.stateMatrix.forEach{ + aes.stateMatrix.forEach { println(it.joinToString { it.toString(16) }) } assertTrue { @@ -31,21 +31,21 @@ class AesTest { @Test fun testShiftRows() { val fakeState = arrayOf( - ubyteArrayOf(0U, 1U, 2U, 3U).toTypedArray(), - ubyteArrayOf(0U, 1U, 2U, 3U).toTypedArray(), - ubyteArrayOf(0U, 1U, 2U, 3U).toTypedArray(), - ubyteArrayOf(0U, 1U, 2U, 3U).toTypedArray() + ubyteArrayOf(0U, 1U, 2U, 3U).toTypedArray(), + ubyteArrayOf(0U, 1U, 2U, 3U).toTypedArray(), + ubyteArrayOf(0U, 1U, 2U, 3U).toTypedArray(), + ubyteArrayOf(0U, 1U, 2U, 3U).toTypedArray() ) val expectedState = arrayOf( - ubyteArrayOf(0U, 1U, 2U, 3U).toTypedArray(), - ubyteArrayOf(1U, 2U, 3U, 0U).toTypedArray(), - ubyteArrayOf(2U, 3U, 0U, 1U).toTypedArray(), - ubyteArrayOf(3U, 0U, 1U, 2U).toTypedArray() + ubyteArrayOf(0U, 1U, 2U, 3U).toTypedArray(), + ubyteArrayOf(1U, 2U, 3U, 0U).toTypedArray(), + ubyteArrayOf(2U, 3U, 0U, 1U).toTypedArray(), + ubyteArrayOf(3U, 0U, 1U, 2U).toTypedArray() ) val aes = Aes() fakeState.copyInto(aes.stateMatrix) aes.shiftRows() - aes.stateMatrix.forEach{ + aes.stateMatrix.forEach { println(it.joinToString { it.toString(16) }) } assertTrue { @@ -55,12 +55,52 @@ class AesTest { @Test fun testGaloisMultiply() { - val a = 0x57U - val b = 0x83U - val aes = Aes() - val c = aes.galoisFieldMultiply(a.toUByte(), b.toUByte()) + //Samples from FIPS-197 assertTrue { + val a = 0x57U + val b = 0x83U + val aes = Aes() + val c = aes.galoisFieldMultiply(a.toUByte(), b.toUByte()) c == 0xC1U.toUByte() } + + assertTrue { + val a = 0x57U + val b = 0x13U + val aes = Aes() + val c = aes.galoisFieldMultiply(a.toUByte(), b.toUByte()) + c == 0xFEU.toUByte() + } + + + } + + @Test + fun testMixColumns() { + //Test vectors from wikipedia + val fakeState = arrayOf( + ubyteArrayOf(0xdbU, 0xf2U, 0x01U, 0xc6U).toTypedArray(), + ubyteArrayOf(0x13U, 0x0aU, 0x01U, 0xc6U).toTypedArray(), + ubyteArrayOf(0x53U, 0x22U, 0x01U, 0xc6U).toTypedArray(), + ubyteArrayOf(0x45U, 0x5cU, 0x01U, 0xc6U).toTypedArray() + ) + + val expectedState = arrayOf( + ubyteArrayOf(0x8eU, 0x9fU, 0x01U, 0xc6U).toTypedArray(), + ubyteArrayOf(0x4dU, 0xdcU, 0x01U, 0xc6U).toTypedArray(), + ubyteArrayOf(0xa1U, 0x58U, 0x01U, 0xc6U).toTypedArray(), + ubyteArrayOf(0xbcU, 0x9dU, 0x01U, 0xc6U).toTypedArray() + ) + + val aes = Aes() + fakeState.copyInto(aes.stateMatrix) + aes.mixColumns() + aes.stateMatrix.forEach { + println(it.joinToString { it.toString(16) }) + } + assertTrue { + aes.stateMatrix.contentDeepEquals(expectedState) + } + } } \ No newline at end of file From 90fd7adcc4a4b03d5c6c3249527ca21594b25331 Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Tue, 17 Sep 2019 23:29:44 +0200 Subject: [PATCH 05/16] Added key expansion --- .../ionspin/kotlin/crypto/symmetric/Aes.kt | 127 +++++++++++++----- .../kotlin/crypto/symmetric/AesTest.kt | 90 ++++++++++++- 2 files changed, 179 insertions(+), 38 deletions(-) diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt index ecc0a8a..419665d 100644 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt @@ -4,7 +4,7 @@ package com.ionspin.kotlin.crypto.symmetric * Created by Ugljesa Jovanovic (jovanovic.ugljesa@gmail.com) on 07/Sep/2019 */ @ExperimentalUnsignedTypes -class Aes { +class Aes(val aesKey: AesKey) { companion object { val sBox: UByteArray = ubyteArrayOf( @@ -50,23 +50,10 @@ class Aes { // @formatter:on ) + val rcon: UByteArray = ubyteArrayOf(0x8DU, 0x01U, 0x02U, 0x04U, 0x08U, 0x10U, 0x20U, 0x40U, 0x80U, 0x1BU, 0x36U) + } - sealed class AesKey(val key: String, val keyLength: Int) { - class Aes128Key(key: String) : AesKey(key, 128) - class Aes192Key(key: String) : AesKey(key, 192) - class Aes256Key(key: String) : AesKey(key, 256) - - init { - checkKeyLength(key, keyLength) - } - - fun checkKeyLength(key: String, expectedLength: Int) { - if ((key.length / 2) != expectedLength) { - throw RuntimeException("Invalid key length") - } - } - } val state: UByteArray = UByteArray(16) { 0U } @@ -74,17 +61,23 @@ class Aes { Array(4) { 0U } }.toTypedArray() + val expandedKey: Array> = expandKey() + + fun subBytes() { stateMatrix.forEachIndexed { indexRow, row -> row.forEachIndexed { indexColumn, element -> - val firstDigit = (element / 16U).toInt() - val secondDigit = (element % 16U).toInt() - val substitutionValue = sBox[firstDigit * 16 + secondDigit] - stateMatrix[indexRow][indexColumn] = substitutionValue + stateMatrix[indexRow][indexColumn] = getSBoxValue(element) } } } + fun getSBoxValue(element: UByte): UByte { + val firstDigit = (element / 16U).toInt() + val secondDigit = (element % 16U).toInt() + return sBox[firstDigit * 16 + secondDigit] + } + fun shiftRows() { stateMatrix[0] = arrayOf(stateMatrix[0][0], stateMatrix[0][1], stateMatrix[0][2], stateMatrix[0][3]) stateMatrix[1] = arrayOf(stateMatrix[1][1], stateMatrix[1][2], stateMatrix[1][3], stateMatrix[1][0]) @@ -93,29 +86,35 @@ class Aes { } fun mixColumns() { - val stateMixed : Array> = (0 until 4).map { + val stateMixed: Array> = (0 until 4).map { Array(4) { 0U } }.toTypedArray() - for (c in 0 .. 3) { + for (c in 0..3) { - stateMixed[0][c] = (2U gfm stateMatrix[0][c]) xor galoisFieldMultiply(3U, stateMatrix[1][c]) xor stateMatrix[2][c] xor stateMatrix[3][c] - stateMixed[1][c] = stateMatrix[0][c] xor (2U gfm stateMatrix[1][c]) xor (3U gfm stateMatrix[2][c]) xor stateMatrix[3][c] - stateMixed[2][c] = stateMatrix[0][c] xor stateMatrix[1][c] xor (2U gfm stateMatrix[2][c]) xor (3U gfm stateMatrix[3][c]) - stateMixed[3][c] = 3U gfm stateMatrix[0][c] xor stateMatrix[1][c] xor stateMatrix[2][c] xor (2U gfm stateMatrix[3][c]) + stateMixed[0][c] = (2U gfm stateMatrix[0][c]) xor galoisFieldMultiply( + 3U, + stateMatrix[1][c] + ) xor stateMatrix[2][c] xor stateMatrix[3][c] + stateMixed[1][c] = + stateMatrix[0][c] xor (2U gfm stateMatrix[1][c]) xor (3U gfm stateMatrix[2][c]) xor stateMatrix[3][c] + stateMixed[2][c] = + stateMatrix[0][c] xor stateMatrix[1][c] xor (2U gfm stateMatrix[2][c]) xor (3U gfm stateMatrix[3][c]) + stateMixed[3][c] = + 3U gfm stateMatrix[0][c] xor stateMatrix[1][c] xor stateMatrix[2][c] xor (2U gfm stateMatrix[3][c]) } stateMixed.copyInto(stateMatrix) } - fun galoisFieldAdd(first : UByte, second : UByte) : UByte { + fun galoisFieldAdd(first: UByte, second: UByte): UByte { return first xor second } - fun galoisFieldMultiply(first : UByte, second : UByte) : UByte { - var result : UInt = 0U + fun galoisFieldMultiply(first: UByte, second: UByte): UByte { + var result: UInt = 0U var firstInt = first.toUInt() var secondInt = second.toUInt() - var carry : UInt = 0U - for (i in 0 .. 7) { + var carry: UInt = 0U + for (i in 0..7) { if (secondInt and 0x01U == 1U) { result = result xor firstInt } @@ -130,11 +129,73 @@ class Aes { return result.toUByte() } - infix fun UInt.gfm(second : UByte) : UByte { + fun addRoundKey() { + + } + + infix fun UInt.gfm(second: UByte): UByte { return galoisFieldMultiply(this.toUByte(), second) } - fun expandKey(key: AesKey) { + fun expandKey(): Array> { + val expandedKey = (0 until 4 * (aesKey.numberOfRounds + 1)).map { + Array(4) { 0U } + }.toTypedArray() + // First round + for (i in 0 until aesKey.numberOf32BitWords) { + expandedKey[i][0] = aesKey.keyArray[i * 4 + 0] + expandedKey[i][1] = aesKey.keyArray[i * 4 + 1] + expandedKey[i][2] = aesKey.keyArray[i * 4 + 2] + expandedKey[i][3] = aesKey.keyArray[i * 4 + 3] + } + for (i in aesKey.numberOf32BitWords until 4 * (aesKey.numberOfRounds + 1)) { + val temp = expandedKey[i - 1].copyOf() + if (i % aesKey.numberOf32BitWords == 0) { + //RotWord + val tempTemp = temp[0] + temp[0] = temp[1] + temp[1] = temp[2] + temp[2] = temp[3] + temp[3] = tempTemp + + //SubWord + temp[0] = getSBoxValue(temp[0]) + temp[1] = getSBoxValue(temp[1]) + temp[2] = getSBoxValue(temp[2]) + temp[3] = getSBoxValue(temp[3]) + + temp[0] = temp[0] xor rcon[i / aesKey.numberOf32BitWords] + + } else if (aesKey is AesKey.Aes256Key && i % aesKey.numberOf32BitWords == 4) { + temp[0] = getSBoxValue(temp[0]) + temp[1] = getSBoxValue(temp[1]) + temp[2] = getSBoxValue(temp[2]) + temp[3] = getSBoxValue(temp[3]) + } + expandedKey[i] = expandedKey[i - aesKey.numberOf32BitWords].mapIndexed { index, it -> + it xor temp[index] + }.toTypedArray() + } + return expandedKey + } +} + +sealed class AesKey(val key: String, val keyLength: Int, val numberOfRounds: Int) { + val keyArray: Array = key.chunked(2).map { it.toUByte(16) }.toTypedArray() + val numberOf32BitWords = keyLength / 32 + + class Aes128Key(key: String) : AesKey(key, 128, 10) + class Aes192Key(key: String) : AesKey(key, 192, 12) + class Aes256Key(key: String) : AesKey(key, 256, 14) + + init { + checkKeyLength(key, keyLength) + } + + fun checkKeyLength(key: String, expectedLength: Int) { + if ((key.length / 2) != expectedLength / 8) { + throw RuntimeException("Invalid key length") + } } } diff --git a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesTest.kt b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesTest.kt index 381c9b6..e97c21b 100644 --- a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesTest.kt +++ b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesTest.kt @@ -17,7 +17,7 @@ class AesTest { ubyteArrayOf(0U, 0U, 0U, 0U).toTypedArray(), ubyteArrayOf(0U, 0U, 0U, 0U).toTypedArray() ) - val aes = Aes() + val aes = Aes(AesKey.Aes128Key("2b7e151628aed2a6abf7158809cf4f3c")) fakeState.copyInto(aes.stateMatrix) aes.subBytes() aes.stateMatrix.forEach { @@ -42,7 +42,7 @@ class AesTest { ubyteArrayOf(2U, 3U, 0U, 1U).toTypedArray(), ubyteArrayOf(3U, 0U, 1U, 2U).toTypedArray() ) - val aes = Aes() + val aes = Aes(AesKey.Aes128Key("2b7e151628aed2a6abf7158809cf4f3c")) fakeState.copyInto(aes.stateMatrix) aes.shiftRows() aes.stateMatrix.forEach { @@ -59,7 +59,7 @@ class AesTest { assertTrue { val a = 0x57U val b = 0x83U - val aes = Aes() + val aes = Aes(AesKey.Aes128Key("2b7e151628aed2a6abf7158809cf4f3c")) val c = aes.galoisFieldMultiply(a.toUByte(), b.toUByte()) c == 0xC1U.toUByte() } @@ -67,7 +67,7 @@ class AesTest { assertTrue { val a = 0x57U val b = 0x13U - val aes = Aes() + val aes = Aes(AesKey.Aes128Key("2b7e151628aed2a6abf7158809cf4f3c")) val c = aes.galoisFieldMultiply(a.toUByte(), b.toUByte()) c == 0xFEU.toUByte() } @@ -92,7 +92,7 @@ class AesTest { ubyteArrayOf(0xbcU, 0x9dU, 0x01U, 0xc6U).toTypedArray() ) - val aes = Aes() + val aes = Aes(AesKey.Aes128Key("2b7e151628aed2a6abf7158809cf4f3c")) fakeState.copyInto(aes.stateMatrix) aes.mixColumns() aes.stateMatrix.forEach { @@ -103,4 +103,84 @@ class AesTest { } } + + @Test + fun testKeyExpansion() { + assertTrue { + val key = "2b7e151628aed2a6abf7158809cf4f3c" + val expectedExpandedKey = uintArrayOf( + // @formatter:off + 0x2b7e1516U, 0x28aed2a6U, 0xabf71588U, 0x09cf4f3cU, 0xa0fafe17U, 0x88542cb1U, + 0x23a33939U, 0x2a6c7605U, 0xf2c295f2U, 0x7a96b943U, 0x5935807aU, 0x7359f67fU, + 0x3d80477dU, 0x4716fe3eU, 0x1e237e44U, 0x6d7a883bU, 0xef44a541U, 0xa8525b7fU, + 0xb671253bU, 0xdb0bad00U, 0xd4d1c6f8U, 0x7c839d87U, 0xcaf2b8bcU, 0x11f915bcU, + 0x6d88a37aU, 0x110b3efdU, 0xdbf98641U, 0xca0093fdU, 0x4e54f70eU, 0x5f5fc9f3U, + 0x84a64fb2U, 0x4ea6dc4fU, 0xead27321U, 0xb58dbad2U, 0x312bf560U, 0x7f8d292fU, + 0xac7766f3U, 0x19fadc21U, 0x28d12941U, 0x575c006eU, 0xd014f9a8U, 0xc9ee2589U, + 0xe13f0cc8U, 0xb6630ca6U + // @formatter:on + ).toTypedArray() + + + val aes = Aes(AesKey.Aes128Key(key)) + val result = aes.expandedKey.map { + it.foldIndexed(0U) { index, acc, uByte -> + acc + (uByte.toUInt() shl (24 - index * 8)) + } + }.toTypedArray() + expectedExpandedKey.contentEquals(result) + } + + assertTrue { + val key = "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b" + val expectedExpandedKey = uintArrayOf( + // @formatter:off + 0x8e73b0f7U, 0xda0e6452U, 0xc810f32bU, 0x809079e5U, 0x62f8ead2U, 0x522c6b7bU, + 0xfe0c91f7U, 0x2402f5a5U, 0xec12068eU, 0x6c827f6bU, 0x0e7a95b9U, 0x5c56fec2U, 0x4db7b4bdU, 0x69b54118U, + 0x85a74796U, 0xe92538fdU, 0xe75fad44U, 0xbb095386U, 0x485af057U, 0x21efb14fU, 0xa448f6d9U, 0x4d6dce24U, + 0xaa326360U, 0x113b30e6U, 0xa25e7ed5U, 0x83b1cf9aU, 0x27f93943U, 0x6a94f767U, 0xc0a69407U, 0xd19da4e1U, + 0xec1786ebU, 0x6fa64971U, 0x485f7032U, 0x22cb8755U, 0xe26d1352U, 0x33f0b7b3U, 0x40beeb28U, 0x2f18a259U, + 0x6747d26bU, 0x458c553eU, 0xa7e1466cU, 0x9411f1dfU, 0x821f750aU, 0xad07d753U, 0xca400538U, 0x8fcc5006U, + 0x282d166aU, 0xbc3ce7b5U, 0xe98ba06fU, 0x448c773cU, 0x8ecc7204U, 0x01002202U + // @formatter:on + ).toTypedArray() + + + val aes = Aes(AesKey.Aes192Key(key)) + val result = aes.expandedKey.map { + it.foldIndexed(0U) { index, acc, uByte -> + acc + (uByte.toUInt() shl (24 - index * 8)) + } + }.toTypedArray() + expectedExpandedKey.contentEquals(result) + } + + assertTrue { + val key = "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4" + + val expectedExpandedKey = uintArrayOf( + // @formatter:off + 0x603deb10U, 0x15ca71beU, 0x2b73aef0U, 0x857d7781U, 0x1f352c07U, 0x3b6108d7U, 0x2d9810a3U, 0x0914dff4U, + 0x9ba35411U, 0x8e6925afU, 0xa51a8b5fU, 0x2067fcdeU, 0xa8b09c1aU, 0x93d194cdU, 0xbe49846eU, 0xb75d5b9aU, + 0xd59aecb8U, 0x5bf3c917U, 0xfee94248U, 0xde8ebe96U, 0xb5a9328aU, 0x2678a647U, 0x98312229U, 0x2f6c79b3U, + 0x812c81adU, 0xdadf48baU, 0x24360af2U, 0xfab8b464U, 0x98c5bfc9U, 0xbebd198eU, 0x268c3ba7U, 0x09e04214U, + 0x68007bacU, 0xb2df3316U, 0x96e939e4U, 0x6c518d80U, 0xc814e204U, 0x76a9fb8aU, 0x5025c02dU, 0x59c58239U, + 0xde136967U, 0x6ccc5a71U, 0xfa256395U, 0x9674ee15U, 0x5886ca5dU, 0x2e2f31d7U, 0x7e0af1faU, 0x27cf73c3U, + 0x749c47abU, 0x18501ddaU, 0xe2757e4fU, 0x7401905aU, 0xcafaaae3U, 0xe4d59b34U, 0x9adf6aceU, 0xbd10190dU, + 0xfe4890d1U, 0xe6188d0bU, 0x046df344U, 0x706c631eU + // @formatter:on + ).toTypedArray() + + + val aes = Aes(AesKey.Aes256Key(key)) + val result = aes.expandedKey.map { + it.foldIndexed(0U) { index, acc, uByte -> + acc + (uByte.toUInt() shl (24 - index * 8)) + } + }.toTypedArray() + expectedExpandedKey.contentEquals(result) + } + + + } } \ No newline at end of file From 9ffd6a837375de93436fa19b584e77b50ddb4e40 Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Wed, 18 Sep 2019 23:22:16 +0200 Subject: [PATCH 06/16] Add encryption algo --- .../ionspin/kotlin/crypto/symmetric/Aes.kt | 99 ++++++++++++++----- .../ionspin/kotlin/crypto/symmetric/Mode.kt | 27 +++++ .../kotlin/crypto/symmetric/AesTest.kt | 49 +++++---- 3 files changed, 134 insertions(+), 41 deletions(-) create mode 100644 multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Mode.kt diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt index 419665d..b8d272f 100644 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt @@ -4,7 +4,7 @@ package com.ionspin.kotlin.crypto.symmetric * Created by Ugljesa Jovanovic (jovanovic.ugljesa@gmail.com) on 07/Sep/2019 */ @ExperimentalUnsignedTypes -class Aes(val aesKey: AesKey) { +class Aes(val aesKey: AesKey, val input: Array) { companion object { val sBox: UByteArray = ubyteArrayOf( @@ -54,20 +54,33 @@ class Aes(val aesKey: AesKey) { } + init { + if (input.size != 16) { + throw RuntimeException("Invalid input size ${input.size}") + } + } - val state: UByteArray = UByteArray(16) { 0U } - - val stateMatrix: Array> = (0 until 4).map { - Array(4) { 0U } + val state: Array> = (0 until 4).map{ outerCounter -> + Array(4) { innerCounter -> input[innerCounter * 4 + outerCounter] } }.toTypedArray() + val numberOfRounds = when(aesKey) { + is AesKey.Aes128Key -> 10 + is AesKey.Aes192Key -> 12 + is AesKey.Aes256Key -> 14 + } + val expandedKey: Array> = expandKey() + + var round = 0 + + fun subBytes() { - stateMatrix.forEachIndexed { indexRow, row -> + state.forEachIndexed { indexRow, row -> row.forEachIndexed { indexColumn, element -> - stateMatrix[indexRow][indexColumn] = getSBoxValue(element) + state[indexRow][indexColumn] = getSBoxValue(element) } } } @@ -79,10 +92,10 @@ class Aes(val aesKey: AesKey) { } fun shiftRows() { - stateMatrix[0] = arrayOf(stateMatrix[0][0], stateMatrix[0][1], stateMatrix[0][2], stateMatrix[0][3]) - stateMatrix[1] = arrayOf(stateMatrix[1][1], stateMatrix[1][2], stateMatrix[1][3], stateMatrix[1][0]) - stateMatrix[2] = arrayOf(stateMatrix[2][2], stateMatrix[2][3], stateMatrix[2][0], stateMatrix[2][1]) - stateMatrix[3] = arrayOf(stateMatrix[3][3], stateMatrix[3][0], stateMatrix[3][1], stateMatrix[3][2]) + state[0] = arrayOf(state[0][0], state[0][1], state[0][2], state[0][3]) + state[1] = arrayOf(state[1][1], state[1][2], state[1][3], state[1][0]) + state[2] = arrayOf(state[2][2], state[2][3], state[2][0], state[2][1]) + state[3] = arrayOf(state[3][3], state[3][0], state[3][1], state[3][2]) } fun mixColumns() { @@ -91,18 +104,18 @@ class Aes(val aesKey: AesKey) { }.toTypedArray() for (c in 0..3) { - stateMixed[0][c] = (2U gfm stateMatrix[0][c]) xor galoisFieldMultiply( + stateMixed[0][c] = (2U gfm state[0][c]) xor galoisFieldMultiply( 3U, - stateMatrix[1][c] - ) xor stateMatrix[2][c] xor stateMatrix[3][c] + state[1][c] + ) xor state[2][c] xor state[3][c] stateMixed[1][c] = - stateMatrix[0][c] xor (2U gfm stateMatrix[1][c]) xor (3U gfm stateMatrix[2][c]) xor stateMatrix[3][c] + state[0][c] xor (2U gfm state[1][c]) xor (3U gfm state[2][c]) xor state[3][c] stateMixed[2][c] = - stateMatrix[0][c] xor stateMatrix[1][c] xor (2U gfm stateMatrix[2][c]) xor (3U gfm stateMatrix[3][c]) + state[0][c] xor state[1][c] xor (2U gfm state[2][c]) xor (3U gfm state[3][c]) stateMixed[3][c] = - 3U gfm stateMatrix[0][c] xor stateMatrix[1][c] xor stateMatrix[2][c] xor (2U gfm stateMatrix[3][c]) + 3U gfm state[0][c] xor state[1][c] xor state[2][c] xor (2U gfm state[3][c]) } - stateMixed.copyInto(stateMatrix) + stateMixed.copyInto(state) } fun galoisFieldAdd(first: UByte, second: UByte): UByte { @@ -131,6 +144,13 @@ class Aes(val aesKey: AesKey) { fun addRoundKey() { + for (i in 0 until 4) { + state[0][i] = state[0][i] xor expandedKey[round * 4 + i][0] + state[1][i] = state[1][i] xor expandedKey[round * 4 + i][1] + state[2][i] = state[2][i] xor expandedKey[round * 4 + i][2] + state[3][i] = state[3][i] xor expandedKey[round * 4 + i][3] + } + round++ } infix fun UInt.gfm(second: UByte): UByte { @@ -138,7 +158,7 @@ class Aes(val aesKey: AesKey) { } fun expandKey(): Array> { - val expandedKey = (0 until 4 * (aesKey.numberOfRounds + 1)).map { + val expandedKey = (0 until 4 * (numberOfRounds + 1)).map { Array(4) { 0U } }.toTypedArray() // First round @@ -149,7 +169,7 @@ class Aes(val aesKey: AesKey) { expandedKey[i][3] = aesKey.keyArray[i * 4 + 3] } - for (i in aesKey.numberOf32BitWords until 4 * (aesKey.numberOfRounds + 1)) { + for (i in aesKey.numberOf32BitWords until 4 * (numberOfRounds + 1)) { val temp = expandedKey[i - 1].copyOf() if (i % aesKey.numberOf32BitWords == 0) { //RotWord @@ -179,15 +199,44 @@ class Aes(val aesKey: AesKey) { } return expandedKey } + + fun encrypt() : Array { + addRoundKey() + + for (i in 0 until numberOfRounds - 1) { + subBytes() + shiftRows() + mixColumns() + addRoundKey() + } + + subBytes() + shiftRows() + addRoundKey() + return state.flatten().toTypedArray() + } + + fun decrypt() : Array { + return ubyteArrayOf().toTypedArray() + } + + fun printState() { + println() + state.forEach { + println(it.joinToString(separator = " ") { it.toString(16) }) + } + } + + } -sealed class AesKey(val key: String, val keyLength: Int, val numberOfRounds: Int) { +sealed class AesKey(val key: String, val keyLength: Int) { val keyArray: Array = key.chunked(2).map { it.toUByte(16) }.toTypedArray() val numberOf32BitWords = keyLength / 32 - class Aes128Key(key: String) : AesKey(key, 128, 10) - class Aes192Key(key: String) : AesKey(key, 192, 12) - class Aes256Key(key: String) : AesKey(key, 256, 14) + class Aes128Key(key: String) : AesKey(key, 128) + class Aes192Key(key: String) : AesKey(key, 192) + class Aes256Key(key: String) : AesKey(key, 256) init { checkKeyLength(key, keyLength) @@ -199,3 +248,5 @@ sealed class AesKey(val key: String, val keyLength: Int, val numberOfRounds: Int } } } + + diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Mode.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Mode.kt new file mode 100644 index 0000000..a0d43f9 --- /dev/null +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Mode.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.symmetric + +/** + * Created by Ugljesa Jovanovic + * ugljesa.jovanovic@ionspin.com + * on 18-Sep-2019 + */ + +enum class Mode { + ENCRYPT, DECRYPT +} \ No newline at end of file diff --git a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesTest.kt b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesTest.kt index e97c21b..bab9e23 100644 --- a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesTest.kt +++ b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesTest.kt @@ -9,6 +9,9 @@ import kotlin.test.assertTrue @ExperimentalUnsignedTypes class AesTest { + val irrelevantKey = "01234567890123345678901234567890" + val irrelevantInput = UByteArray(16) { 0U }.toTypedArray() + @Test fun testSubBytes() { val fakeState = arrayOf( @@ -17,14 +20,14 @@ class AesTest { ubyteArrayOf(0U, 0U, 0U, 0U).toTypedArray(), ubyteArrayOf(0U, 0U, 0U, 0U).toTypedArray() ) - val aes = Aes(AesKey.Aes128Key("2b7e151628aed2a6abf7158809cf4f3c")) - fakeState.copyInto(aes.stateMatrix) + val aes = Aes(AesKey.Aes128Key(irrelevantKey), irrelevantInput) + fakeState.copyInto(aes.state) aes.subBytes() - aes.stateMatrix.forEach { + aes.state.forEach { println(it.joinToString { it.toString(16) }) } assertTrue { - aes.stateMatrix[0][0] == 0xEDU.toUByte() + aes.state[0][0] == 0xEDU.toUByte() } } @@ -42,14 +45,14 @@ class AesTest { ubyteArrayOf(2U, 3U, 0U, 1U).toTypedArray(), ubyteArrayOf(3U, 0U, 1U, 2U).toTypedArray() ) - val aes = Aes(AesKey.Aes128Key("2b7e151628aed2a6abf7158809cf4f3c")) - fakeState.copyInto(aes.stateMatrix) + val aes = Aes(AesKey.Aes128Key(irrelevantKey), irrelevantInput) + fakeState.copyInto(aes.state) aes.shiftRows() - aes.stateMatrix.forEach { + aes.state.forEach { println(it.joinToString { it.toString(16) }) } assertTrue { - aes.stateMatrix.contentDeepEquals(expectedState) + aes.state.contentDeepEquals(expectedState) } } @@ -59,7 +62,7 @@ class AesTest { assertTrue { val a = 0x57U val b = 0x83U - val aes = Aes(AesKey.Aes128Key("2b7e151628aed2a6abf7158809cf4f3c")) + val aes = Aes(AesKey.Aes128Key(irrelevantKey), irrelevantInput) val c = aes.galoisFieldMultiply(a.toUByte(), b.toUByte()) c == 0xC1U.toUByte() } @@ -67,7 +70,7 @@ class AesTest { assertTrue { val a = 0x57U val b = 0x13U - val aes = Aes(AesKey.Aes128Key("2b7e151628aed2a6abf7158809cf4f3c")) + val aes = Aes(AesKey.Aes128Key(irrelevantKey), irrelevantInput) val c = aes.galoisFieldMultiply(a.toUByte(), b.toUByte()) c == 0xFEU.toUByte() } @@ -92,14 +95,14 @@ class AesTest { ubyteArrayOf(0xbcU, 0x9dU, 0x01U, 0xc6U).toTypedArray() ) - val aes = Aes(AesKey.Aes128Key("2b7e151628aed2a6abf7158809cf4f3c")) - fakeState.copyInto(aes.stateMatrix) + val aes = Aes(AesKey.Aes128Key(irrelevantKey), irrelevantInput) + fakeState.copyInto(aes.state) aes.mixColumns() - aes.stateMatrix.forEach { + aes.state.forEach { println(it.joinToString { it.toString(16) }) } assertTrue { - aes.stateMatrix.contentDeepEquals(expectedState) + aes.state.contentDeepEquals(expectedState) } } @@ -122,7 +125,7 @@ class AesTest { ).toTypedArray() - val aes = Aes(AesKey.Aes128Key(key)) + val aes = Aes(AesKey.Aes128Key(key), irrelevantInput) val result = aes.expandedKey.map { it.foldIndexed(0U) { index, acc, uByte -> acc + (uByte.toUInt() shl (24 - index * 8)) @@ -146,7 +149,7 @@ class AesTest { ).toTypedArray() - val aes = Aes(AesKey.Aes192Key(key)) + val aes = Aes(AesKey.Aes192Key(key), irrelevantInput) val result = aes.expandedKey.map { it.foldIndexed(0U) { index, acc, uByte -> acc + (uByte.toUInt() shl (24 - index * 8)) @@ -172,7 +175,7 @@ class AesTest { ).toTypedArray() - val aes = Aes(AesKey.Aes256Key(key)) + val aes = Aes(AesKey.Aes256Key(key), irrelevantInput) val result = aes.expandedKey.map { it.foldIndexed(0U) { index, acc, uByte -> acc + (uByte.toUInt() shl (24 - index * 8)) @@ -181,6 +184,18 @@ class AesTest { expectedExpandedKey.contentEquals(result) } + } + @Test + fun testEncryption() { + val input = "3243f6a8885a308d313198a2e0370734" + val key = "2b7e151628aed2a6abf7158809cf4f3c" + val expectedResult = "3902dc1925dc116a8409850b1dfb9732" + + val aes = Aes(AesKey.Aes128Key(key), input.chunked(2).map { it.toInt(16).toUByte() }.toTypedArray()) + val result = aes.encrypt() + assertTrue { + result.contentEquals(expectedResult.chunked(2).map { it.toInt(16).toUByte() }.toTypedArray()) + } } } \ No newline at end of file From 1a91d90f419546baa3fc0dc4c19198451855b552 Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Thu, 19 Sep 2019 23:53:23 +0200 Subject: [PATCH 07/16] Added decryption --- README.md | 14 +- .../ionspin/kotlin/crypto/symmetric/Aes.kt | 142 +++++++++++++++--- .../kotlin/crypto/symmetric/AesTest.kt | 51 +++++-- 3 files changed, 177 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 0aeaf5b..673ee68 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,16 @@ After that tenative plan is to add 25519 curve based signing and key exchange ne ## Should I use this in production? -No, it's untested and unproven. +No. + +## Should I use this in code that is critical in any way, shape or form? + +No. + +## Why? + +This is an experimental implementation, mostly for expanding personal understanding of cryptography. +It's not peer reviewed, not guaranteed to be bug free, and not guaranteed to be secure. ## Supported @@ -26,6 +35,9 @@ No, it's untested and unproven. * SHA512 * SHA256 +## Symmetric cipher +* AES + More to come. ## Integration diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt index b8d272f..aa18506 100644 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt @@ -4,9 +4,9 @@ package com.ionspin.kotlin.crypto.symmetric * Created by Ugljesa Jovanovic (jovanovic.ugljesa@gmail.com) on 07/Sep/2019 */ @ExperimentalUnsignedTypes -class Aes(val aesKey: AesKey, val input: Array) { +internal class Aes internal constructor(val aesKey: AesKey, val input: Array) { companion object { - val sBox: UByteArray = + private val sBox: UByteArray = ubyteArrayOf( // @formatter:off 0x63U, 0x7cU, 0x77U, 0x7bU, 0xf2U, 0x6bU, 0x6fU, 0xc5U, 0x30U, 0x01U, 0x67U, 0x2bU, 0xfeU, 0xd7U, 0xabU, 0x76U, @@ -28,7 +28,7 @@ class Aes(val aesKey: AesKey, val input: Array) { // @formatter:on ) - val inverseSBox: UByteArray = + private val inverseSBox: UByteArray = ubyteArrayOf( // @formatter:off 0x52U, 0x09U, 0x6aU, 0xd5U, 0x30U, 0x36U, 0xa5U, 0x38U, 0xbfU, 0x40U, 0xa3U, 0x9eU, 0x81U, 0xf3U, 0xd7U, 0xfbU, @@ -52,6 +52,14 @@ class Aes(val aesKey: AesKey, val input: Array) { val rcon: UByteArray = ubyteArrayOf(0x8DU, 0x01U, 0x02U, 0x04U, 0x08U, 0x10U, 0x20U, 0x40U, 0x80U, 0x1BU, 0x36U) + fun encrypt(aesKey: AesKey, input: Array): Array { + return Aes(aesKey, input).encrypt() + } + + fun decrypt(aesKey: AesKey, input: Array): Array { + return Aes(aesKey, input).decrypt() + } + } init { @@ -60,11 +68,11 @@ class Aes(val aesKey: AesKey, val input: Array) { } } - val state: Array> = (0 until 4).map{ outerCounter -> + val state: Array> = (0 until 4).map { outerCounter -> Array(4) { innerCounter -> input[innerCounter * 4 + outerCounter] } }.toTypedArray() - val numberOfRounds = when(aesKey) { + val numberOfRounds = when (aesKey) { is AesKey.Aes128Key -> 10 is AesKey.Aes192Key -> 12 is AesKey.Aes256Key -> 14 @@ -73,7 +81,6 @@ class Aes(val aesKey: AesKey, val input: Array) { val expandedKey: Array> = expandKey() - var round = 0 @@ -91,6 +98,20 @@ class Aes(val aesKey: AesKey, val input: Array) { return sBox[firstDigit * 16 + secondDigit] } + fun inverseSubBytes() { + state.forEachIndexed { indexRow, row -> + row.forEachIndexed { indexColumn, element -> + state[indexRow][indexColumn] = getInverseSBoxValue(element) + } + } + } + + fun getInverseSBoxValue(element: UByte): UByte { + val firstDigit = (element / 16U).toInt() + val secondDigit = (element % 16U).toInt() + return inverseSBox[firstDigit * 16 + secondDigit] + } + fun shiftRows() { state[0] = arrayOf(state[0][0], state[0][1], state[0][2], state[0][3]) state[1] = arrayOf(state[1][1], state[1][2], state[1][3], state[1][0]) @@ -98,22 +119,40 @@ class Aes(val aesKey: AesKey, val input: Array) { state[3] = arrayOf(state[3][3], state[3][0], state[3][1], state[3][2]) } + fun inversShiftRows() { + state[0] = arrayOf(state[0][0], state[0][1], state[0][2], state[0][3]) + state[1] = arrayOf(state[1][3], state[1][0], state[1][1], state[1][2]) + state[2] = arrayOf(state[2][2], state[2][3], state[2][0], state[2][1]) + state[3] = arrayOf(state[3][1], state[3][2], state[3][3], state[3][0]) + } + fun mixColumns() { val stateMixed: Array> = (0 until 4).map { Array(4) { 0U } }.toTypedArray() for (c in 0..3) { - stateMixed[0][c] = (2U gfm state[0][c]) xor galoisFieldMultiply( - 3U, - state[1][c] - ) xor state[2][c] xor state[3][c] + stateMixed[0][c] = (2U gfm state[0][c]) xor (3U gfm state[1][c]) xor state[2][c] xor state[3][c] + stateMixed[1][c] = state[0][c] xor (2U gfm state[1][c]) xor (3U gfm state[2][c]) xor state[3][c] + stateMixed[2][c] = state[0][c] xor state[1][c] xor (2U gfm state[2][c]) xor (3U gfm state[3][c]) + stateMixed[3][c] = 3U gfm state[0][c] xor state[1][c] xor state[2][c] xor (2U gfm state[3][c]) + } + stateMixed.copyInto(state) + } + + fun inverseMixColumns() { + val stateMixed: Array> = (0 until 4).map { + Array(4) { 0U } + }.toTypedArray() + for (c in 0..3) { + stateMixed[0][c] = + (0x0eU gfm state[0][c]) xor (0x0bU gfm state[1][c]) xor (0x0dU gfm state[2][c]) xor (0x09U gfm state[3][c]) stateMixed[1][c] = - state[0][c] xor (2U gfm state[1][c]) xor (3U gfm state[2][c]) xor state[3][c] + (0x09U gfm state[0][c]) xor (0x0eU gfm state[1][c]) xor (0x0bU gfm state[2][c]) xor (0x0dU gfm state[3][c]) stateMixed[2][c] = - state[0][c] xor state[1][c] xor (2U gfm state[2][c]) xor (3U gfm state[3][c]) + (0x0dU gfm state[0][c]) xor (0x09U gfm state[1][c]) xor (0x0eU gfm state[2][c]) xor (0x0bU gfm state[3][c]) stateMixed[3][c] = - 3U gfm state[0][c] xor state[1][c] xor state[2][c] xor (2U gfm state[3][c]) + (0x0bU gfm state[0][c]) xor (0x0dU gfm state[1][c]) xor (0x09U gfm state[2][c]) xor (0x0eU gfm state[3][c]) } stateMixed.copyInto(state) } @@ -153,6 +192,16 @@ class Aes(val aesKey: AesKey, val input: Array) { round++ } + fun inverseAddRoundKey() { + for (i in 0 until 4) { + state[0][i] = state[0][i] xor expandedKey[round * 4 + i][0] + state[1][i] = state[1][i] xor expandedKey[round * 4 + i][1] + state[2][i] = state[2][i] xor expandedKey[round * 4 + i][2] + state[3][i] = state[3][i] xor expandedKey[round * 4 + i][3] + } + round-- + } + infix fun UInt.gfm(second: UByte): UByte { return galoisFieldMultiply(this.toUByte(), second) } @@ -200,30 +249,85 @@ class Aes(val aesKey: AesKey, val input: Array) { return expandedKey } - fun encrypt() : Array { + fun encrypt(): Array { + printState() addRoundKey() - + printState() for (i in 0 until numberOfRounds - 1) { subBytes() + printState() shiftRows() + printState() mixColumns() + printState() addRoundKey() + printState() } subBytes() + printState() shiftRows() + printState() addRoundKey() - return state.flatten().toTypedArray() + printState() + val transposedMatrix = (0 until 4).map { outerCounter -> + Array(4) { 0U } + }.toTypedArray() + for (i in 0 until 4) { + for (j in 0 until 4) { + transposedMatrix[i][j] = state[j][i] + } + } +// printState(transposedMatrix) + return transposedMatrix.flatten().toTypedArray() } - fun decrypt() : Array { - return ubyteArrayOf().toTypedArray() + fun decrypt(): Array { + round = numberOfRounds + printState() + inverseAddRoundKey() + printState() + for (i in 0 until numberOfRounds - 1) { + inversShiftRows() + printState() + inverseSubBytes() + printState() + inverseAddRoundKey() + printState() + inverseMixColumns() + printState() + } + + inversShiftRows() + printState() + inverseSubBytes() + printState() + inverseAddRoundKey() + printState() + + val transposedMatrix = (0 until 4).map { outerCounter -> + Array(4) { 0U } + }.toTypedArray() + for (i in 0 until 4) { + for (j in 0 until 4) { + transposedMatrix[i][j] = state[j][i] + } + } + printState(transposedMatrix) + return transposedMatrix.flatten().toTypedArray() } fun printState() { println() state.forEach { - println(it.joinToString(separator = " ") { it.toString(16) }) + println(it.joinToString(separator = " ") { it.toString(16) }) + } + } + + fun printState(specific : Array>) { + println() + specific.forEach { + println(it.joinToString(separator = " ") { it.toString(16) }) } } diff --git a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesTest.kt b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesTest.kt index bab9e23..8f4e77e 100644 --- a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesTest.kt +++ b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesTest.kt @@ -23,9 +23,6 @@ class AesTest { val aes = Aes(AesKey.Aes128Key(irrelevantKey), irrelevantInput) fakeState.copyInto(aes.state) aes.subBytes() - aes.state.forEach { - println(it.joinToString { it.toString(16) }) - } assertTrue { aes.state[0][0] == 0xEDU.toUByte() } @@ -48,9 +45,6 @@ class AesTest { val aes = Aes(AesKey.Aes128Key(irrelevantKey), irrelevantInput) fakeState.copyInto(aes.state) aes.shiftRows() - aes.state.forEach { - println(it.joinToString { it.toString(16) }) - } assertTrue { aes.state.contentDeepEquals(expectedState) } @@ -98,9 +92,6 @@ class AesTest { val aes = Aes(AesKey.Aes128Key(irrelevantKey), irrelevantInput) fakeState.copyInto(aes.state) aes.mixColumns() - aes.state.forEach { - println(it.joinToString { it.toString(16) }) - } assertTrue { aes.state.contentDeepEquals(expectedState) } @@ -190,7 +181,7 @@ class AesTest { fun testEncryption() { val input = "3243f6a8885a308d313198a2e0370734" val key = "2b7e151628aed2a6abf7158809cf4f3c" - val expectedResult = "3902dc1925dc116a8409850b1dfb9732" + val expectedResult = "3925841d02dc09fbdc118597196a0b32" val aes = Aes(AesKey.Aes128Key(key), input.chunked(2).map { it.toInt(16).toUByte() }.toTypedArray()) val result = aes.encrypt() @@ -198,4 +189,44 @@ class AesTest { result.contentEquals(expectedResult.chunked(2).map { it.toInt(16).toUByte() }.toTypedArray()) } } + + @Test + fun testDecryption() { + val input = "3243f6a8885a308d313198a2e0370734" + val key = "2b7e151628aed2a6abf7158809cf4f3c" + val expectedResult = "3925841d02dc09fbdc118597196a0b32" + val original = input.chunked(2).map { it.toInt(16).toUByte() }.toTypedArray() + val aes = Aes(AesKey.Aes128Key(key), original) + val encrypted = aes.encrypt() + assertTrue { + encrypted.contentEquals(expectedResult.chunked(2).map { it.toInt(16).toUByte() }.toTypedArray()) + } + val decrypted = Aes.decrypt(AesKey.Aes128Key(key), encrypted) + assertTrue { + decrypted.contentEquals(original) + } + } + + @Test + fun testDecryption2() { + val input = "00112233445566778899aabbccddeeff" + val key = "000102030405060708090a0b0c0d0e0f" + val expectedResult = "69c4e0d86a7b0430d8cdb78070b4c55a" + val original = input.chunked(2).map { it.toInt(16).toUByte() }.toTypedArray() + val aes = Aes(AesKey.Aes128Key(key), original) + val encrypted = aes.encrypt() + assertTrue { + encrypted.contentEquals(expectedResult.chunked(2).map { it.toInt(16).toUByte() }.toTypedArray()) + } + val aesDec = Aes(AesKey.Aes128Key(key), encrypted) + val decrypted = aesDec.decrypt() + assertTrue { + aesDec.expandedKey.contentDeepEquals(aes.expandedKey) + } + assertTrue { + decrypted.contentDeepEquals(original) + } + } + + } \ No newline at end of file From d3ed17b1c008e80f6645036ebb3ffc3020a49fd8 Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Sat, 21 Sep 2019 19:19:07 +0200 Subject: [PATCH 08/16] Adding cbc, added tests --- .../kotlin/com/ionspin/kotlin/crypto/SRNG.kt | 26 +++++ .../kotlin/com/ionspin/kotlin/crypto/Util.kt | 8 ++ .../ionspin/kotlin/crypto/symmetric/Aes.kt | 14 ++- .../ionspin/kotlin/crypto/symmetric/AesCbc.kt | 110 ++++++++++++++++++ .../kotlin/crypto/symmetric/AesTest.kt | 85 ++++++++++---- .../kotlin/com/ionspin/kotlin/crypto/SRNG.kt | 28 +++++ .../kotlin/com/ionspin/kotlin/crypto/SRNG.kt | 28 +++++ .../kotlin/com/ionspin/kotlin/crypto/SRNG.kt | 28 +++++ .../ionspin/kotlin/bignum/integer/Placeholder | 0 .../kotlin/com/ionspin/kotlin/crypto/SRNG.kt | 28 +++++ 10 files changed, 329 insertions(+), 26 deletions(-) create mode 100644 multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/SRNG.kt create mode 100644 multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/AesCbc.kt create mode 100644 multiplatform-crypto/src/iosCommonMain/kotlin/com/ionspin/kotlin/crypto/SRNG.kt create mode 100644 multiplatform-crypto/src/jsMain/kotlin/com/ionspin/kotlin/crypto/SRNG.kt create mode 100644 multiplatform-crypto/src/jvmMain/kotlin/com/ionspin/kotlin/crypto/SRNG.kt delete mode 100644 multiplatform-crypto/src/nativeMain/kotlin/com/ionspin/kotlin/bignum/integer/Placeholder create mode 100644 multiplatform-crypto/src/nativeMain/kotlin/com/ionspin/kotlin/crypto/SRNG.kt diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/SRNG.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/SRNG.kt new file mode 100644 index 0000000..174801d --- /dev/null +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/SRNG.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 + +/** + * Created by Ugljesa Jovanovic + * ugljesa.jovanovic@ionspin.com + * on 21-Sep-2019 + */ +expect object SRNG { + fun getRandomBytes(amount : Int) : Array +} \ No newline at end of file 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 0a59b5f..64e3783 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 @@ -63,4 +63,12 @@ infix fun UInt.rotateRight(places: Int): UInt { @ExperimentalUnsignedTypes infix fun ULong.rotateRight(places: Int): ULong { return (this shr places) xor (this shl (64 - places)) +} + +@ExperimentalUnsignedTypes +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() } \ No newline at end of file diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt index aa18506..427cc74 100644 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt @@ -6,6 +6,8 @@ package com.ionspin.kotlin.crypto.symmetric @ExperimentalUnsignedTypes internal class Aes internal constructor(val aesKey: AesKey, val input: Array) { companion object { + private val debug = false + private val sBox: UByteArray = ubyteArrayOf( // @formatter:off @@ -317,14 +319,22 @@ internal class Aes internal constructor(val aesKey: AesKey, val input: Array>) { + private fun printState(specific : Array>) { + if (!debug) { + return + } println() specific.forEach { println(it.joinToString(separator = " ") { it.toString(16) }) 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 new file mode 100644 index 0000000..cdbfd30 --- /dev/null +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/AesCbc.kt @@ -0,0 +1,110 @@ +/* + * 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.symmetric + +import com.ionspin.kotlin.crypto.SRNG +import com.ionspin.kotlin.crypto.xor + +/** + * Created by Ugljesa Jovanovic + * ugljesa.jovanovic@ionspin.com + * on 21-Sep-2019 + */ +@ExperimentalUnsignedTypes +class AesCbc (val aesKey: AesKey) { + + companion object { + val BLOCK_BYTES = 16 + } + + var currentOutput : Array = arrayOf() + val iv = SRNG.getRandomBytes(16) + + val output = MutableList>(0) { arrayOf() } + + val buffer : Array = UByteArray(16) { 0U }.toTypedArray() + var bufferCounter = 0 + + +// fun addData(data : UByteArray) { +// //Padding +// when { +// bufferCounter + data.size < BLOCK_BYTES -> appendToBuffer(data, bufferCounter) +// bufferCounter + data.size >= BLOCK_BYTES -> { +// val chunked = data.chunked(BLOCK_BYTES) +// chunked.forEach { chunk -> +// if (bufferCounter + chunk.size < BLOCK_BYTES) { +// appendToBuffer(chunk, bufferCounter) +// } else { +// chunk.copyInto( +// destination = buffer, +// destinationOffset = bufferCounter, +// startIndex = 0, +// endIndex = BLOCK_BYTES - bufferCounter +// ) +// counter += BLOCK_BYTES +// consumeBlock(buffer) +// buffer = Array(BLOCK_BYTES) { +// when (it) { +// in (0 until (chunk.size - (BLOCK_BYTES - bufferCounter))) -> { +// chunk[it + (BLOCK_BYTES - bufferCounter)] +// } +// else -> { +// 0U +// } +// } +// +// } +// bufferCounter = chunk.size - (BLOCK_BYTES - bufferCounter) +// } +// } +// +// } +// data.size < 16 -> { +// val paddingSize = 16 - data.size +// val padding = UByteArray(16 - data.size) { paddingSize.toUByte() } +// output += processBlock(data + padding) +// } +// data.size == 16 -> { +// +// } +// data.size > 16 -> { +// +// } +// } +// +// if (data.size < 16) { +// +// } +// +// } + + private fun appendToBuffer(array: Array, start: Int) { + array.copyInto(destination = buffer, destinationOffset = start, startIndex = 0, endIndex = array.size) + bufferCounter += array.size + } + + private fun processBlock(data : Array) : Array { + if (currentOutput.isEmpty()) { + currentOutput = Aes.encrypt(aesKey, data xor iv) + } else { + currentOutput = Aes.encrypt(aesKey, data xor currentOutput) + } + return currentOutput + } + +} \ No newline at end of file diff --git a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesTest.kt b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesTest.kt index 8f4e77e..1ac2f90 100644 --- a/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesTest.kt +++ b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesTest.kt @@ -191,42 +191,79 @@ class AesTest { } @Test - fun testDecryption() { - val input = "3243f6a8885a308d313198a2e0370734" - val key = "2b7e151628aed2a6abf7158809cf4f3c" - val expectedResult = "3925841d02dc09fbdc118597196a0b32" - val original = input.chunked(2).map { it.toInt(16).toUByte() }.toTypedArray() - val aes = Aes(AesKey.Aes128Key(key), original) - val encrypted = aes.encrypt() - assertTrue { - encrypted.contentEquals(expectedResult.chunked(2).map { it.toInt(16).toUByte() }.toTypedArray()) - } - val decrypted = Aes.decrypt(AesKey.Aes128Key(key), encrypted) + fun testEncryptionAndDecryption() { assertTrue { + val input = "3243f6a8885a308d313198a2e0370734" + val key = "2b7e151628aed2a6abf7158809cf4f3c" + val expectedResult = "3925841d02dc09fbdc118597196a0b32" + val original = input.chunked(2).map { it.toInt(16).toUByte() }.toTypedArray() + val aes = Aes(AesKey.Aes128Key(key), original) + val encrypted = aes.encrypt() + assertTrue { + encrypted.contentEquals(expectedResult.chunked(2).map { it.toInt(16).toUByte() }.toTypedArray()) + } + val decrypted = Aes.decrypt(AesKey.Aes128Key(key), encrypted) + decrypted.contentEquals(original) } - } + assertTrue { + val input = "00112233445566778899aabbccddeeff" + val key = "000102030405060708090a0b0c0d0e0f" + val expectedResult = "69c4e0d86a7b0430d8cdb78070b4c55a" + val original = input.chunked(2).map { it.toInt(16).toUByte() }.toTypedArray() + val aes = Aes(AesKey.Aes128Key(key), original) + val encrypted = aes.encrypt() + assertTrue { + encrypted.contentEquals(expectedResult.chunked(2).map { it.toInt(16).toUByte() }.toTypedArray()) + } + val aesDec = Aes(AesKey.Aes128Key(key), encrypted) + val decrypted = aesDec.decrypt() + assertTrue { + aesDec.expandedKey.contentDeepEquals(aes.expandedKey) + } + decrypted.contentDeepEquals(original) + } - @Test - fun testDecryption2() { - val input = "00112233445566778899aabbccddeeff" - val key = "000102030405060708090a0b0c0d0e0f" - val expectedResult = "69c4e0d86a7b0430d8cdb78070b4c55a" - val original = input.chunked(2).map { it.toInt(16).toUByte() }.toTypedArray() - val aes = Aes(AesKey.Aes128Key(key), original) - val encrypted = aes.encrypt() assertTrue { - encrypted.contentEquals(expectedResult.chunked(2).map { it.toInt(16).toUByte() }.toTypedArray()) + val input = "00112233445566778899aabbccddeeff" + val key = "000102030405060708090a0b0c0d0e0f" + val expectedResult = "69c4e0d86a7b0430d8cdb78070b4c55a" + val original = input.chunked(2).map { it.toInt(16).toUByte() }.toTypedArray() + val encrypted = Aes.encrypt(AesKey.Aes128Key(key), original) + assertTrue { + encrypted.contentEquals(expectedResult.chunked(2).map { it.toInt(16).toUByte() }.toTypedArray()) + } + val decrypted = Aes.decrypt(AesKey.Aes128Key(key), encrypted) + decrypted.contentDeepEquals(original) } - val aesDec = Aes(AesKey.Aes128Key(key), encrypted) - val decrypted = aesDec.decrypt() + assertTrue { - aesDec.expandedKey.contentDeepEquals(aes.expandedKey) + val input = "00112233445566778899aabbccddeeff" + val key = "000102030405060708090a0b0c0d0e0f1011121314151617" + val expectedResult = "dda97ca4864cdfe06eaf70a0ec0d7191" + val original = input.chunked(2).map { it.toInt(16).toUByte() }.toTypedArray() + val encrypted = Aes.encrypt(AesKey.Aes192Key(key), original) + assertTrue { + encrypted.contentEquals(expectedResult.chunked(2).map { it.toInt(16).toUByte() }.toTypedArray()) + } + val decrypted = Aes.decrypt(AesKey.Aes192Key(key), encrypted) + decrypted.contentDeepEquals(original) } + assertTrue { + val input = "00112233445566778899aabbccddeeff" + val key = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" + val expectedResult = "8ea2b7ca516745bfeafc49904b496089" + val original = input.chunked(2).map { it.toInt(16).toUByte() }.toTypedArray() + val encrypted = Aes.encrypt(AesKey.Aes256Key(key), original) + assertTrue { + encrypted.contentEquals(expectedResult.chunked(2).map { it.toInt(16).toUByte() }.toTypedArray()) + } + val decrypted = Aes.decrypt(AesKey.Aes256Key(key), encrypted) decrypted.contentDeepEquals(original) } } + } \ No newline at end of file diff --git a/multiplatform-crypto/src/iosCommonMain/kotlin/com/ionspin/kotlin/crypto/SRNG.kt b/multiplatform-crypto/src/iosCommonMain/kotlin/com/ionspin/kotlin/crypto/SRNG.kt new file mode 100644 index 0000000..8e8bbd7 --- /dev/null +++ b/multiplatform-crypto/src/iosCommonMain/kotlin/com/ionspin/kotlin/crypto/SRNG.kt @@ -0,0 +1,28 @@ +/* + * 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 + +/** + * Created by Ugljesa Jovanovic + * ugljesa.jovanovic@ionspin.com + * on 21-Sep-2019 + */ +actual object SRNG { + actual fun getRandomBytes(amount: Int): Array { + TODO("not implemented yet") + } +} \ No newline at end of file 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 new file mode 100644 index 0000000..8e8bbd7 --- /dev/null +++ b/multiplatform-crypto/src/jsMain/kotlin/com/ionspin/kotlin/crypto/SRNG.kt @@ -0,0 +1,28 @@ +/* + * 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 + +/** + * Created by Ugljesa Jovanovic + * ugljesa.jovanovic@ionspin.com + * on 21-Sep-2019 + */ +actual object SRNG { + actual fun getRandomBytes(amount: Int): Array { + TODO("not implemented yet") + } +} \ No newline at end of file diff --git a/multiplatform-crypto/src/jvmMain/kotlin/com/ionspin/kotlin/crypto/SRNG.kt b/multiplatform-crypto/src/jvmMain/kotlin/com/ionspin/kotlin/crypto/SRNG.kt new file mode 100644 index 0000000..8e8bbd7 --- /dev/null +++ b/multiplatform-crypto/src/jvmMain/kotlin/com/ionspin/kotlin/crypto/SRNG.kt @@ -0,0 +1,28 @@ +/* + * 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 + +/** + * Created by Ugljesa Jovanovic + * ugljesa.jovanovic@ionspin.com + * on 21-Sep-2019 + */ +actual object SRNG { + actual fun getRandomBytes(amount: Int): Array { + TODO("not implemented yet") + } +} \ No newline at end of file diff --git a/multiplatform-crypto/src/nativeMain/kotlin/com/ionspin/kotlin/bignum/integer/Placeholder b/multiplatform-crypto/src/nativeMain/kotlin/com/ionspin/kotlin/bignum/integer/Placeholder deleted file mode 100644 index e69de29..0000000 diff --git a/multiplatform-crypto/src/nativeMain/kotlin/com/ionspin/kotlin/crypto/SRNG.kt b/multiplatform-crypto/src/nativeMain/kotlin/com/ionspin/kotlin/crypto/SRNG.kt new file mode 100644 index 0000000..8e8bbd7 --- /dev/null +++ b/multiplatform-crypto/src/nativeMain/kotlin/com/ionspin/kotlin/crypto/SRNG.kt @@ -0,0 +1,28 @@ +/* + * 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 + +/** + * Created by Ugljesa Jovanovic + * ugljesa.jovanovic@ionspin.com + * on 21-Sep-2019 + */ +actual object SRNG { + actual fun getRandomBytes(amount: Int): Array { + TODO("not implemented yet") + } +} \ No newline at end of file From 1345125252f17d679cf7ee083a748b78d920602a Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Sat, 21 Sep 2019 22:20:05 +0200 Subject: [PATCH 09/16] Added aes cbc --- multiplatform-crypto/build.gradle.kts | 1 - .../kotlin/com/ionspin/kotlin/crypto/Util.kt | 16 ++ .../ionspin/kotlin/crypto/symmetric/AesCbc.kt | 174 +++++++++++------- .../com/ionspin/kotlin/crypto/SRNGTest.kt} | 12 +- .../kotlin/crypto/symmetric/AesCbcTest.kt | 67 +++++++ .../kotlin/com/ionspin/kotlin/crypto/SRNG.kt | 3 +- .../kotlin/com/ionspin/kotlin/crypto/SRNG.kt | 8 +- .../kotlin/com/ionspin/kotlin/crypto/SRNG.kt | 15 +- 8 files changed, 226 insertions(+), 70 deletions(-) rename multiplatform-crypto/src/{iosCommonMain/kotlin/com/ionspin/kotlin/crypto/SRNG.kt => commonTest/kotlin/com/ionspin/kotlin/crypto/SRNGTest.kt} (73%) create mode 100644 multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesCbcTest.kt diff --git a/multiplatform-crypto/build.gradle.kts b/multiplatform-crypto/build.gradle.kts index c3f51e7..ea57cb1 100644 --- a/multiplatform-crypto/build.gradle.kts +++ b/multiplatform-crypto/build.gradle.kts @@ -150,7 +150,6 @@ kotlin { val nativeMain by creating { dependsOn(commonMain) dependencies { - implementation(Deps.Native.coroutines) } } val nativeTest by creating { 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 64e3783..1b222ce 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 @@ -71,4 +71,20 @@ infix fun Array.xor(other : Array) : Array { throw RuntimeException("Operands of different sizes are not supported yet") } return this.copyOf().mapIndexed { index, it -> it xor other[index] }.toTypedArray() +} + +@ExperimentalUnsignedTypes +fun String.hexStringToUByteArray() : Array { + return this.chunked(2).map { it.toUByte(16) }.toTypedArray() +} + +@ExperimentalUnsignedTypes +fun Array.toHexString() : String { + return this.joinToString(separator = "") { + if (it <= 0x0FU) { + "0${it.toString(16)}" + } else { + it.toString(16) + } + } } \ No newline at end of file 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 cdbfd30..01a1597 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,6 +17,7 @@ package com.ionspin.kotlin.crypto.symmetric import com.ionspin.kotlin.crypto.SRNG +import com.ionspin.kotlin.crypto.chunked import com.ionspin.kotlin.crypto.xor /** @@ -25,86 +26,133 @@ import com.ionspin.kotlin.crypto.xor * on 21-Sep-2019 */ @ExperimentalUnsignedTypes -class AesCbc (val aesKey: AesKey) { +class AesCbc internal constructor(val aesKey: AesKey, val mode: Mode, initializationVector: Array? = null) { companion object { - val BLOCK_BYTES = 16 + const val BLOCK_BYTES = 16 + + fun encrypt(aesKey: AesKey, data: Array): Array { + val aesCbc = AesCbc(aesKey, Mode.ENCRYPT) + aesCbc.addData(data) + return aesCbc.encrypt() + } + + private fun padToBlock(unpadded: Array): Array { + val paddingSize = 16 - unpadded.size + if (unpadded.size == BLOCK_BYTES) { + return unpadded + } + + if (unpadded.size == BLOCK_BYTES) { + return Array(BLOCK_BYTES) { + BLOCK_BYTES.toUByte() + } + } + + if (unpadded.size > BLOCK_BYTES) { + throw IllegalStateException("Block larger than 128 bytes") + } + + return Array(BLOCK_BYTES) { + when (it) { + in unpadded.indices -> unpadded[it] + else -> paddingSize.toUByte() + } + } + + } } - var currentOutput : Array = arrayOf() - val iv = SRNG.getRandomBytes(16) + var currentOutput: Array = arrayOf() + var previousEncrypted: Array = arrayOf() + val iv = initializationVector ?: SRNG.getRandomBytes(16) val output = MutableList>(0) { arrayOf() } - val buffer : Array = UByteArray(16) { 0U }.toTypedArray() + var buffer: Array = UByteArray(16) { 0U }.toTypedArray() var bufferCounter = 0 + fun addData(data: Array) { + //Padding + when { + bufferCounter + data.size < BLOCK_BYTES -> appendToBuffer(data, bufferCounter) + bufferCounter + data.size >= BLOCK_BYTES -> { + val chunked = data.chunked(BLOCK_BYTES) + chunked.forEach { chunk -> + if (bufferCounter + chunk.size < BLOCK_BYTES) { + appendToBuffer(chunk, bufferCounter) + } else { + chunk.copyInto( + destination = buffer, + destinationOffset = bufferCounter, + startIndex = 0, + endIndex = BLOCK_BYTES - bufferCounter + ) + output += consumeBlock(buffer) + buffer = Array(BLOCK_BYTES) { + when (it) { + in (0 until (chunk.size - (BLOCK_BYTES - bufferCounter))) -> { + chunk[it + (BLOCK_BYTES - bufferCounter)] + } + else -> { + 0U + } + } -// fun addData(data : UByteArray) { -// //Padding -// when { -// bufferCounter + data.size < BLOCK_BYTES -> appendToBuffer(data, bufferCounter) -// bufferCounter + data.size >= BLOCK_BYTES -> { -// val chunked = data.chunked(BLOCK_BYTES) -// chunked.forEach { chunk -> -// if (bufferCounter + chunk.size < BLOCK_BYTES) { -// appendToBuffer(chunk, bufferCounter) -// } else { -// chunk.copyInto( -// destination = buffer, -// destinationOffset = bufferCounter, -// startIndex = 0, -// endIndex = BLOCK_BYTES - bufferCounter -// ) -// counter += BLOCK_BYTES -// consumeBlock(buffer) -// buffer = Array(BLOCK_BYTES) { -// when (it) { -// in (0 until (chunk.size - (BLOCK_BYTES - bufferCounter))) -> { -// chunk[it + (BLOCK_BYTES - bufferCounter)] -// } -// else -> { -// 0U -// } -// } -// -// } -// bufferCounter = chunk.size - (BLOCK_BYTES - bufferCounter) -// } -// } -// -// } -// data.size < 16 -> { -// val paddingSize = 16 - data.size -// val padding = UByteArray(16 - data.size) { paddingSize.toUByte() } -// output += processBlock(data + padding) -// } -// data.size == 16 -> { -// -// } -// data.size > 16 -> { -// -// } -// } -// -// if (data.size < 16) { -// -// } -// -// } + } + bufferCounter = chunk.size - (BLOCK_BYTES - bufferCounter) + } + } + + } + } + + } + + fun encrypt(): Array { + if (bufferCounter > 0) { + val lastBlockPadded = padToBlock(buffer) + if (lastBlockPadded.size > BLOCK_BYTES) { + val chunks = lastBlockPadded.chunked(BLOCK_BYTES) + output += consumeBlock(chunks[0]) + output += consumeBlock(chunks[1]) + } else { + output += consumeBlock(lastBlockPadded) + } + } + return output.reversed().foldRight(Array(0) { 0U }) { arrayOfUBytes, acc -> acc + arrayOfUBytes } + } + + fun decrypt(): Array { + return output.reversed().foldRight(Array(0) { 0U }) { arrayOfUBytes, acc -> acc + arrayOfUBytes } + } private fun appendToBuffer(array: Array, start: Int) { array.copyInto(destination = buffer, destinationOffset = start, startIndex = 0, endIndex = array.size) bufferCounter += array.size } - private fun processBlock(data : Array) : Array { - if (currentOutput.isEmpty()) { - currentOutput = Aes.encrypt(aesKey, data xor iv) - } else { - currentOutput = Aes.encrypt(aesKey, data xor currentOutput) + private fun consumeBlock(data: Array): Array { + return when (mode) { + Mode.ENCRYPT -> { + currentOutput = if (currentOutput.isEmpty()) { + Aes.encrypt(aesKey, data xor iv) + } else { + Aes.encrypt(aesKey, data xor currentOutput) + } + currentOutput + } + Mode.DECRYPT -> { + if (currentOutput.isEmpty()) { + currentOutput = Aes.decrypt(aesKey, data) xor iv + previousEncrypted = data + } else { + currentOutput = Aes.decrypt(aesKey, data) xor previousEncrypted + } + currentOutput + } } - return currentOutput + } } \ No newline at end of file diff --git a/multiplatform-crypto/src/iosCommonMain/kotlin/com/ionspin/kotlin/crypto/SRNG.kt b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/SRNGTest.kt similarity index 73% rename from multiplatform-crypto/src/iosCommonMain/kotlin/com/ionspin/kotlin/crypto/SRNG.kt rename to multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/SRNGTest.kt index 8e8bbd7..fcede56 100644 --- a/multiplatform-crypto/src/iosCommonMain/kotlin/com/ionspin/kotlin/crypto/SRNG.kt +++ b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/SRNGTest.kt @@ -16,13 +16,19 @@ package com.ionspin.kotlin.crypto +import kotlin.test.Test +import kotlin.test.assertTrue + /** * Created by Ugljesa Jovanovic * ugljesa.jovanovic@ionspin.com * on 21-Sep-2019 */ -actual object SRNG { - actual fun getRandomBytes(amount: Int): Array { - TODO("not implemented yet") +class SRNGTest { + @Test + fun testSrng() { + val randomBytes1 = SRNG.getRandomBytes(10) + val randomBytes2 = SRNG.getRandomBytes(10) + assertTrue { !randomBytes1.contentEquals(randomBytes2) } } } \ 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 new file mode 100644 index 0000000..0c676c9 --- /dev/null +++ b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesCbcTest.kt @@ -0,0 +1,67 @@ +/* + * 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.symmetric + +import com.ionspin.kotlin.crypto.hexStringToUByteArray +import com.ionspin.kotlin.crypto.toHexString +import kotlin.test.Test +import kotlin.test.assertTrue + +/** + * Created by Ugljesa Jovanovic + * ugljesa.jovanovic@ionspin.com + * on 21-Sep-2019 + */ +@ExperimentalUnsignedTypes +class AesCbcTest { + + @Test + fun testCbcEncryption() { + val key = "4278b840fb44aaa757c1bf04acbe1a3e" + val iv = "57f02a5c5339daeb0a2908a06ac6393f" + val plaintext = "3c888bbbb1a8eb9f3e9b87acaad986c466e2f7071c83083b8a557971918850e5" + val expectedCipherText = "479c89ec14bc98994e62b2c705b5014e175bd7832e7e60a1e92aac568a861eb7" + val aesCbc = AesCbc(AesKey.Aes128Key(key), mode = Mode.ENCRYPT, initializationVector = iv.hexStringToUByteArray()) + aesCbc.addData(plaintext.hexStringToUByteArray()) + val encrypted = aesCbc.encrypt() + println("Decrypted: ${encrypted.toHexString()}") + assertTrue { + expectedCipherText == encrypted.toHexString() + } + + } + + @Test + fun testCbcDecryption() { + val key = "4278b840fb44aaa757c1bf04acbe1a3e" + val iv = "57f02a5c5339daeb0a2908a06ac6393f" + val cipherText = "479c89ec14bc98994e62b2c705b5014e175bd7832e7e60a1e92aac568a861eb7" + val expectedPlainText = "3c888bbbb1a8eb9f3e9b87acaad986c466e2f7071c83083b8a557971918850e5" + val aesCbc = AesCbc(AesKey.Aes128Key(key), mode = Mode.DECRYPT, initializationVector = iv.hexStringToUByteArray()) + aesCbc.addData(cipherText.hexStringToUByteArray()) + val decrypted = aesCbc.decrypt() + println("Encrypted: ${decrypted.toHexString()}") + assertTrue { + expectedPlainText == decrypted.toHexString() + } + + } + + + + +} \ No newline at end of file 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 8e8bbd7..36cd9e2 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 @@ -22,7 +22,8 @@ package com.ionspin.kotlin.crypto * on 21-Sep-2019 */ actual object SRNG { + var counter = 0 actual fun getRandomBytes(amount: Int): Array { - TODO("not implemented yet") + return arrayOf((counter++).toUByte()) // TODO Wow. Such random. Very entropy. } } \ No newline at end of file diff --git a/multiplatform-crypto/src/jvmMain/kotlin/com/ionspin/kotlin/crypto/SRNG.kt b/multiplatform-crypto/src/jvmMain/kotlin/com/ionspin/kotlin/crypto/SRNG.kt index 8e8bbd7..8bfdaa3 100644 --- a/multiplatform-crypto/src/jvmMain/kotlin/com/ionspin/kotlin/crypto/SRNG.kt +++ b/multiplatform-crypto/src/jvmMain/kotlin/com/ionspin/kotlin/crypto/SRNG.kt @@ -16,13 +16,19 @@ package com.ionspin.kotlin.crypto +import java.security.SecureRandom + /** * Created by Ugljesa Jovanovic * ugljesa.jovanovic@ionspin.com * on 21-Sep-2019 */ +@ExperimentalUnsignedTypes actual object SRNG { + val secureRandom = SecureRandom() actual fun getRandomBytes(amount: Int): Array { - TODO("not implemented yet") + val byteArray = ByteArray(amount) + secureRandom.nextBytes(byteArray) + return byteArray.toUByteArray().toTypedArray() } } \ No newline at end of file diff --git a/multiplatform-crypto/src/nativeMain/kotlin/com/ionspin/kotlin/crypto/SRNG.kt b/multiplatform-crypto/src/nativeMain/kotlin/com/ionspin/kotlin/crypto/SRNG.kt index 8e8bbd7..ea8b0fc 100644 --- a/multiplatform-crypto/src/nativeMain/kotlin/com/ionspin/kotlin/crypto/SRNG.kt +++ b/multiplatform-crypto/src/nativeMain/kotlin/com/ionspin/kotlin/crypto/SRNG.kt @@ -16,13 +16,26 @@ package com.ionspin.kotlin.crypto +import kotlinx.cinterop.* +import platform.posix.* + /** * Created by Ugljesa Jovanovic * ugljesa.jovanovic@ionspin.com * on 21-Sep-2019 */ actual object SRNG { + @Suppress("EXPERIMENTAL_UNSIGNED_LITERALS") actual fun getRandomBytes(amount: Int): Array { - TODO("not implemented yet") + memScoped { + val array = allocArray(amount) + val urandomFile = fopen("/dev/urandom", "rb") + if (urandomFile != null) { + fread(array, 1, amount.convert(), urandomFile) + } + return Array(amount) { + array[it] + } + } } } \ No newline at end of file From 8ecc55af1fc3a0331e48cca70ab5b7b36596f051 Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Sat, 21 Sep 2019 22:39:59 +0200 Subject: [PATCH 10/16] Fix decryption --- .../kotlin/com/ionspin/kotlin/crypto/symmetric/AesCbc.kt | 3 ++- .../com/ionspin/kotlin/crypto/symmetric/AesCbcTest.kt | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) 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 01a1597..b0f1fc0 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 @@ -18,6 +18,7 @@ package com.ionspin.kotlin.crypto.symmetric import com.ionspin.kotlin.crypto.SRNG import com.ionspin.kotlin.crypto.chunked +import com.ionspin.kotlin.crypto.toHexString import com.ionspin.kotlin.crypto.xor /** @@ -145,10 +146,10 @@ class AesCbc internal constructor(val aesKey: AesKey, val mode: Mode, initializa Mode.DECRYPT -> { if (currentOutput.isEmpty()) { currentOutput = Aes.decrypt(aesKey, data) xor iv - previousEncrypted = data } else { currentOutput = Aes.decrypt(aesKey, data) xor previousEncrypted } + previousEncrypted = data currentOutput } } 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 0c676c9..442df8d 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 @@ -38,7 +38,7 @@ class AesCbcTest { val aesCbc = AesCbc(AesKey.Aes128Key(key), mode = Mode.ENCRYPT, initializationVector = iv.hexStringToUByteArray()) aesCbc.addData(plaintext.hexStringToUByteArray()) val encrypted = aesCbc.encrypt() - println("Decrypted: ${encrypted.toHexString()}") + println("Encrypted: ${encrypted.toHexString()}") assertTrue { expectedCipherText == encrypted.toHexString() } @@ -54,7 +54,7 @@ class AesCbcTest { val aesCbc = AesCbc(AesKey.Aes128Key(key), mode = Mode.DECRYPT, initializationVector = iv.hexStringToUByteArray()) aesCbc.addData(cipherText.hexStringToUByteArray()) val decrypted = aesCbc.decrypt() - println("Encrypted: ${decrypted.toHexString()}") + println("Decrypted: ${decrypted.toHexString()}") assertTrue { expectedPlainText == decrypted.toHexString() } @@ -64,4 +64,6 @@ class AesCbcTest { + + } \ No newline at end of file From a8ad00a690830d392ed92cd79ee2100d20fbcdc5 Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Sat, 21 Sep 2019 22:52:10 +0200 Subject: [PATCH 11/16] Remove padding when decrypting --- .../kotlin/com/ionspin/kotlin/crypto/symmetric/AesCbc.kt | 7 +++++-- .../com/ionspin/kotlin/crypto/symmetric/AesCbcTest.kt | 4 ++++ 2 files changed, 9 insertions(+), 2 deletions(-) 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 b0f1fc0..33663a5 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 @@ -18,7 +18,6 @@ package com.ionspin.kotlin.crypto.symmetric import com.ionspin.kotlin.crypto.SRNG import com.ionspin.kotlin.crypto.chunked -import com.ionspin.kotlin.crypto.toHexString import com.ionspin.kotlin.crypto.xor /** @@ -125,7 +124,11 @@ class AesCbc internal constructor(val aesKey: AesKey, val mode: Mode, initializa } fun decrypt(): Array { - return output.reversed().foldRight(Array(0) { 0U }) { arrayOfUBytes, acc -> acc + arrayOfUBytes } + val removePaddingCount = output.last().last() + val removedPadding = output.last().dropLast(removePaddingCount.toInt() and 0x7F) + val preparedOutput = output.dropLast(1).toTypedArray() + removedPadding.toTypedArray() + + return preparedOutput.reversed().foldRight(Array(0) { 0U }) { arrayOfUBytes, acc -> acc + arrayOfUBytes } } private fun appendToBuffer(array: Array, start: Int) { 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 442df8d..12c3370 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 @@ -66,4 +66,8 @@ class AesCbcTest { + + + + } \ No newline at end of file From 18ac28f3c38923583f1189450af7ac627ff93bfa Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Sun, 22 Sep 2019 22:46:30 +0200 Subject: [PATCH 12/16] Working encryption ctr, but decryption fails --- .../ionspin/kotlin/crypto/symmetric/Aes.kt | 21 ++- .../ionspin/kotlin/crypto/symmetric/AesCbc.kt | 16 +- .../ionspin/kotlin/crypto/symmetric/AesCtr.kt | 162 ++++++++++++++++++ .../kotlin/crypto/symmetric/AesCtrTest.kt | 75 ++++++++ .../kotlin/com/ionspin/kotlin/crypto/SRNG.kt | 7 + 5 files changed, 276 insertions(+), 5 deletions(-) create mode 100644 multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/AesCtr.kt create mode 100644 multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesCtrTest.kt diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt index 427cc74..b775a68 100644 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt @@ -84,7 +84,8 @@ internal class Aes internal constructor(val aesKey: AesKey, val input: Array @@ -247,11 +248,16 @@ internal class Aes internal constructor(val aesKey: AesKey, val input: Array it xor temp[index] }.toTypedArray() + clearArray(temp) } return expandedKey } fun encrypt(): Array { + if (completed) { + throw RuntimeException("Encrypt can only be called once per Aes instance, since the state is cleared at the " + + "end of the operation") + } printState() addRoundKey() printState() @@ -280,11 +286,16 @@ internal class Aes internal constructor(val aesKey: AesKey, val input: Array { + if (completed) { + throw RuntimeException("Decrypt can only be called once per Aes instance, since the state is cleared at the " + + "end of the operation") + } round = numberOfRounds printState() inverseAddRoundKey() @@ -316,9 +327,15 @@ internal class Aes internal constructor(val aesKey: AesKey, val input: Array) { + array.indices.forEach { array[it] = 0U } + } + private fun printState() { 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 33663a5..7369b94 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 @@ -125,10 +125,20 @@ class AesCbc internal constructor(val aesKey: AesKey, val mode: Mode, initializa fun decrypt(): Array { val removePaddingCount = output.last().last() - val removedPadding = output.last().dropLast(removePaddingCount.toInt() and 0x7F) - val preparedOutput = output.dropLast(1).toTypedArray() + removedPadding.toTypedArray() - return preparedOutput.reversed().foldRight(Array(0) { 0U }) { arrayOfUBytes, acc -> acc + arrayOfUBytes } + + val removedPadding = if (removePaddingCount > 0U && removePaddingCount < 16U) { + output.last().dropLast(removePaddingCount.toInt() and 0x7F) + } else { + output.last().toList() + } + val preparedOutput = output.dropLast(1).toTypedArray() + removedPadding.toTypedArray() + //JS compiler freaks out here if we don't supply exact type + val reversed : List> = preparedOutput.reversed() as List> + val folded : Array = reversed.foldRight(Array(0) { 0U }) { arrayOfUBytes, acc -> + acc + arrayOfUBytes } + return folded + } private fun appendToBuffer(array: Array, start: Int) { 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 new file mode 100644 index 0000000..30806c1 --- /dev/null +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/AesCtr.kt @@ -0,0 +1,162 @@ +/* + * 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.symmetric + +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.xor + +/** + * Created by Ugljesa Jovanovic + * ugljesa.jovanovic@ionspin.com + * on 22-Sep-2019 + */ +@ExperimentalUnsignedTypes +class AesCtr internal constructor(val aesKey: AesKey, val mode: Mode, initialCounter: Array? = null) { + + companion object { + const val BLOCK_BYTES = 16 + + val modularCreator = ModularBigInteger.creatorForModulo(BigInteger.ONE.shl(129) - 1) + + fun encrypt(aesKey: AesKey, data: Array): Array { + val aesCbc = AesCbc(aesKey, Mode.ENCRYPT) + aesCbc.addData(data) + return aesCbc.encrypt() + } + + private fun padToBlock(unpadded: Array): Array { + val paddingSize = 16 - unpadded.size + if (unpadded.size == BLOCK_BYTES) { + return unpadded + } + + if (unpadded.size == BLOCK_BYTES) { + return Array(BLOCK_BYTES) { + BLOCK_BYTES.toUByte() + } + } + + if (unpadded.size > BLOCK_BYTES) { + throw IllegalStateException("Block larger than 128 bytes") + } + + return Array(BLOCK_BYTES) { + when (it) { + in unpadded.indices -> unpadded[it] + else -> paddingSize.toUByte() + } + } + + } + } + + var currentOutput: Array = arrayOf() + var previousEncrypted: Array = arrayOf() + val counterStart = initialCounter ?: SRNG.getRandomBytes(16) + var blockCounter = modularCreator.fromBigInteger(BigInteger.fromUByteArray(counterStart, Endianness.BIG)) + + val output = MutableList>(0) { arrayOf() } + + var buffer: Array = UByteArray(16) { 0U }.toTypedArray() + var bufferCounter = 0 + + fun addData(data: Array) { + //Padding + when { + bufferCounter + data.size < BLOCK_BYTES -> appendToBuffer(data, bufferCounter) + bufferCounter + data.size >= BLOCK_BYTES -> { + val chunked = data.chunked(BLOCK_BYTES) + chunked.forEach { chunk -> + if (bufferCounter + chunk.size < BLOCK_BYTES) { + appendToBuffer(chunk, bufferCounter) + } else { + chunk.copyInto( + destination = buffer, + destinationOffset = bufferCounter, + startIndex = 0, + endIndex = BLOCK_BYTES - bufferCounter + ) + output += consumeBlock(buffer, blockCounter) + blockCounter += 1 + buffer = Array(BLOCK_BYTES) { + when (it) { + in (0 until (chunk.size - (BLOCK_BYTES - bufferCounter))) -> { + chunk[it + (BLOCK_BYTES - bufferCounter)] + } + else -> { + 0U + } + } + + } + bufferCounter = chunk.size - (BLOCK_BYTES - bufferCounter) + } + } + + } + } + + } + + fun encrypt(): Array { + if (bufferCounter > 0) { + val lastBlockPadded = padToBlock(buffer) + if (lastBlockPadded.size > BLOCK_BYTES) { + val chunks = lastBlockPadded.chunked(BLOCK_BYTES) + output += consumeBlock(chunks[0], blockCounter) + blockCounter += 1 + output += consumeBlock(chunks[1], blockCounter) + } else { + output += consumeBlock(lastBlockPadded, blockCounter) + } + } + return output.reversed().foldRight(Array(0) { 0U }) { arrayOfUBytes, acc -> acc + arrayOfUBytes } + } + + fun decrypt(): Array { + val removePaddingCount = output.last().last() + val removedPadding = output.last().dropLast(removePaddingCount.toInt() and 0x7F) + val preparedOutput = output.dropLast(1).toTypedArray() + removedPadding.toTypedArray() + //JS compiler freaks out here if we don't supply exact type + val reversed : List> = preparedOutput.reversed() as List> + val folded : Array = reversed.foldRight(Array(0) { 0U }) { arrayOfUBytes, acc -> + acc + arrayOfUBytes } + return folded + } + + private fun appendToBuffer(array: Array, start: Int) { + array.copyInto(destination = buffer, destinationOffset = start, startIndex = 0, endIndex = array.size) + bufferCounter += array.size + } + + private fun consumeBlock(data: Array, blockCount: ModularBigInteger): Array { + return when (mode) { + Mode.ENCRYPT -> { + Aes.encrypt(aesKey, blockCount.toUByteArray(Endianness.BIG)) xor data + } + Mode.DECRYPT -> { + Aes.decrypt(aesKey, blockCount.toUByteArray(Endianness.BIG)) xor data + } + } + + } + +} \ No newline at end of file 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 new file mode 100644 index 0000000..6f80b71 --- /dev/null +++ b/multiplatform-crypto/src/commonTest/kotlin/com/ionspin/kotlin/crypto/symmetric/AesCtrTest.kt @@ -0,0 +1,75 @@ +/* + * 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.symmetric + +import com.ionspin.kotlin.crypto.hexStringToUByteArray +import com.ionspin.kotlin.crypto.toHexString +import kotlin.test.Test +import kotlin.test.assertTrue + +/** + * Created by Ugljesa Jovanovic + * ugljesa.jovanovic@ionspin.com + * on 21-Sep-2019 + */ +@ExperimentalUnsignedTypes +class AesCtrTest { + + @Test + fun testCtrEncryption() { + val key = "2b7e151628aed2a6abf7158809cf4f3c" + val ic = "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff" + val plaintext = "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710" + val expectedCipherText = "874d6191b620e3261bef6864990db6ce9806f66b7970fdff8617187bb9fffdff5ae4df3edbd5d35e5b4f09020db03eab1e031dda2fbe03d1792170a0f3009cee" + val aesCbc = AesCtr(AesKey.Aes128Key(key), mode = Mode.ENCRYPT, initialCounter = ic.hexStringToUByteArray()) + aesCbc.addData( + plaintext.hexStringToUByteArray() + ) + val encrypted = aesCbc.encrypt() + println("Encrypted: ${encrypted.toHexString()}") + assertTrue { + expectedCipherText == encrypted.toHexString() + } + + } + + @Test + fun testCtrDecryption() { + val key = "2b7e151628aed2a6abf7158809cf4f3c" + val ic = "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff" + val cipherText = "874d6191b620e3261bef6864990db6ce9806f66b7970fdff8617187bb9fffdff5ae4df3edbd5d35e5b4f09020db03eab1e031dda2fbe03d1792170a0f3009cee" + val expectedPlainText = "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710" + val aesCbc = AesCtr(AesKey.Aes128Key(key), mode = Mode.DECRYPT, initialCounter = ic.hexStringToUByteArray()) + aesCbc.addData(cipherText.hexStringToUByteArray()) + val decrypted = aesCbc.decrypt() + println("Decrypted: ${decrypted.toHexString()}") + assertTrue { + expectedPlainText == decrypted.toHexString() + } + + } + + + + + + + + + + +} \ No newline at end of file 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 36cd9e2..c1db5e7 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 @@ -23,7 +23,14 @@ package com.ionspin.kotlin.crypto */ 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 arrayOf((counter++).toUByte()) // TODO Wow. Such random. Very entropy. } } \ No newline at end of file From 69c81ec8e9062eb8c47012967a283d84b8b7b084 Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Mon, 23 Sep 2019 22:14:42 +0200 Subject: [PATCH 13/16] Working ctr decryption --- .../kotlin/com/ionspin/kotlin/crypto/symmetric/AesCtr.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) 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 30806c1..23e63df 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 @@ -133,7 +133,11 @@ class AesCtr internal constructor(val aesKey: AesKey, val mode: Mode, initialCou fun decrypt(): Array { val removePaddingCount = output.last().last() - val removedPadding = output.last().dropLast(removePaddingCount.toInt() and 0x7F) + val removedPadding = if (removePaddingCount > 0U && removePaddingCount < 16U) { + output.last().dropLast(removePaddingCount.toInt() and 0x7F) + } else { + output.last().toList() + } val preparedOutput = output.dropLast(1).toTypedArray() + removedPadding.toTypedArray() //JS compiler freaks out here if we don't supply exact type val reversed : List> = preparedOutput.reversed() as List> @@ -153,7 +157,7 @@ class AesCtr internal constructor(val aesKey: AesKey, val mode: Mode, initialCou Aes.encrypt(aesKey, blockCount.toUByteArray(Endianness.BIG)) xor data } Mode.DECRYPT -> { - Aes.decrypt(aesKey, blockCount.toUByteArray(Endianness.BIG)) xor data + Aes.encrypt(aesKey, blockCount.toUByteArray(Endianness.BIG)) xor data } } From ec3b87db4963ca58a7d960dbec1ff1e73d9d295a Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Mon, 23 Sep 2019 23:14:55 +0200 Subject: [PATCH 14/16] Aes implemented --- README.md | 58 +++++++- .../ionspin/kotlin/crypto/symmetric/AesCbc.kt | 70 +++++++++- .../ionspin/kotlin/crypto/symmetric/AesCtr.kt | 131 +++++++++++------- .../kotlin/crypto/symmetric/AesCbcTest.kt | 74 ++++++---- .../kotlin/crypto/symmetric/AesCtrTest.kt | 80 +++++++---- 5 files changed, 302 insertions(+), 111 deletions(-) diff --git a/README.md b/README.md index 673ee68..de19e49 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,9 @@ Kotlin Multiplatform Crypto is a library for various cryptographic applications. This is an extremely early release, currently only consisting of Blake2b and SHA256 and 512. +API is very opinionated, ment to be used on both encrypting and decrypting side. The idea is that API leaves less room for +errors when using it. + ## Notes & Roadmap **The API will move fast and break often until v1.0** @@ -28,8 +31,6 @@ No. This is an experimental implementation, mostly for expanding personal understanding of cryptography. It's not peer reviewed, not guaranteed to be bug free, and not guaranteed to be secure. -## Supported - ## Hashing functions * Blake2b * SHA512 @@ -37,6 +38,7 @@ It's not peer reviewed, not guaranteed to be bug free, and not guaranteed to be ## Symmetric cipher * AES + * Modes: CBC, CTR More to come. @@ -129,6 +131,58 @@ val sha512 = Sha512() sha512.update("abc") val result = sha512.digest() ``` +### Symmetric encryption + +#### AES + +Aes is available with CBC and CTR mode through `AesCbc` and `AesCtr` classes/objects. +Similarly to hashes you can either use stateless or updateable version. + +Initialization vector, or counter states are chosen by the SDK automaticaly, and returned alongside encrypted data + +##### Stateless AesCbc and AesCtr + +AesCtr + +```kotlin +val keyString = "4278b840fb44aaa757c1bf04acbe1a3e" +val key = AesKey.Aes128Key(keyString) +val plainText = "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710" + +val encryptedDataAndInitializationVector = AesCtr.encrypt(key, plainText.hexStringToUByteArray()) +val decrypted = AesCtr.decrypt( + key, + encryptedDataAndInitializationVector.encryptedData, + encryptedDataAndInitializationVector.initialCounter +) +plainText == decrypted.toHexString() +``` + +AesCbc + +```kotlin + +val keyString = "4278b840fb44aaa757c1bf04acbe1a3e" +val key = AesKey.Aes128Key(keyString) + +val plainText = "3c888bbbb1a8eb9f3e9b87acaad986c466e2f7071c83083b8a557971918850e5" + +val encryptedDataAndInitializationVector = AesCbc.encrypt(key, plainText.hexStringToUByteArray()) +val decrypted = AesCbc.decrypt( + key, + encryptedDataAndInitializationVector.encryptedData, + encryptedDataAndInitializationVector.initilizationVector +) +plainText == decrypted.toHexString() + +``` + + + + + + + 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 7369b94..a9a3038 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 @@ -21,6 +21,12 @@ import com.ionspin.kotlin.crypto.chunked import com.ionspin.kotlin.crypto.xor /** + * Advanced encryption standard with cipher block chaining and PKCS #5 + * + * For bulk encryption/decryption use [AesCbc.encrypt] and [AesCbc.decrypt] + * + * To get an instance of AesCbc and then feed it data sequentially with [addData] use [createEncryptor] and [createDecryptor] + * * Created by Ugljesa Jovanovic * ugljesa.jovanovic@ionspin.com * on 21-Sep-2019 @@ -30,13 +36,39 @@ class AesCbc internal constructor(val aesKey: AesKey, val mode: Mode, initializa companion object { const val BLOCK_BYTES = 16 + /** + * Creates and returns AesCbc instance that can be fed data using [addData]. Once you have submitted all + * data call [encrypt] + */ + fun createEncryptor(aesKey: AesKey) : AesCbc { + return AesCbc(aesKey, Mode.ENCRYPT) + } + /** + * Creates and returns AesCbc instance that can be fed data using [addData]. Once you have submitted all + * data call [decrypt] + */ + fun createDecryptor(aesKey : AesKey) : AesCbc { + return AesCbc(aesKey, Mode.DECRYPT) + } - fun encrypt(aesKey: AesKey, data: Array): Array { + /** + * Bulk encryption, returns encrypted data and a random initialization vector + */ + fun encrypt(aesKey: AesKey, data: Array): EncryptedDataAndInitializationVector { val aesCbc = AesCbc(aesKey, Mode.ENCRYPT) aesCbc.addData(data) return aesCbc.encrypt() } + /** + * Bulk decryption, returns decrypted data + */ + fun decrypt(aesKey: AesKey, data: Array, initialCounter: Array? = null): Array { + val aesCbc = AesCbc(aesKey, Mode.DECRYPT, initialCounter) + aesCbc.addData(data) + return aesCbc.decrypt() + } + private fun padToBlock(unpadded: Array): Array { val paddingSize = 16 - unpadded.size if (unpadded.size == BLOCK_BYTES) { @@ -109,7 +141,11 @@ class AesCbc internal constructor(val aesKey: AesKey, val mode: Mode, initializa } - fun encrypt(): Array { + /** + * Encrypt fed data and return it alongside the randomly chosen initialization vector + * @return Encrypted data and initialization vector + */ + fun encrypt(): EncryptedDataAndInitializationVector { if (bufferCounter > 0) { val lastBlockPadded = padToBlock(buffer) if (lastBlockPadded.size > BLOCK_BYTES) { @@ -120,9 +156,16 @@ class AesCbc internal constructor(val aesKey: AesKey, val mode: Mode, initializa output += consumeBlock(lastBlockPadded) } } - return output.reversed().foldRight(Array(0) { 0U }) { arrayOfUBytes, acc -> acc + arrayOfUBytes } + return EncryptedDataAndInitializationVector( + output.reversed().foldRight(Array(0) { 0U }) { arrayOfUBytes, acc -> acc + arrayOfUBytes }, + iv + ) } + /** + * Decrypt data + * @return Decrypted data + */ fun decrypt(): Array { val removePaddingCount = output.last().last() @@ -169,4 +212,25 @@ class AesCbc internal constructor(val aesKey: AesKey, val mode: Mode, initializa } +} + +@ExperimentalUnsignedTypes +data class EncryptedDataAndInitializationVector(val encryptedData : Array, val initilizationVector : Array) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || this::class != other::class) return false + + other as EncryptedDataAndInitializationVector + + if (!encryptedData.contentEquals(other.encryptedData)) return false + if (!initilizationVector.contentEquals(other.initilizationVector)) return false + + return true + } + + override fun hashCode(): Int { + var result = encryptedData.contentHashCode() + result = 31 * result + initilizationVector.contentHashCode() + return result + } } \ No newline at end of file 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 23e63df..a966dde 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 @@ -21,9 +21,17 @@ 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.symmetric.AesCtr.Companion.encrypt import com.ionspin.kotlin.crypto.xor /** + * + * Advanced encryption standard with counter mode + * + * For bulk encryption/decryption use [AesCtr.encrypt] and [AesCtr.decrypt] + * + * To get an instance of AesCtr and then feed it data sequentially with [addData] use [createEncryptor] and [createDecryptor] + * * Created by Ugljesa Jovanovic * ugljesa.jovanovic@ionspin.com * on 22-Sep-2019 @@ -34,38 +42,38 @@ class AesCtr internal constructor(val aesKey: AesKey, val mode: Mode, initialCou companion object { const val BLOCK_BYTES = 16 - val modularCreator = ModularBigInteger.creatorForModulo(BigInteger.ONE.shl(129) - 1) - - fun encrypt(aesKey: AesKey, data: Array): Array { - val aesCbc = AesCbc(aesKey, Mode.ENCRYPT) - aesCbc.addData(data) - return aesCbc.encrypt() + val modularCreator = ModularBigInteger.creatorForModulo(BigInteger.ONE.shl(128) - 1) + /** + * Creates and returns AesCtr instance that can be fed data using [addData]. Once you have submitted all + * data call [encrypt] + */ + fun createEncryptor(aesKey: AesKey) : AesCtr { + return AesCtr(aesKey, Mode.ENCRYPT) + } + /** + * Creates and returns AesCtr instance that can be fed data using [addData]. Once you have submitted all + * data call [decrypt] + */ + fun createDecryptor(aesKey : AesKey) : AesCtr { + return AesCtr(aesKey, Mode.DECRYPT) + } + /** + * Bulk encryption, returns encrypted data and a random initial counter + */ + fun encrypt(aesKey: AesKey, data: Array): EncryptedDataAndInitialCounter { + val aesCtr = AesCtr(aesKey, Mode.ENCRYPT) + aesCtr.addData(data) + return aesCtr.encrypt() + } + /** + * Bulk decryption, returns decrypted data + */ + fun decrypt(aesKey: AesKey, data: Array, initialCounter: Array? = null): Array { + val aesCtr = AesCtr(aesKey, Mode.DECRYPT, initialCounter) + aesCtr.addData(data) + return aesCtr.decrypt() } - private fun padToBlock(unpadded: Array): Array { - val paddingSize = 16 - unpadded.size - if (unpadded.size == BLOCK_BYTES) { - return unpadded - } - - if (unpadded.size == BLOCK_BYTES) { - return Array(BLOCK_BYTES) { - BLOCK_BYTES.toUByte() - } - } - - if (unpadded.size > BLOCK_BYTES) { - throw IllegalStateException("Block larger than 128 bytes") - } - - return Array(BLOCK_BYTES) { - when (it) { - in unpadded.indices -> unpadded[it] - else -> paddingSize.toUByte() - } - } - - } } var currentOutput: Array = arrayOf() @@ -115,34 +123,32 @@ class AesCtr internal constructor(val aesKey: AesKey, val mode: Mode, initialCou } } - - fun encrypt(): Array { + /** + * Encrypt fed data and return it alongside the randomly chosen initial counter state + * @return Encrypted data and initial counter state + */ + fun encrypt(): EncryptedDataAndInitialCounter { if (bufferCounter > 0) { - val lastBlockPadded = padToBlock(buffer) - if (lastBlockPadded.size > BLOCK_BYTES) { - val chunks = lastBlockPadded.chunked(BLOCK_BYTES) - output += consumeBlock(chunks[0], blockCounter) - blockCounter += 1 - output += consumeBlock(chunks[1], blockCounter) - } else { - output += consumeBlock(lastBlockPadded, blockCounter) - } + output += consumeBlock(buffer, blockCounter) } - return output.reversed().foldRight(Array(0) { 0U }) { arrayOfUBytes, acc -> acc + arrayOfUBytes } + return EncryptedDataAndInitialCounter( + output.reversed().foldRight(Array(0) { 0U }) { arrayOfUBytes, acc -> acc + arrayOfUBytes }, + counterStart + ) } - + /** + * Decrypt data + * @return Decrypted data + */ fun decrypt(): Array { - val removePaddingCount = output.last().last() - val removedPadding = if (removePaddingCount > 0U && removePaddingCount < 16U) { - output.last().dropLast(removePaddingCount.toInt() and 0x7F) - } else { - output.last().toList() + if (bufferCounter > 0) { + output += consumeBlock(buffer, blockCounter) } - val preparedOutput = output.dropLast(1).toTypedArray() + removedPadding.toTypedArray() //JS compiler freaks out here if we don't supply exact type - val reversed : List> = preparedOutput.reversed() as List> - val folded : Array = reversed.foldRight(Array(0) { 0U }) { arrayOfUBytes, acc -> - acc + arrayOfUBytes } + val reversed: List> = output.reversed() as List> + val folded: Array = reversed.foldRight(Array(0) { 0U }) { arrayOfUBytes, acc -> + acc + arrayOfUBytes + } return folded } @@ -163,4 +169,25 @@ class AesCtr internal constructor(val aesKey: AesKey, val mode: Mode, initialCou } +} + +@ExperimentalUnsignedTypes +data class EncryptedDataAndInitialCounter(val encryptedData : Array, val initialCounter : Array) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || this::class != other::class) return false + + other as EncryptedDataAndInitializationVector + + if (!encryptedData.contentEquals(other.encryptedData)) return false + if (!initialCounter.contentEquals(other.initilizationVector)) return false + + return true + } + + override fun hashCode(): Int { + var result = encryptedData.contentHashCode() + result = 31 * result + initialCounter.contentHashCode() + return result + } } \ 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 12c3370..98b56a7 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 @@ -31,43 +31,67 @@ class AesCbcTest { @Test fun testCbcEncryption() { - val key = "4278b840fb44aaa757c1bf04acbe1a3e" - val iv = "57f02a5c5339daeb0a2908a06ac6393f" - val plaintext = "3c888bbbb1a8eb9f3e9b87acaad986c466e2f7071c83083b8a557971918850e5" - val expectedCipherText = "479c89ec14bc98994e62b2c705b5014e175bd7832e7e60a1e92aac568a861eb7" - val aesCbc = AesCbc(AesKey.Aes128Key(key), mode = Mode.ENCRYPT, initializationVector = iv.hexStringToUByteArray()) - aesCbc.addData(plaintext.hexStringToUByteArray()) - val encrypted = aesCbc.encrypt() - println("Encrypted: ${encrypted.toHexString()}") assertTrue { - expectedCipherText == encrypted.toHexString() + val key = "4278b840fb44aaa757c1bf04acbe1a3e" + val iv = "57f02a5c5339daeb0a2908a06ac6393f" + val plaintext = "3c888bbbb1a8eb9f3e9b87acaad986c466e2f7071c83083b8a557971918850e5" + val expectedCipherText = "479c89ec14bc98994e62b2c705b5014e175bd7832e7e60a1e92aac568a861eb7" + val aesCbc = + AesCbc(AesKey.Aes128Key(key), mode = Mode.ENCRYPT, initializationVector = iv.hexStringToUByteArray()) + aesCbc.addData(plaintext.hexStringToUByteArray()) + val encrypted = aesCbc.encrypt() + println("Encrypted: ${encrypted.encryptedData.toHexString()}") + + expectedCipherText == encrypted.encryptedData.toHexString() && + iv == encrypted.initilizationVector.toHexString() } + assertTrue { + val keyString = "4278b840fb44aaa757c1bf04acbe1a3e" + val key = AesKey.Aes128Key(keyString) + + val plainText = "3c888bbbb1a8eb9f3e9b87acaad986c466e2f7071c83083b8a557971918850e5" + + val encryptedDataAndInitializationVector = AesCbc.encrypt(key, plainText.hexStringToUByteArray()) + val decrypted = AesCbc.decrypt( + key, + encryptedDataAndInitializationVector.encryptedData, + encryptedDataAndInitializationVector.initilizationVector + ) + plainText == decrypted.toHexString() + } + + } @Test fun testCbcDecryption() { - val key = "4278b840fb44aaa757c1bf04acbe1a3e" - val iv = "57f02a5c5339daeb0a2908a06ac6393f" - val cipherText = "479c89ec14bc98994e62b2c705b5014e175bd7832e7e60a1e92aac568a861eb7" - val expectedPlainText = "3c888bbbb1a8eb9f3e9b87acaad986c466e2f7071c83083b8a557971918850e5" - val aesCbc = AesCbc(AesKey.Aes128Key(key), mode = Mode.DECRYPT, initializationVector = iv.hexStringToUByteArray()) - aesCbc.addData(cipherText.hexStringToUByteArray()) - val decrypted = aesCbc.decrypt() - println("Decrypted: ${decrypted.toHexString()}") assertTrue { + val key = "4278b840fb44aaa757c1bf04acbe1a3e" + val iv = "57f02a5c5339daeb0a2908a06ac6393f" + val cipherText = "479c89ec14bc98994e62b2c705b5014e175bd7832e7e60a1e92aac568a861eb7" + val expectedPlainText = "3c888bbbb1a8eb9f3e9b87acaad986c466e2f7071c83083b8a557971918850e5" + val aesCbc = + AesCbc(AesKey.Aes128Key(key), mode = Mode.DECRYPT, initializationVector = iv.hexStringToUByteArray()) + aesCbc.addData(cipherText.hexStringToUByteArray()) + val decrypted = aesCbc.decrypt() + println("Decrypted: ${decrypted.toHexString()}") + + expectedPlainText == decrypted.toHexString() + } + + assertTrue { + val key = "4278b840fb44aaa757c1bf04acbe1a3e" + val iv = "57f02a5c5339daeb0a2908a06ac6393f" + val cipherText = "479c89ec14bc98994e62b2c705b5014e175bd7832e7e60a1e92aac568a861eb7" + val expectedPlainText = "3c888bbbb1a8eb9f3e9b87acaad986c466e2f7071c83083b8a557971918850e5" + val decrypted = AesCbc.decrypt(AesKey.Aes128Key(key), cipherText.hexStringToUByteArray(), iv.hexStringToUByteArray()) + println("Decrypted: ${decrypted.toHexString()}") + expectedPlainText == decrypted.toHexString() } } - - - - - - - - } \ No newline at end of file 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 6f80b71..977766f 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 @@ -31,45 +31,67 @@ class AesCtrTest { @Test fun testCtrEncryption() { - val key = "2b7e151628aed2a6abf7158809cf4f3c" - val ic = "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff" - val plaintext = "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710" - val expectedCipherText = "874d6191b620e3261bef6864990db6ce9806f66b7970fdff8617187bb9fffdff5ae4df3edbd5d35e5b4f09020db03eab1e031dda2fbe03d1792170a0f3009cee" - val aesCbc = AesCtr(AesKey.Aes128Key(key), mode = Mode.ENCRYPT, initialCounter = ic.hexStringToUByteArray()) - aesCbc.addData( - plaintext.hexStringToUByteArray() - ) - val encrypted = aesCbc.encrypt() - println("Encrypted: ${encrypted.toHexString()}") assertTrue { - expectedCipherText == encrypted.toHexString() + val key = "2b7e151628aed2a6abf7158809cf4f3c" + val ic = "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff" + val plaintext = + "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710" + val expectedCipherText = + "874d6191b620e3261bef6864990db6ce9806f66b7970fdff8617187bb9fffdff5ae4df3edbd5d35e5b4f09020db03eab1e031dda2fbe03d1792170a0f3009cee" + val aesCtr = AesCtr(AesKey.Aes128Key(key), mode = Mode.ENCRYPT, initialCounter = ic.hexStringToUByteArray()) + aesCtr.addData( + plaintext.hexStringToUByteArray() + ) + val encrypted = aesCtr.encrypt() + println("Encrypted: ${encrypted.encryptedData.toHexString()}") + + expectedCipherText == encrypted.encryptedData.toHexString() && + ic == encrypted.initialCounter.toHexString() + } + + assertTrue { + val keyString = "4278b840fb44aaa757c1bf04acbe1a3e" + val key = AesKey.Aes128Key(keyString) + val plainText = "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710" + + val encryptedDataAndInitializationVector = AesCtr.encrypt(key, plainText.hexStringToUByteArray()) + val decrypted = AesCtr.decrypt( + key, + encryptedDataAndInitializationVector.encryptedData, + encryptedDataAndInitializationVector.initialCounter + ) + plainText == decrypted.toHexString() } } @Test fun testCtrDecryption() { - val key = "2b7e151628aed2a6abf7158809cf4f3c" - val ic = "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff" - val cipherText = "874d6191b620e3261bef6864990db6ce9806f66b7970fdff8617187bb9fffdff5ae4df3edbd5d35e5b4f09020db03eab1e031dda2fbe03d1792170a0f3009cee" - val expectedPlainText = "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710" - val aesCbc = AesCtr(AesKey.Aes128Key(key), mode = Mode.DECRYPT, initialCounter = ic.hexStringToUByteArray()) - aesCbc.addData(cipherText.hexStringToUByteArray()) - val decrypted = aesCbc.decrypt() - println("Decrypted: ${decrypted.toHexString()}") assertTrue { + val key = "2b7e151628aed2a6abf7158809cf4f3c" + val ic = "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff" + val cipherText = + "874d6191b620e3261bef6864990db6ce9806f66b7970fdff8617187bb9fffdff5ae4df3edbd5d35e5b4f09020db03eab1e031dda2fbe03d1792170a0f3009cee" + val expectedPlainText = + "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710" + val aesCtr = AesCtr(AesKey.Aes128Key(key), mode = Mode.DECRYPT, initialCounter = ic.hexStringToUByteArray()) + aesCtr.addData(cipherText.hexStringToUByteArray()) + val decrypted = aesCtr.decrypt() + println("Decrypted: ${decrypted.toHexString()}") + expectedPlainText == decrypted.toHexString() + } + + assertTrue { + val key = "2b7e151628aed2a6abf7158809cf4f3c" + val ic = "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff" + val cipherText = + "874d6191b620e3261bef6864990db6ce9806f66b7970fdff8617187bb9fffdff5ae4df3edbd5d35e5b4f09020db03eab1e031dda2fbe03d1792170a0f3009cee" + val expectedPlainText = + "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710" + val decrypted = AesCtr.decrypt(AesKey.Aes128Key(key), cipherText.hexStringToUByteArray(), ic.hexStringToUByteArray()) + println("Decrypted: ${decrypted.toHexString()}") expectedPlainText == decrypted.toHexString() } } - - - - - - - - - - } \ No newline at end of file From d8ea3d269399ce2830b0f21f9c04e1ab84f879ba Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Tue, 24 Sep 2019 00:52:49 +0200 Subject: [PATCH 15/16] Fix missing leading zeroes in counter --- multiplatform-crypto/build.gradle.kts | 2 -- .../ionspin/kotlin/crypto/symmetric/Aes.kt | 6 ---- .../ionspin/kotlin/crypto/symmetric/AesCbc.kt | 13 ++++--- .../ionspin/kotlin/crypto/symmetric/AesCtr.kt | 22 ++++++++++-- .../kotlin/crypto/symmetric/AesCbcTest.kt | 16 +++++++-- .../kotlin/crypto/symmetric/AesCtrTest.kt | 34 ++++++++++++------- .../kotlin/com/ionspin/kotlin/crypto/SRNG.kt | 2 +- 7 files changed, 63 insertions(+), 32 deletions(-) diff --git a/multiplatform-crypto/build.gradle.kts b/multiplatform-crypto/build.gradle.kts index ea57cb1..e9a1922 100644 --- a/multiplatform-crypto/build.gradle.kts +++ b/multiplatform-crypto/build.gradle.kts @@ -149,8 +149,6 @@ kotlin { } val nativeMain by creating { dependsOn(commonMain) - dependencies { - } } val nativeTest by creating { diff --git a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt index b775a68..69e2a00 100644 --- a/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt +++ b/multiplatform-crypto/src/commonMain/kotlin/com/ionspin/kotlin/crypto/symmetric/Aes.kt @@ -64,12 +64,6 @@ internal class Aes internal constructor(val aesKey: AesKey, val input: Array> = (0 until 4).map { outerCounter -> Array(4) { innerCounter -> input[innerCounter * 4 + outerCounter] } }.toTypedArray() 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 a9a3038..4d7125e 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 @@ -97,7 +97,11 @@ class AesCbc internal constructor(val aesKey: AesKey, val mode: Mode, initializa var currentOutput: Array = arrayOf() var previousEncrypted: Array = arrayOf() - val iv = initializationVector ?: SRNG.getRandomBytes(16) + val initVector = if (initializationVector.isNullOrEmpty()) { + SRNG.getRandomBytes(16) + } else { + initializationVector + } val output = MutableList>(0) { arrayOf() } @@ -158,7 +162,7 @@ class AesCbc internal constructor(val aesKey: AesKey, val mode: Mode, initializa } return EncryptedDataAndInitializationVector( output.reversed().foldRight(Array(0) { 0U }) { arrayOfUBytes, acc -> acc + arrayOfUBytes }, - iv + initVector ) } @@ -193,7 +197,8 @@ class AesCbc internal constructor(val aesKey: AesKey, val mode: Mode, initializa return when (mode) { Mode.ENCRYPT -> { currentOutput = if (currentOutput.isEmpty()) { - Aes.encrypt(aesKey, data xor iv) + println("IV: $initVector") + Aes.encrypt(aesKey, data xor initVector) } else { Aes.encrypt(aesKey, data xor currentOutput) } @@ -201,7 +206,7 @@ class AesCbc internal constructor(val aesKey: AesKey, val mode: Mode, initializa } Mode.DECRYPT -> { if (currentOutput.isEmpty()) { - currentOutput = Aes.decrypt(aesKey, data) xor iv + currentOutput = Aes.decrypt(aesKey, data) xor initVector } else { currentOutput = Aes.decrypt(aesKey, data) xor previousEncrypted } 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 a966dde..4d5373a 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 @@ -78,7 +78,11 @@ class AesCtr internal constructor(val aesKey: AesKey, val mode: Mode, initialCou var currentOutput: Array = arrayOf() var previousEncrypted: Array = arrayOf() - val counterStart = initialCounter ?: SRNG.getRandomBytes(16) + val counterStart = if (initialCounter.isNullOrEmpty()) { + SRNG.getRandomBytes(16) + } else { + initialCounter + } var blockCounter = modularCreator.fromBigInteger(BigInteger.fromUByteArray(counterStart, Endianness.BIG)) val output = MutableList>(0) { arrayOf() } @@ -158,17 +162,29 @@ class AesCtr internal constructor(val aesKey: AesKey, val mode: Mode, initialCou } private fun consumeBlock(data: Array, blockCount: ModularBigInteger): Array { + val blockCountAsByteArray = blockCount.toUByteArray(Endianness.BIG).expandCounterTo16Bytes() return when (mode) { Mode.ENCRYPT -> { - Aes.encrypt(aesKey, blockCount.toUByteArray(Endianness.BIG)) xor data + Aes.encrypt(aesKey, blockCountAsByteArray) xor data } Mode.DECRYPT -> { - Aes.encrypt(aesKey, blockCount.toUByteArray(Endianness.BIG)) xor data + Aes.encrypt(aesKey, blockCountAsByteArray) xor data } } } + private fun Array.expandCounterTo16Bytes() : Array { + return if (this.size < 16) { + println("Expanding") + val diff = 16 - this.size + val pad = Array(diff) { 0U } + pad + this + } else { + this + } + } + } @ExperimentalUnsignedTypes 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 98b56a7..b55a715 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 @@ -46,6 +46,12 @@ class AesCbcTest { iv == encrypted.initilizationVector.toHexString() } + + + } + + @Test + fun testEncryptionApi() { assertTrue { val keyString = "4278b840fb44aaa757c1bf04acbe1a3e" val key = AesKey.Aes128Key(keyString) @@ -60,8 +66,6 @@ class AesCbcTest { ) plainText == decrypted.toHexString() } - - } @Test @@ -80,6 +84,12 @@ class AesCbcTest { expectedPlainText == decrypted.toHexString() } + + + } + + @Test + fun testDecryptionApi() { assertTrue { val key = "4278b840fb44aaa757c1bf04acbe1a3e" val iv = "57f02a5c5339daeb0a2908a06ac6393f" @@ -90,8 +100,8 @@ class AesCbcTest { expectedPlainText == decrypted.toHexString() } - } + } \ No newline at end of file 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 977766f..e80a995 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 @@ -49,20 +49,23 @@ class AesCtrTest { ic == encrypted.initialCounter.toHexString() } - assertTrue { - val keyString = "4278b840fb44aaa757c1bf04acbe1a3e" - val key = AesKey.Aes128Key(keyString) - val plainText = "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710" + } - val encryptedDataAndInitializationVector = AesCtr.encrypt(key, plainText.hexStringToUByteArray()) - val decrypted = AesCtr.decrypt( - key, - encryptedDataAndInitializationVector.encryptedData, - encryptedDataAndInitializationVector.initialCounter - ) + @Test + fun testEncryptionApi() { + val keyString = "4278b840fb44aaa757c1bf04acbe1a3e" + val key = AesKey.Aes128Key(keyString) + val plainText = "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710" + + val encryptedDataAndInitializationVector = AesCtr.encrypt(key, plainText.hexStringToUByteArray()) + val decrypted = AesCtr.decrypt( + key, + encryptedDataAndInitializationVector.encryptedData, + encryptedDataAndInitializationVector.initialCounter + ) + assertTrue { plainText == decrypted.toHexString() } - } @Test @@ -81,7 +84,13 @@ class AesCtrTest { expectedPlainText == decrypted.toHexString() } - assertTrue { + + + } + + @Test + fun testCtrDecryptionApi() { + assertTrue { val key = "2b7e151628aed2a6abf7158809cf4f3c" val ic = "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff" val cipherText = @@ -92,6 +101,5 @@ class AesCtrTest { println("Decrypted: ${decrypted.toHexString()}") expectedPlainText == decrypted.toHexString() } - } } \ No newline at end of file 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 c1db5e7..0a7a85c 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 @@ -31,6 +31,6 @@ actual object SRNG { // } else { // throw RuntimeException("Secure random not supported yet for non-nodejs environment") // } - return arrayOf((counter++).toUByte()) // TODO Wow. Such random. Very entropy. + return Array(amount) { (counter++).toUByte() } // TODO Wow. Such random. Very entropy. } } \ No newline at end of file From 8d17b850149a4cc695f3852c044746099ad0c382 Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Wed, 25 Sep 2019 00:42:01 +0200 Subject: [PATCH 16/16] Update README and CHANGELOG --- CHANGELOG.md | 3 +++ README.md | 10 +++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e72430e..7d2f660 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ ## Descriptive changelog (All dates are DD.MM.YYYY) +#### AES - 0.0.3-SNAPSHOT - 25.9.2019 +- Added AES with CBC and CTR modes + #### Updatable SHA hash implementation - 0.0.2 - 21.7.2019 - Added "updatable" version for SHA - Moved sha and blake to hash package diff --git a/README.md b/README.md index de19e49..38dde37 100644 --- a/README.md +++ b/README.md @@ -31,12 +31,16 @@ No. This is an experimental implementation, mostly for expanding personal understanding of cryptography. It's not peer reviewed, not guaranteed to be bug free, and not guaranteed to be secure. +## Integration + + + ## Hashing functions * Blake2b * SHA512 * SHA256 -## Symmetric cipher +## Symmetric cipher (Currently only available only in 0.0.3-SNAPSHOT) * AES * Modes: CBC, CTR @@ -46,7 +50,7 @@ More to come. #### Gradle ```kotlin -implementation("com.ionspin.kotlin:crypto:0.0.1") +implementation("com.ionspin.kotlin:crypto:0.0.2") ``` #### Snapshot builds @@ -56,7 +60,7 @@ repositories { url = uri("https://oss.sonatype.org/content/repositories/snapshots") } } -implementation("com.ionspin.kotlin:crypto:0.0.1-SNAPSHOT") +implementation("com.ionspin.kotlin:crypto:0.0.3-SNAPSHOT") ```